Coverage Report

Created: 2025-12-31 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/classify/intproto.cpp
Line
Count
Source
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
9.77k
#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
0
#define OLD_MAX_NUM_CONFIGS 32
96
0
#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
410k
#define CircularIncrement(i, r) (((i) < (r)-1) ? ((i)++) : ((i) = 0))
103
104
/** macro for mapping floats to ints without bounds checking */
105
190k
#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
 */
180
47.6k
static int TruncateParam(float Param, int Min, int Max) {
181
47.6k
  int result;
182
47.6k
  if (Param < Min) {
183
1.11k
    result = Min;
184
46.5k
  } else if (Param > Max) {
185
7.21k
    result = Max;
186
39.3k
  } else {
187
39.3k
    result = static_cast<int>(std::floor(Param));
188
39.3k
  }
189
47.6k
  return result;
190
47.6k
}
191
192
/*-----------------------------------------------------------------------------
193
              Public Code
194
-----------------------------------------------------------------------------*/
195
/// Builds a feature from an FCOORD for position with all the necessary
196
/// clipping and rounding.
197
INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(const FCOORD &pos, uint8_t theta)
198
228M
    : X(ClipToRange<int16_t>(static_cast<int16_t>(pos.x() + 0.5), 0, 255))
199
228M
    , Y(ClipToRange<int16_t>(static_cast<int16_t>(pos.y() + 0.5), 0, 255))
200
228M
    , Theta(theta)
201
228M
    , CP_misses(0) {}
202
/** Builds a feature from ints with all the necessary clipping and casting. */
203
INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(int x, int y, int theta)
204
0
    : X(static_cast<uint8_t>(ClipToRange<int>(x, 0, UINT8_MAX)))
205
0
    , Y(static_cast<uint8_t>(ClipToRange<int>(y, 0, UINT8_MAX)))
206
0
    , Theta(static_cast<uint8_t>(ClipToRange<int>(theta, 0, UINT8_MAX)))
207
0
    , 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
 */
220
3.21M
void AddIntClass(INT_TEMPLATES_STRUCT *Templates, CLASS_ID ClassId, INT_CLASS_STRUCT *Class) {
221
3.21M
  int Pruner;
222
223
3.21M
  assert(LegalClassId(ClassId));
224
3.21M
  if (static_cast<unsigned>(ClassId) != Templates->NumClasses) {
225
0
    fprintf(stderr,
226
0
            "Please make sure that classes are added to templates"
227
0
            " in increasing order of ClassIds\n");
228
0
    exit(1);
229
0
  }
230
3.21M
  ClassForClassId(Templates, ClassId) = Class;
231
3.21M
  Templates->NumClasses++;
232
233
3.21M
  if (Templates->NumClasses > MaxNumClassesIn(Templates)) {
234
100k
    Pruner = Templates->NumClassPruners++;
235
100k
    Templates->ClassPruners[Pruner] = new CLASS_PRUNER_STRUCT;
236
100k
    memset(Templates->ClassPruners[Pruner], 0, sizeof(CLASS_PRUNER_STRUCT));
237
100k
  }
238
3.21M
} /* 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
 */
250
1.08k
int AddIntConfig(INT_CLASS_STRUCT *Class) {
251
1.08k
  int Index;
252
253
1.08k
  assert(Class->NumConfigs < MAX_NUM_CONFIGS);
254
255
1.08k
  Index = Class->NumConfigs++;
256
1.08k
  Class->ConfigLengths[Index] = 0;
257
1.08k
  return Index;
258
1.08k
} /* 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
 */
270
11.9k
int AddIntProto(INT_CLASS_STRUCT *Class) {
271
11.9k
  if (Class->NumProtos >= MAX_NUM_PROTOS) {
272
2
    return (NO_PROTO);
273
2
  }
274
275
11.9k
  int Index = Class->NumProtos++;
276
277
11.9k
  if (Class->NumProtos > MaxNumIntProtosIn(Class)) {
278
37
    int ProtoSetId = Class->NumProtoSets++;
279
37
    auto ProtoSet = new PROTO_SET_STRUCT;
280
37
    Class->ProtoSets[ProtoSetId] = ProtoSet;
281
37
    memset(ProtoSet, 0, sizeof(*ProtoSet));
282
283
    /* reallocate space for the proto lengths and install in class */
284
37
    Class->ProtoLengths.resize(MaxNumIntProtosIn(Class));
285
37
  }
286
287
  /* initialize proto so its length is zero and it isn't in any configs */
288
11.9k
  Class->ProtoLengths[Index] = 0;
289
11.9k
  auto Proto = ProtoForProtoId(Class, Index);
290
35.7k
  for (uint32_t *Word = Proto->Configs; Word < Proto->Configs + WERDS_PER_CONFIG_VEC; *Word++ = 0) {
291
23.8k
  }
292
293
11.9k
  return (Index);
294
11.9k
}
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
 */
306
void AddProtoToClassPruner(PROTO_STRUCT *Proto, CLASS_ID ClassId, INT_TEMPLATES_STRUCT *Templates)
307
#define MAX_LEVEL 2
308
659
{
309
659
  CLASS_PRUNER_STRUCT *Pruner;
310
659
  uint32_t ClassMask;
311
659
  uint32_t ClassCount;
312
659
  uint32_t WordIndex;
313
659
  int Level;
314
659
  float EndPad, SidePad, AnglePad;
315
659
  TABLE_FILLER TableFiller;
316
659
  FILL_SPEC FillSpec;
317
318
659
  Pruner = CPrunerFor(Templates, ClassId);
319
659
  WordIndex = CPrunerWordIndexFor(ClassId);
320
659
  ClassMask = CPrunerMaskFor(MAX_LEVEL, ClassId);
321
322
2.63k
  for (Level = classify_num_cp_levels - 1; Level >= 0; Level--) {
323
1.97k
    GetCPPadsForLevel(Level, &EndPad, &SidePad, &AnglePad);
324
1.97k
    ClassCount = CPrunerMaskFor(Level, ClassId);
325
1.97k
    InitTableFiller(EndPad, SidePad, AnglePad, Proto, &TableFiller);
326
327
11.2k
    while (!FillerDone(&TableFiller)) {
328
9.26k
      GetNextFill(&TableFiller, &FillSpec);
329
9.26k
      DoFill(&FillSpec, Pruner, ClassMask, ClassCount, WordIndex);
330
9.26k
    }
331
1.97k
  }
332
659
} /* 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
 */
344
11.9k
void AddProtoToProtoPruner(PROTO_STRUCT *Proto, int ProtoId, INT_CLASS_STRUCT *Class, bool debug) {
345
11.9k
  float X, Y, Length;
346
11.9k
  float Pad;
347
348
11.9k
  if (ProtoId >= Class->NumProtos) {
349
0
    tprintf("AddProtoToProtoPruner:assert failed: %d < %d", ProtoId, Class->NumProtos);
350
0
  }
351
11.9k
  assert(ProtoId < Class->NumProtos);
352
353
11.9k
  int Index = IndexForProto(ProtoId);
354
11.9k
  auto ProtoSet = Class->ProtoSets[SetForProto(ProtoId)];
355
356
11.9k
  float Angle = Proto->Angle;
357
11.9k
#ifndef _WIN32
358
11.9k
  assert(!std::isnan(Angle));
359
11.9k
#endif
360
361
11.9k
  FillPPCircularBits(ProtoSet->ProtoPruner[PRUNER_ANGLE], Index, Angle + ANGLE_SHIFT,
362
11.9k
                     classify_pp_angle_pad / 360.0, debug);
363
364
11.9k
  Angle *= 2.0 * M_PI;
365
11.9k
  Length = Proto->Length;
366
367
11.9k
  X = Proto->X + X_SHIFT;
368
11.9k
  Pad = std::max(fabs(std::cos(Angle)) * (Length / 2.0 + classify_pp_end_pad * GetPicoFeatureLength()),
369
11.9k
                 fabs(std::sin(Angle)) * (classify_pp_side_pad * GetPicoFeatureLength()));
370
371
11.9k
  FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_X], Index, X, Pad, debug);
372
373
11.9k
  Y = Proto->Y + Y_SHIFT;
374
11.9k
  Pad = std::max(fabs(std::sin(Angle)) * (Length / 2.0 + classify_pp_end_pad * GetPicoFeatureLength()),
375
11.9k
                 fabs(std::cos(Angle)) * (classify_pp_side_pad * GetPicoFeatureLength()));
376
377
11.9k
  FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_Y], Index, Y, Pad, debug);
378
11.9k
} /* 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
 */
385
122k
uint8_t Bucket8For(float param, float offset, int num_buckets) {
386
122k
  int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
387
122k
  return static_cast<uint8_t>(ClipToRange<int>(bucket, 0, num_buckets - 1));
388
122k
}
389
5.40k
uint16_t Bucket16For(float param, float offset, int num_buckets) {
390
5.40k
  int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
391
5.40k
  return static_cast<uint16_t>(ClipToRange<int>(bucket, 0, num_buckets - 1));
392
5.40k
}
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
 */
399
61.9k
uint8_t CircBucketFor(float param, float offset, int num_buckets) {
400
61.9k
  int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
401
61.9k
  return static_cast<uint8_t>(Modulo(bucket, num_buckets));
402
61.9k
} /* 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
 */
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
 */
430
1.08k
void ConvertConfig(BIT_VECTOR Config, int ConfigId, INT_CLASS_STRUCT *Class) {
431
1.08k
  int ProtoId;
432
1.08k
  INT_PROTO_STRUCT *Proto;
433
1.08k
  int TotalLength;
434
435
38.1k
  for (ProtoId = 0, TotalLength = 0; ProtoId < Class->NumProtos; ProtoId++) {
436
37.0k
    if (test_bit(Config, ProtoId)) {
437
16.4k
      Proto = ProtoForProtoId(Class, ProtoId);
438
16.4k
      SET_BIT(Proto->Configs, ConfigId);
439
16.4k
      TotalLength += Class->ProtoLengths[ProtoId];
440
16.4k
    }
441
37.0k
  }
442
1.08k
  Class->ConfigLengths[ConfigId] = TotalLength;
443
1.08k
} /* 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
 */
452
11.9k
void Classify::ConvertProto(PROTO_STRUCT *Proto, int ProtoId, INT_CLASS_STRUCT *Class) {
453
11.9k
  assert(ProtoId < Class->NumProtos);
454
455
11.9k
  INT_PROTO_STRUCT *P = ProtoForProtoId(Class, ProtoId);
456
457
11.9k
  float Param = Proto->A * 128;
458
11.9k
  P->A = TruncateParam(Param, -128, 127);
459
460
11.9k
  Param = -Proto->B * 256;
461
11.9k
  P->B = TruncateParam(Param, 0, 255);
462
463
11.9k
  Param = Proto->C * 128;
464
11.9k
  P->C = TruncateParam(Param, -128, 127);
465
466
11.9k
  Param = Proto->Angle * 256;
467
11.9k
  if (Param < 0 || Param >= 256) {
468
0
    P->Angle = 0;
469
11.9k
  } else {
470
11.9k
    P->Angle = static_cast<uint8_t>(Param);
471
11.9k
  }
472
473
  /* round proto length to nearest integer number of pico-features */
474
11.9k
  Param = (Proto->Length / GetPicoFeatureLength()) + 0.5;
475
11.9k
  Class->ProtoLengths[ProtoId] = TruncateParam(Param, 1, 255);
476
11.9k
  if (classify_learning_debug_level >= 2) {
477
0
    tprintf("Converted ffeat to (A=%d,B=%d,C=%d,L=%d)", P->A, P->B, P->C,
478
0
            Class->ProtoLengths[ProtoId]);
479
0
  }
480
11.9k
} /* 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
 */
490
INT_TEMPLATES_STRUCT *Classify::CreateIntTemplates(CLASSES FloatProtos,
491
0
                                           const UNICHARSET &target_unicharset) {
492
0
  CLASS_TYPE FClass;
493
0
  INT_CLASS_STRUCT *IClass;
494
0
  int ProtoId;
495
0
  int ConfigId;
496
497
0
  auto IntTemplates = new INT_TEMPLATES_STRUCT;
498
499
0
  for (unsigned ClassId = 0; ClassId < target_unicharset.size(); ClassId++) {
500
0
    FClass = &(FloatProtos[ClassId]);
501
0
    if (FClass->NumProtos == 0 && FClass->NumConfigs == 0 &&
502
0
        strcmp(target_unicharset.id_to_unichar(ClassId), " ") != 0) {
503
0
      tprintf("Warning: no protos/configs for %s in CreateIntTemplates()\n",
504
0
              target_unicharset.id_to_unichar(ClassId));
505
0
    }
506
0
    assert(UnusedClassIdIn(IntTemplates, ClassId));
507
0
    IClass = new INT_CLASS_STRUCT(FClass->NumProtos, FClass->NumConfigs);
508
0
    unsigned fs_size = FClass->font_set.size();
509
0
    FontSet fs;
510
0
    fs.reserve(fs_size);
511
0
    for (unsigned i = 0; i < fs_size; ++i) {
512
0
      fs.push_back(FClass->font_set[i]);
513
0
    }
514
0
    IClass->font_set_id = this->fontset_table_.push_back(std::move(fs));
515
0
    AddIntClass(IntTemplates, ClassId, IClass);
516
517
0
    for (ProtoId = 0; ProtoId < FClass->NumProtos; ProtoId++) {
518
0
      AddIntProto(IClass);
519
0
      ConvertProto(ProtoIn(FClass, ProtoId), ProtoId, IClass);
520
0
      AddProtoToProtoPruner(ProtoIn(FClass, ProtoId), ProtoId, IClass,
521
0
                            classify_learning_debug_level >= 2);
522
0
      AddProtoToClassPruner(ProtoIn(FClass, ProtoId), ClassId, IntTemplates);
523
0
    }
524
525
0
    for (ConfigId = 0; ConfigId < FClass->NumConfigs; ConfigId++) {
526
0
      AddIntConfig(IClass);
527
0
      ConvertConfig(FClass->Configurations[ConfigId], ConfigId, IClass);
528
0
    }
529
0
  }
530
0
  return (IntTemplates);
531
0
} /* CreateIntTemplates */
532
533
#ifndef GRAPHICS_DISABLED
534
/**
535
 * This routine renders the specified feature into a
536
 * global display list.
537
 *
538
 * Globals:
539
 * - FeatureShapes global display list for features
540
 * @param Feature   pico-feature to be displayed
541
 * @param Evidence  best evidence for this feature (0-1)
542
 */
543
void DisplayIntFeature(const INT_FEATURE_STRUCT *Feature, float Evidence) {
544
  ScrollView::Color color = GetMatchColorFor(Evidence);
545
  RenderIntFeature(IntMatchWindow, Feature, color);
546
  if (FeatureDisplayWindow) {
547
    RenderIntFeature(FeatureDisplayWindow, Feature, color);
548
  }
549
} /* DisplayIntFeature */
550
551
/**
552
 * This routine renders the specified proto into a
553
 * global display list.
554
 *
555
 * Globals:
556
 * - ProtoShapes global display list for protos
557
 * @param Class   class to take proto from
558
 * @param ProtoId   id of proto in Class to be displayed
559
 * @param Evidence  total evidence for proto (0-1)
560
 */
561
void DisplayIntProto(INT_CLASS_STRUCT *Class, PROTO_ID ProtoId, float Evidence) {
562
  ScrollView::Color color = GetMatchColorFor(Evidence);
563
  RenderIntProto(IntMatchWindow, Class, ProtoId, color);
564
  if (ProtoDisplayWindow) {
565
    RenderIntProto(ProtoDisplayWindow, Class, ProtoId, color);
566
  }
567
} /* DisplayIntProto */
568
#endif
569
570
/// This constructor creates a new integer class data structure
571
/// and returns it.  Sufficient space is allocated
572
/// to handle the specified number of protos and configs.
573
/// @param MaxNumProtos  number of protos to allocate space for
574
/// @param MaxNumConfigs number of configs to allocate space for
575
INT_CLASS_STRUCT::INT_CLASS_STRUCT(int MaxNumProtos, int MaxNumConfigs) :
576
3.21M
  NumProtos(0),
577
3.21M
  NumProtoSets((MaxNumProtos + PROTOS_PER_PROTO_SET - 1) / PROTOS_PER_PROTO_SET),
578
3.21M
  NumConfigs(0),
579
3.21M
  ProtoLengths(MaxNumIntProtosIn(this))
580
3.21M
{
581
3.21M
  assert(MaxNumConfigs <= MAX_NUM_CONFIGS);
582
3.21M
  assert(NumProtoSets <= MAX_NUM_PROTO_SETS);
583
584
6.43M
  for (int i = 0; i < NumProtoSets; i++) {
585
    /* allocate space for a proto set, install in class, and initialize */
586
3.21M
    auto ProtoSet = new PROTO_SET_STRUCT;
587
3.21M
    memset(ProtoSet, 0, sizeof(*ProtoSet));
588
3.21M
    ProtoSets[i] = ProtoSet;
589
590
    /* allocate space for the proto lengths and install in class */
591
3.21M
  }
592
3.21M
  memset(ConfigLengths, 0, sizeof(ConfigLengths));
593
3.21M
}
594
595
3.21M
INT_CLASS_STRUCT::~INT_CLASS_STRUCT() {
596
6.43M
  for (int i = 0; i < NumProtoSets; i++) {
597
3.21M
    delete ProtoSets[i];
598
3.21M
  }
599
3.21M
}
600
601
/// This constructor allocates a new set of integer templates
602
/// initialized to hold 0 classes.
603
6.70k
INT_TEMPLATES_STRUCT::INT_TEMPLATES_STRUCT() {
604
6.70k
  NumClasses = 0;
605
6.70k
  NumClassPruners = 0;
606
607
219M
  for (int i = 0; i < MAX_NUM_CLASSES; i++) {
608
219M
    ClassForClassId(this, i) = nullptr;
609
219M
  }
610
6.70k
}
611
612
6.69k
INT_TEMPLATES_STRUCT::~INT_TEMPLATES_STRUCT() {
613
3.22M
  for (unsigned i = 0; i < NumClasses; i++) {
614
3.21M
    delete Class[i];
615
3.21M
  }
616
107k
  for (unsigned i = 0; i < NumClassPruners; i++) {
617
100k
    delete ClassPruners[i];
618
100k
  }
619
6.69k
}
620
621
/**
622
 * This routine reads a set of integer templates from
623
 * File.  File must already be open and must be in the
624
 * correct binary format.
625
 * @param  fp open file to read templates from
626
 * @return Pointer to integer templates read from File.
627
 * @note Globals: none
628
 */
629
4
INT_TEMPLATES_STRUCT *Classify::ReadIntTemplates(TFile *fp) {
630
4
  int j, w, x, y, z;
631
4
  INT_TEMPLATES_STRUCT *Templates;
632
4
  CLASS_PRUNER_STRUCT *Pruner;
633
4
  INT_CLASS_STRUCT *Class;
634
635
  /* variables for conversion from older inttemp formats */
636
4
  int b, bit_number, last_cp_bit_number, new_b, new_i, new_w;
637
4
  CLASS_ID class_id, max_class_id;
638
4
  std::vector<CLASS_ID> ClassIdFor(MAX_NUM_CLASSES);
639
4
  std::vector<CLASS_PRUNER_STRUCT *> TempClassPruner(MAX_NUM_CLASS_PRUNERS);
640
4
  uint32_t SetBitsForMask =          // word with NUM_BITS_PER_CLASS
641
4
      (1 << NUM_BITS_PER_CLASS) - 1; // set starting at bit 0
642
4
  uint32_t Mask, NewMask, ClassBits;
643
4
  unsigned MaxNumConfigs = MAX_NUM_CONFIGS;
644
4
  unsigned WerdsPerConfigVec = WERDS_PER_CONFIG_VEC;
645
646
  /* first read the high level template struct */
647
4
  Templates = new INT_TEMPLATES_STRUCT;
648
  // Read Templates in parts for 64 bit compatibility.
649
4
  uint32_t unicharset_size;
650
4
  if (fp->FReadEndian(&unicharset_size, sizeof(unicharset_size), 1) != 1) {
651
0
    tprintf("Bad read of inttemp!\n");
652
0
  }
653
4
  int32_t version_id = 0;
654
4
  if (fp->FReadEndian(&version_id, sizeof(version_id), 1) != 1 ||
655
4
      fp->FReadEndian(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1) != 1) {
656
0
    tprintf("Bad read of inttemp!\n");
657
0
  }
658
4
  if (version_id < 0) {
659
    // This file has a version id!
660
4
    version_id = -version_id;
661
4
    if (fp->FReadEndian(&Templates->NumClasses, sizeof(Templates->NumClasses), 1) != 1) {
662
0
      tprintf("Bad read of inttemp!\n");
663
0
    }
664
4
  } else {
665
0
    Templates->NumClasses = version_id;
666
0
  }
667
668
4
  if (version_id < 3) {
669
0
    MaxNumConfigs = OLD_MAX_NUM_CONFIGS;
670
0
    WerdsPerConfigVec = OLD_WERDS_PER_CONFIG_VEC;
671
0
  }
672
673
4
  if (version_id < 2) {
674
0
    std::vector<int16_t> IndexFor(MAX_NUM_CLASSES);
675
0
    if (fp->FReadEndian(&IndexFor[0], sizeof(IndexFor[0]), unicharset_size) != unicharset_size) {
676
0
      tprintf("Bad read of inttemp!\n");
677
0
    }
678
0
    if (fp->FReadEndian(&ClassIdFor[0], sizeof(ClassIdFor[0]), Templates->NumClasses) !=
679
0
        Templates->NumClasses) {
680
0
      tprintf("Bad read of inttemp!\n");
681
0
    }
682
0
  }
683
684
  /* then read in the class pruners */
685
4
  const unsigned kNumBuckets = NUM_CP_BUCKETS * NUM_CP_BUCKETS * NUM_CP_BUCKETS * WERDS_PER_CP_VECTOR;
686
20
  for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
687
16
    Pruner = new CLASS_PRUNER_STRUCT;
688
16
    if (fp->FReadEndian(Pruner, sizeof(Pruner->p[0][0][0][0]), kNumBuckets) != kNumBuckets) {
689
0
      tprintf("Bad read of inttemp!\n");
690
0
    }
691
16
    if (version_id < 2) {
692
0
      TempClassPruner[i] = Pruner;
693
16
    } else {
694
16
      Templates->ClassPruners[i] = Pruner;
695
16
    }
696
16
  }
697
698
  /* fix class pruners if they came from an old version of inttemp */
699
4
  if (version_id < 2) {
700
    // Allocate enough class pruners to cover all the class ids.
701
0
    max_class_id = 0;
702
0
    for (unsigned i = 0; i < Templates->NumClasses; i++) {
703
0
      if (ClassIdFor[i] > max_class_id) {
704
0
        max_class_id = ClassIdFor[i];
705
0
      }
706
0
    }
707
0
    for (int i = 0; i <= CPrunerIdFor(max_class_id); i++) {
708
0
      Templates->ClassPruners[i] = new CLASS_PRUNER_STRUCT;
709
0
      memset(Templates->ClassPruners[i], 0, sizeof(CLASS_PRUNER_STRUCT));
710
0
    }
711
    // Convert class pruners from the old format (indexed by class index)
712
    // to the new format (indexed by class id).
713
0
    last_cp_bit_number = NUM_BITS_PER_CLASS * Templates->NumClasses - 1;
714
0
    for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
715
0
      for (x = 0; x < NUM_CP_BUCKETS; x++) {
716
0
        for (y = 0; y < NUM_CP_BUCKETS; y++) {
717
0
          for (z = 0; z < NUM_CP_BUCKETS; z++) {
718
0
            for (w = 0; w < WERDS_PER_CP_VECTOR; w++) {
719
0
              if (TempClassPruner[i]->p[x][y][z][w] == 0) {
720
0
                continue;
721
0
              }
722
0
              for (b = 0; b < BITS_PER_WERD; b += NUM_BITS_PER_CLASS) {
723
0
                bit_number = i * BITS_PER_CP_VECTOR + w * BITS_PER_WERD + b;
724
0
                if (bit_number > last_cp_bit_number) {
725
0
                  break; // the rest of the bits in this word are not used
726
0
                }
727
0
                class_id = ClassIdFor[bit_number / NUM_BITS_PER_CLASS];
728
                // Single out NUM_BITS_PER_CLASS bits relating to class_id.
729
0
                Mask = SetBitsForMask << b;
730
0
                ClassBits = TempClassPruner[i]->p[x][y][z][w] & Mask;
731
                // Move these bits to the new position in which they should
732
                // appear (indexed corresponding to the class_id).
733
0
                new_i = CPrunerIdFor(class_id);
734
0
                new_w = CPrunerWordIndexFor(class_id);
735
0
                new_b = CPrunerBitIndexFor(class_id) * NUM_BITS_PER_CLASS;
736
0
                if (new_b > b) {
737
0
                  ClassBits <<= (new_b - b);
738
0
                } else {
739
0
                  ClassBits >>= (b - new_b);
740
0
                }
741
                // Copy bits relating to class_id to the correct position
742
                // in Templates->ClassPruner.
743
0
                NewMask = SetBitsForMask << new_b;
744
0
                Templates->ClassPruners[new_i]->p[x][y][z][new_w] &= ~NewMask;
745
0
                Templates->ClassPruners[new_i]->p[x][y][z][new_w] |= ClassBits;
746
0
              }
747
0
            }
748
0
          }
749
0
        }
750
0
      }
751
0
    }
752
0
    for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
753
0
      delete TempClassPruner[i];
754
0
    }
755
0
  }
756
757
  /* then read in each class */
758
456
  for (unsigned i = 0; i < Templates->NumClasses; i++) {
759
    /* first read in the high level struct for the class */
760
452
    Class = new INT_CLASS_STRUCT;
761
452
    if (fp->FReadEndian(&Class->NumProtos, sizeof(Class->NumProtos), 1) != 1 ||
762
452
        fp->FRead(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1) != 1 ||
763
452
        fp->FRead(&Class->NumConfigs, sizeof(Class->NumConfigs), 1) != 1) {
764
0
      tprintf("Bad read of inttemp!\n");
765
0
    }
766
452
    if (version_id == 0) {
767
      // Only version 0 writes 5 pointless pointers to the file.
768
0
      for (j = 0; j < 5; ++j) {
769
0
        int32_t junk;
770
0
        if (fp->FRead(&junk, sizeof(junk), 1) != 1) {
771
0
          tprintf("Bad read of inttemp!\n");
772
0
        }
773
0
      }
774
0
    }
775
452
    unsigned num_configs = version_id < 4 ? MaxNumConfigs : Class->NumConfigs;
776
452
    ASSERT_HOST(num_configs <= MaxNumConfigs);
777
452
    if (fp->FReadEndian(Class->ConfigLengths, sizeof(uint16_t), num_configs) != num_configs) {
778
0
      tprintf("Bad read of inttemp!\n");
779
0
    }
780
452
    if (version_id < 2) {
781
0
      ClassForClassId(Templates, ClassIdFor[i]) = Class;
782
452
    } else {
783
452
      ClassForClassId(Templates, i) = Class;
784
452
    }
785
786
    /* then read in the proto lengths */
787
452
    Class->ProtoLengths.clear();
788
452
    if (MaxNumIntProtosIn(Class) > 0) {
789
440
      Class->ProtoLengths.resize(MaxNumIntProtosIn(Class));
790
440
      if (fp->FRead(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class)) !=
791
440
          MaxNumIntProtosIn(Class)) {
792
0
        tprintf("Bad read of inttemp!\n");
793
0
      }
794
440
    }
795
796
    /* then read in the proto sets */
797
1.24k
    for (j = 0; j < Class->NumProtoSets; j++) {
798
796
      auto ProtoSet = new PROTO_SET_STRUCT;
799
796
      unsigned num_buckets = NUM_PP_PARAMS * NUM_PP_BUCKETS * WERDS_PER_PP_VECTOR;
800
796
      if (fp->FReadEndian(&ProtoSet->ProtoPruner, sizeof(ProtoSet->ProtoPruner[0][0][0]),
801
796
                          num_buckets) != num_buckets) {
802
0
        tprintf("Bad read of inttemp!\n");
803
0
      }
804
51.7k
      for (x = 0; x < PROTOS_PER_PROTO_SET; x++) {
805
50.9k
        if (fp->FRead(&ProtoSet->Protos[x].A, sizeof(ProtoSet->Protos[x].A), 1) != 1 ||
806
50.9k
            fp->FRead(&ProtoSet->Protos[x].B, sizeof(ProtoSet->Protos[x].B), 1) != 1 ||
807
50.9k
            fp->FRead(&ProtoSet->Protos[x].C, sizeof(ProtoSet->Protos[x].C), 1) != 1 ||
808
50.9k
            fp->FRead(&ProtoSet->Protos[x].Angle, sizeof(ProtoSet->Protos[x].Angle), 1) != 1) {
809
0
          tprintf("Bad read of inttemp!\n");
810
0
        }
811
50.9k
        if (fp->FReadEndian(&ProtoSet->Protos[x].Configs, sizeof(ProtoSet->Protos[x].Configs[0]),
812
50.9k
                            WerdsPerConfigVec) != WerdsPerConfigVec) {
813
0
          tprintf("Bad read of inttemp!\n");
814
0
        }
815
50.9k
      }
816
796
      Class->ProtoSets[j] = ProtoSet;
817
796
    }
818
452
    if (version_id < 4) {
819
0
      Class->font_set_id = -1;
820
452
    } else {
821
452
      fp->FReadEndian(&Class->font_set_id, sizeof(Class->font_set_id), 1);
822
452
    }
823
452
  }
824
825
4
  if (version_id < 2) {
826
    /* add an empty nullptr class with class id 0 */
827
0
    assert(UnusedClassIdIn(Templates, 0));
828
0
    ClassForClassId(Templates, 0) = new INT_CLASS_STRUCT(1, 1);
829
0
    ClassForClassId(Templates, 0)->font_set_id = -1;
830
0
    Templates->NumClasses++;
831
    /* make sure the classes are contiguous */
832
0
    for (unsigned i = 0; i < MAX_NUM_CLASSES; i++) {
833
0
      if (i < Templates->NumClasses) {
834
0
        if (ClassForClassId(Templates, i) == nullptr) {
835
0
          fprintf(stderr, "Non-contiguous class ids in inttemp\n");
836
0
          exit(1);
837
0
        }
838
0
      } else {
839
0
        if (ClassForClassId(Templates, i) != nullptr) {
840
0
          fprintf(stderr, "Class id %u exceeds NumClassesIn (Templates) %u\n", i,
841
0
                  Templates->NumClasses);
842
0
          exit(1);
843
0
        }
844
0
      }
845
0
    }
846
0
  }
847
4
  if (version_id >= 4) {
848
4
    using namespace std::placeholders; // for _1, _2
849
4
    this->fontinfo_table_.read(fp, std::bind(read_info, _1, _2));
850
4
    if (version_id >= 5) {
851
4
      this->fontinfo_table_.read(fp, std::bind(read_spacing_info, _1, _2));
852
4
    }
853
444
    this->fontset_table_.read(fp, [](auto *f, auto *fs) { return f->DeSerialize(*fs); } );
854
4
  }
855
856
4
  return (Templates);
857
4
} /* ReadIntTemplates */
858
859
#ifndef GRAPHICS_DISABLED
860
/**
861
 * This routine sends the shapes in the global display
862
 * lists to the match debugger window.
863
 *
864
 * Globals:
865
 * - FeatureShapes display list containing feature matches
866
 * - ProtoShapes display list containing proto matches
867
 */
868
void Classify::ShowMatchDisplay() {
869
  InitIntMatchWindowIfReqd();
870
  if (ProtoDisplayWindow) {
871
    ProtoDisplayWindow->Clear();
872
  }
873
  if (FeatureDisplayWindow) {
874
    FeatureDisplayWindow->Clear();
875
  }
876
  ClearFeatureSpaceWindow(static_cast<NORM_METHOD>(static_cast<int>(classify_norm_method)),
877
                          IntMatchWindow);
878
  IntMatchWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
879
  if (ProtoDisplayWindow) {
880
    ProtoDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
881
  }
882
  if (FeatureDisplayWindow) {
883
    FeatureDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
884
  }
885
} /* ShowMatchDisplay */
886
887
/// Clears the given window and draws the featurespace guides for the
888
/// appropriate normalization method.
889
void ClearFeatureSpaceWindow(NORM_METHOD norm_method, ScrollView *window) {
890
  window->Clear();
891
892
  window->Pen(ScrollView::GREY);
893
  // Draw the feature space limit rectangle.
894
  window->Rectangle(0, 0, INT_MAX_X, INT_MAX_Y);
895
  if (norm_method == baseline) {
896
    window->SetCursor(0, INT_DESCENDER);
897
    window->DrawTo(INT_MAX_X, INT_DESCENDER);
898
    window->SetCursor(0, INT_BASELINE);
899
    window->DrawTo(INT_MAX_X, INT_BASELINE);
900
    window->SetCursor(0, INT_XHEIGHT);
901
    window->DrawTo(INT_MAX_X, INT_XHEIGHT);
902
    window->SetCursor(0, INT_CAPHEIGHT);
903
    window->DrawTo(INT_MAX_X, INT_CAPHEIGHT);
904
  } else {
905
    window->Rectangle(INT_XCENTER - INT_XRADIUS, INT_YCENTER - INT_YRADIUS,
906
                      INT_XCENTER + INT_XRADIUS, INT_YCENTER + INT_YRADIUS);
907
  }
908
}
909
#endif
910
911
/**
912
 * This routine writes Templates to File.  The format
913
 * is an efficient binary format.  File must already be open
914
 * for writing.
915
 * @param File open file to write templates to
916
 * @param Templates templates to save into File
917
 * @param target_unicharset the UNICHARSET to use
918
 */
919
void Classify::WriteIntTemplates(FILE *File, INT_TEMPLATES_STRUCT *Templates,
920
0
                                 const UNICHARSET &target_unicharset) {
921
0
  INT_CLASS_STRUCT *Class;
922
0
  uint32_t unicharset_size = target_unicharset.size();
923
0
  int version_id = -5; // When negated by the reader -1 becomes +1 etc.
924
925
0
  if (Templates->NumClasses != unicharset_size) {
926
0
    tprintf(
927
0
        "Warning: executing WriteIntTemplates() with %d classes in"
928
0
        " Templates, while target_unicharset size is %" PRIu32 "\n",
929
0
        Templates->NumClasses, unicharset_size);
930
0
  }
931
932
  /* first write the high level template struct */
933
0
  fwrite(&unicharset_size, sizeof(unicharset_size), 1, File);
934
0
  fwrite(&version_id, sizeof(version_id), 1, File);
935
0
  fwrite(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1, File);
936
0
  fwrite(&Templates->NumClasses, sizeof(Templates->NumClasses), 1, File);
937
938
  /* then write out the class pruners */
939
0
  for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
940
0
    fwrite(Templates->ClassPruners[i], sizeof(CLASS_PRUNER_STRUCT), 1, File);
941
0
  }
942
943
  /* then write out each class */
944
0
  for (unsigned i = 0; i < Templates->NumClasses; i++) {
945
0
    Class = Templates->Class[i];
946
947
    /* first write out the high level struct for the class */
948
0
    fwrite(&Class->NumProtos, sizeof(Class->NumProtos), 1, File);
949
0
    fwrite(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1, File);
950
0
    ASSERT_HOST(Class->NumConfigs == this->fontset_table_.at(Class->font_set_id).size());
951
0
    fwrite(&Class->NumConfigs, sizeof(Class->NumConfigs), 1, File);
952
0
    for (int j = 0; j < Class->NumConfigs; ++j) {
953
0
      fwrite(&Class->ConfigLengths[j], sizeof(uint16_t), 1, File);
954
0
    }
955
956
    /* then write out the proto lengths */
957
0
    if (MaxNumIntProtosIn(Class) > 0) {
958
0
      fwrite(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class), File);
959
0
    }
960
961
    /* then write out the proto sets */
962
0
    for (int j = 0; j < Class->NumProtoSets; j++) {
963
0
      fwrite(Class->ProtoSets[j], sizeof(PROTO_SET_STRUCT), 1, File);
964
0
    }
965
966
    /* then write the fonts info */
967
0
    fwrite(&Class->font_set_id, sizeof(int), 1, File);
968
0
  }
969
970
  /* Write the fonts info tables */
971
0
  using namespace std::placeholders; // for _1, _2
972
0
  this->fontinfo_table_.write(File, std::bind(write_info, _1, _2));
973
0
  this->fontinfo_table_.write(File, std::bind(write_spacing_info, _1, _2));
974
0
  this->fontset_table_.write(File, std::bind(write_set, _1, _2));
975
0
} /* WriteIntTemplates */
976
977
/*-----------------------------------------------------------------------------
978
              Private Code
979
-----------------------------------------------------------------------------*/
980
/**
981
 * This routine returns the parameter value which
982
 * corresponds to the beginning of the specified bucket.
983
 * The bucket number should have been generated using the
984
 * BucketFor() function with parameters Offset and NumBuckets.
985
 * @param Bucket    bucket whose start is to be computed
986
 * @param Offset    offset used to map params to buckets
987
 * @param NumBuckets  total number of buckets
988
 * @return Param value corresponding to start position of Bucket.
989
 * @note Globals: none
990
 */
991
1.45k
float BucketStart(int Bucket, float Offset, int NumBuckets) {
992
1.45k
  return static_cast<float>(Bucket) / NumBuckets - Offset;
993
994
1.45k
} /* BucketStart */
995
996
/**
997
 * This routine returns the parameter value which
998
 * corresponds to the end of the specified bucket.
999
 * The bucket number should have been generated using the
1000
 * BucketFor() function with parameters Offset and NumBuckets.
1001
 * @param Bucket    bucket whose end is to be computed
1002
 * @param Offset    offset used to map params to buckets
1003
 * @param NumBuckets  total number of buckets
1004
 * @return Param value corresponding to end position of Bucket.
1005
 * @note Globals: none
1006
 */
1007
726
float BucketEnd(int Bucket, float Offset, int NumBuckets) {
1008
726
  return static_cast<float>(Bucket + 1) / NumBuckets - Offset;
1009
726
} /* BucketEnd */
1010
1011
/**
1012
 * This routine fills in the section of a class pruner
1013
 * corresponding to a single x value for a single proto of
1014
 * a class.
1015
 * @param FillSpec  specifies which bits to fill in pruner
1016
 * @param Pruner    class pruner to be filled
1017
 * @param ClassMask indicates which bits to change in each word
1018
 * @param ClassCount  indicates what to change bits to
1019
 * @param WordIndex indicates which word to change
1020
 */
1021
void DoFill(FILL_SPEC *FillSpec, CLASS_PRUNER_STRUCT *Pruner, uint32_t ClassMask,
1022
9.26k
            uint32_t ClassCount, uint32_t WordIndex) {
1023
9.26k
  int X, Y, Angle;
1024
9.26k
  uint32_t OldWord;
1025
1026
9.26k
  X = FillSpec->X;
1027
9.26k
  if (X < 0) {
1028
0
    X = 0;
1029
0
  }
1030
9.26k
  if (X >= NUM_CP_BUCKETS) {
1031
0
    X = NUM_CP_BUCKETS - 1;
1032
0
  }
1033
1034
9.26k
  if (FillSpec->YStart < 0) {
1035
0
    FillSpec->YStart = 0;
1036
0
  }
1037
9.26k
  if (FillSpec->YEnd >= NUM_CP_BUCKETS) {
1038
215
    FillSpec->YEnd = NUM_CP_BUCKETS - 1;
1039
215
  }
1040
1041
61.4k
  for (Y = FillSpec->YStart; Y <= FillSpec->YEnd; Y++) {
1042
272k
    for (Angle = FillSpec->AngleStart;; CircularIncrement(Angle, NUM_CP_BUCKETS)) {
1043
272k
      OldWord = Pruner->p[X][Y][Angle][WordIndex];
1044
272k
      if (ClassCount > (OldWord & ClassMask)) {
1045
145k
        OldWord &= ~ClassMask;
1046
145k
        OldWord |= ClassCount;
1047
145k
        Pruner->p[X][Y][Angle][WordIndex] = OldWord;
1048
145k
      }
1049
272k
      if (Angle == FillSpec->AngleEnd) {
1050
52.2k
        break;
1051
52.2k
      }
1052
272k
    }
1053
52.2k
  }
1054
9.26k
} /* DoFill */
1055
1056
/**
1057
 * Return true if the specified table filler is done, i.e.
1058
 * if it has no more lines to fill.
1059
 * @param Filler    table filler to check if done
1060
 * @return true if no more lines to fill, false otherwise.
1061
 * @note Globals: none
1062
 */
1063
11.2k
bool FillerDone(TABLE_FILLER *Filler) {
1064
11.2k
  FILL_SWITCH *Next;
1065
1066
11.2k
  Next = &(Filler->Switch[Filler->NextSwitch]);
1067
1068
11.2k
  return Filler->X > Next->X && Next->Type == LastSwitch;
1069
1070
11.2k
} /* FillerDone */
1071
1072
/**
1073
 * This routine sets Bit in each bit vector whose
1074
 * bucket lies within the range Center +- Spread.  The fill
1075
 * is done for a circular dimension, i.e. bucket 0 is adjacent
1076
 * to the last bucket.  It is assumed that Center and Spread
1077
 * are expressed in a circular coordinate system whose range
1078
 * is 0 to 1.
1079
 * @param ParamTable  table of bit vectors, one per param bucket
1080
 * @param Bit bit position in vectors to be filled
1081
 * @param Center center of filled area
1082
 * @param Spread spread of filled area
1083
 * @param debug debug flag
1084
 */
1085
void FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
1086
11.9k
                        float Center, float Spread, bool debug) {
1087
11.9k
  int i, FirstBucket, LastBucket;
1088
1089
11.9k
  if (Spread > 0.5) {
1090
0
    Spread = 0.5;
1091
0
  }
1092
1093
11.9k
  FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
1094
11.9k
  if (FirstBucket < 0) {
1095
1.38k
    FirstBucket += NUM_PP_BUCKETS;
1096
1.38k
  }
1097
1098
11.9k
  LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
1099
11.9k
  if (LastBucket >= NUM_PP_BUCKETS) {
1100
277
    LastBucket -= NUM_PP_BUCKETS;
1101
277
  }
1102
11.9k
  if (debug) {
1103
0
    tprintf("Circular fill from %d to %d", FirstBucket, LastBucket);
1104
0
  }
1105
202k
  for (i = FirstBucket; true; CircularIncrement(i, NUM_PP_BUCKETS)) {
1106
202k
    SET_BIT(ParamTable[i], Bit);
1107
1108
    /* exit loop after we have set the bit for the last bucket */
1109
202k
    if (i == LastBucket) {
1110
11.9k
      break;
1111
11.9k
    }
1112
202k
  }
1113
1114
11.9k
} /* FillPPCircularBits */
1115
1116
/**
1117
 * This routine sets Bit in each bit vector whose
1118
 * bucket lies within the range Center +- Spread.  The fill
1119
 * is done for a linear dimension, i.e. there is no wrap-around
1120
 * for this dimension.  It is assumed that Center and Spread
1121
 * are expressed in a linear coordinate system whose range
1122
 * is approximately 0 to 1.  Values outside this range will
1123
 * be clipped.
1124
 * @param ParamTable table of bit vectors, one per param bucket
1125
 * @param Bit bit number being filled
1126
 * @param Center center of filled area
1127
 * @param Spread spread of filled area
1128
 * @param debug debug flag
1129
 */
1130
void FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
1131
23.8k
                      float Center, float Spread, bool debug) {
1132
23.8k
  int i, FirstBucket, LastBucket;
1133
1134
23.8k
  FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
1135
23.8k
  if (FirstBucket < 0) {
1136
863
    FirstBucket = 0;
1137
863
  }
1138
1139
23.8k
  LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
1140
23.8k
  if (LastBucket >= NUM_PP_BUCKETS) {
1141
3.86k
    LastBucket = NUM_PP_BUCKETS - 1;
1142
3.86k
  }
1143
1144
23.8k
  if (debug) {
1145
0
    tprintf("Linear fill from %d to %d", FirstBucket, LastBucket);
1146
0
  }
1147
342k
  for (i = FirstBucket; i <= LastBucket; i++) {
1148
318k
    SET_BIT(ParamTable[i], Bit);
1149
318k
  }
1150
1151
23.8k
} /* FillPPLinearBits */
1152
1153
/*---------------------------------------------------------------------------*/
1154
#ifndef GRAPHICS_DISABLED
1155
/**
1156
 * This routine prompts the user with Prompt and waits
1157
 * for the user to enter something in the debug window.
1158
 * @param Prompt prompt to print while waiting for input from window
1159
 * @param adaptive_on
1160
 * @param pretrained_on
1161
 * @param shape_id
1162
 * @return Character entered in the debug window.
1163
 * @note Globals: none
1164
 */
1165
CLASS_ID Classify::GetClassToDebug(const char *Prompt, bool *adaptive_on, bool *pretrained_on,
1166
                                   int *shape_id) {
1167
  tprintf("%s\n", Prompt);
1168
  SVEventType ev_type;
1169
  int unichar_id = INVALID_UNICHAR_ID;
1170
  // Wait until a click or popup event.
1171
  do {
1172
    auto ev = IntMatchWindow->AwaitEvent(SVET_ANY);
1173
    ev_type = ev->type;
1174
    if (ev_type == SVET_POPUP) {
1175
      if (ev->command_id == IDA_SHAPE_INDEX) {
1176
        if (shape_table_ != nullptr) {
1177
          *shape_id = atoi(ev->parameter);
1178
          *adaptive_on = false;
1179
          *pretrained_on = true;
1180
          if (*shape_id >= 0 && static_cast<unsigned>(*shape_id) < shape_table_->NumShapes()) {
1181
            int font_id;
1182
            shape_table_->GetFirstUnicharAndFont(*shape_id, &unichar_id, &font_id);
1183
            tprintf("Shape %d, first unichar=%d, font=%d\n", *shape_id, unichar_id, font_id);
1184
            return unichar_id;
1185
          }
1186
          tprintf("Shape index '%s' not found in shape table\n", ev->parameter);
1187
        } else {
1188
          tprintf("No shape table loaded!\n");
1189
        }
1190
      } else {
1191
        if (unicharset.contains_unichar(ev->parameter)) {
1192
          unichar_id = unicharset.unichar_to_id(ev->parameter);
1193
          if (ev->command_id == IDA_ADAPTIVE) {
1194
            *adaptive_on = true;
1195
            *pretrained_on = false;
1196
            *shape_id = -1;
1197
          } else if (ev->command_id == IDA_STATIC) {
1198
            *adaptive_on = false;
1199
            *pretrained_on = true;
1200
          } else {
1201
            *adaptive_on = true;
1202
            *pretrained_on = true;
1203
          }
1204
          if (ev->command_id == IDA_ADAPTIVE || shape_table_ == nullptr) {
1205
            *shape_id = -1;
1206
            return unichar_id;
1207
          }
1208
          for (unsigned s = 0; s < shape_table_->NumShapes(); ++s) {
1209
            if (shape_table_->GetShape(s).ContainsUnichar(unichar_id)) {
1210
              tprintf("%s\n", shape_table_->DebugStr(s).c_str());
1211
            }
1212
          }
1213
        } else {
1214
          tprintf("Char class '%s' not found in unicharset", ev->parameter);
1215
        }
1216
      }
1217
    }
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
 */
1235
1.97k
void GetCPPadsForLevel(int Level, float *EndPad, float *SidePad, float *AnglePad) {
1236
1.97k
  switch (Level) {
1237
659
    case 0:
1238
659
      *EndPad = classify_cp_end_pad_loose * GetPicoFeatureLength();
1239
659
      *SidePad = classify_cp_side_pad_loose * GetPicoFeatureLength();
1240
659
      *AnglePad = classify_cp_angle_pad_loose / 360.0;
1241
659
      break;
1242
1243
659
    case 1:
1244
659
      *EndPad = classify_cp_end_pad_medium * GetPicoFeatureLength();
1245
659
      *SidePad = classify_cp_side_pad_medium * GetPicoFeatureLength();
1246
659
      *AnglePad = classify_cp_angle_pad_medium / 360.0;
1247
659
      break;
1248
1249
659
    case 2:
1250
659
      *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength();
1251
659
      *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength();
1252
659
      *AnglePad = classify_cp_angle_pad_tight / 360.0;
1253
659
      break;
1254
1255
0
    default:
1256
0
      *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength();
1257
0
      *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength();
1258
0
      *AnglePad = classify_cp_angle_pad_tight / 360.0;
1259
0
      break;
1260
1.97k
  }
1261
1.97k
  if (*AnglePad > 0.5) {
1262
0
    *AnglePad = 0.5;
1263
0
  }
1264
1265
1.97k
} /* 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
 */
1272
0
ScrollView::Color GetMatchColorFor(float Evidence) {
1273
0
  assert(Evidence >= 0.0);
1274
0
  assert(Evidence <= 1.0);
1275
1276
0
  if (Evidence >= 0.90) {
1277
0
    return ScrollView::WHITE;
1278
0
  } else if (Evidence >= 0.75) {
1279
0
    return ScrollView::GREEN;
1280
0
  } else if (Evidence >= 0.50) {
1281
0
    return ScrollView::RED;
1282
0
  } else {
1283
0
    return ScrollView::BLUE;
1284
0
  }
1285
0
} /* 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
 */
1295
9.26k
void GetNextFill(TABLE_FILLER *Filler, FILL_SPEC *Fill) {
1296
9.26k
  FILL_SWITCH *Next;
1297
1298
  /* compute the fill assuming no switches will be encountered */
1299
9.26k
  Fill->AngleStart = Filler->AngleStart;
1300
9.26k
  Fill->AngleEnd = Filler->AngleEnd;
1301
9.26k
  Fill->X = Filler->X;
1302
9.26k
  Fill->YStart = Filler->YStart >> 8;
1303
9.26k
  Fill->YEnd = Filler->YEnd >> 8;
1304
1305
  /* update the fill info and the filler for ALL switches at this X value */
1306
9.26k
  Next = &(Filler->Switch[Filler->NextSwitch]);
1307
10.7k
  while (Filler->X >= Next->X) {
1308
3.42k
    Fill->X = Filler->X = Next->X;
1309
3.42k
    if (Next->Type == StartSwitch) {
1310
726
      Fill->YStart = Next->Y;
1311
726
      Filler->StartDelta = Next->Delta;
1312
726
      Filler->YStart = Next->YInit;
1313
2.70k
    } else if (Next->Type == EndSwitch) {
1314
726
      Fill->YEnd = Next->Y;
1315
726
      Filler->EndDelta = Next->Delta;
1316
726
      Filler->YEnd = Next->YInit;
1317
1.97k
    } else { /* Type must be LastSwitch */
1318
1.97k
      break;
1319
1.97k
    }
1320
1.45k
    Filler->NextSwitch++;
1321
1.45k
    Next = &(Filler->Switch[Filler->NextSwitch]);
1322
1.45k
  }
1323
1324
  /* prepare the filler for the next call to this routine */
1325
9.26k
  Filler->X++;
1326
9.26k
  Filler->YStart += Filler->StartDelta;
1327
9.26k
  Filler->YEnd += Filler->EndDelta;
1328
1329
9.26k
} /* 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
 */
1340
void InitTableFiller(float EndPad, float SidePad, float AnglePad, PROTO_STRUCT *Proto, TABLE_FILLER *Filler)
1341
7.58k
#define XS X_SHIFT
1342
6.85k
#define YS Y_SHIFT
1343
3.95k
#define AS ANGLE_SHIFT
1344
18.3k
#define NB NUM_CP_BUCKETS
1345
1.97k
{
1346
1.97k
  float Angle;
1347
1.97k
  float X, Y, HalfLength;
1348
1.97k
  float Cos, Sin;
1349
1.97k
  float XAdjust, YAdjust;
1350
1.97k
  FPOINT Start, Switch1, Switch2, End;
1351
1.97k
  int S1 = 0;
1352
1.97k
  int S2 = 1;
1353
1354
1.97k
  Angle = Proto->Angle;
1355
1.97k
  X = Proto->X;
1356
1.97k
  Y = Proto->Y;
1357
1.97k
  HalfLength = Proto->Length / 2.0;
1358
1359
1.97k
  Filler->AngleStart = CircBucketFor(Angle - AnglePad, AS, NB);
1360
1.97k
  Filler->AngleEnd = CircBucketFor(Angle + AnglePad, AS, NB);
1361
1.97k
  Filler->NextSwitch = 0;
1362
1363
1.97k
  if (fabs(Angle - 0.0) < HV_TOLERANCE || fabs(Angle - 0.5) < HV_TOLERANCE) {
1364
    /* horizontal proto - handle as special case */
1365
489
    Filler->X = Bucket8For(X - HalfLength - EndPad, XS, NB);
1366
489
    Filler->YStart = Bucket16For(Y - SidePad, YS, NB * 256);
1367
489
    Filler->YEnd = Bucket16For(Y + SidePad, YS, NB * 256);
1368
489
    Filler->StartDelta = 0;
1369
489
    Filler->EndDelta = 0;
1370
489
    Filler->Switch[0].Type = LastSwitch;
1371
489
    Filler->Switch[0].X = Bucket8For(X + HalfLength + EndPad, XS, NB);
1372
1.48k
  } else if (fabs(Angle - 0.25) < HV_TOLERANCE || fabs(Angle - 0.75) < HV_TOLERANCE) {
1373
    /* vertical proto - handle as special case */
1374
762
    Filler->X = Bucket8For(X - SidePad, XS, NB);
1375
762
    Filler->YStart = Bucket16For(Y - HalfLength - EndPad, YS, NB * 256);
1376
762
    Filler->YEnd = Bucket16For(Y + HalfLength + EndPad, YS, NB * 256);
1377
762
    Filler->StartDelta = 0;
1378
762
    Filler->EndDelta = 0;
1379
762
    Filler->Switch[0].Type = LastSwitch;
1380
762
    Filler->Switch[0].X = Bucket8For(X + SidePad, XS, NB);
1381
762
  } else {
1382
    /* diagonal proto */
1383
1384
726
    if ((Angle > 0.0 && Angle < 0.25) || (Angle > 0.5 && Angle < 0.75)) {
1385
      /* rising diagonal proto */
1386
351
      Angle *= 2.0 * M_PI;
1387
351
      Cos = fabs(std::cos(Angle));
1388
351
      Sin = fabs(std::sin(Angle));
1389
1390
      /* compute the positions of the corners of the acceptance region */
1391
351
      Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin;
1392
351
      Start.y = Y - (HalfLength + EndPad) * Sin + SidePad * Cos;
1393
351
      End.x = 2.0 * X - Start.x;
1394
351
      End.y = 2.0 * Y - Start.y;
1395
351
      Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin;
1396
351
      Switch1.y = Y - (HalfLength + EndPad) * Sin - SidePad * Cos;
1397
351
      Switch2.x = 2.0 * X - Switch1.x;
1398
351
      Switch2.y = 2.0 * Y - Switch1.y;
1399
1400
351
      if (Switch1.x > Switch2.x) {
1401
232
        S1 = 1;
1402
232
        S2 = 0;
1403
232
      }
1404
1405
      /* translate into bucket positions and deltas */
1406
351
      Filler->X = Bucket8For(Start.x, XS, NB);
1407
351
      Filler->StartDelta = -static_cast<int16_t>((Cos / Sin) * 256);
1408
351
      Filler->EndDelta = static_cast<int16_t>((Sin / Cos) * 256);
1409
1410
351
      XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
1411
351
      YAdjust = XAdjust * Cos / Sin;
1412
351
      Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
1413
351
      YAdjust = XAdjust * Sin / Cos;
1414
351
      Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
1415
1416
351
      Filler->Switch[S1].Type = StartSwitch;
1417
351
      Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
1418
351
      Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
1419
351
      XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
1420
351
      YAdjust = XAdjust * Sin / Cos;
1421
351
      Filler->Switch[S1].YInit = Bucket16For(Switch1.y - YAdjust, YS, NB * 256);
1422
351
      Filler->Switch[S1].Delta = Filler->EndDelta;
1423
1424
351
      Filler->Switch[S2].Type = EndSwitch;
1425
351
      Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
1426
351
      Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
1427
351
      XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
1428
351
      YAdjust = XAdjust * Cos / Sin;
1429
351
      Filler->Switch[S2].YInit = Bucket16For(Switch2.y + YAdjust, YS, NB * 256);
1430
351
      Filler->Switch[S2].Delta = Filler->StartDelta;
1431
1432
351
      Filler->Switch[2].Type = LastSwitch;
1433
351
      Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
1434
375
    } else {
1435
      /* falling diagonal proto */
1436
375
      Angle *= 2.0 * M_PI;
1437
375
      Cos = fabs(std::cos(Angle));
1438
375
      Sin = fabs(std::sin(Angle));
1439
1440
      /* compute the positions of the corners of the acceptance region */
1441
375
      Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin;
1442
375
      Start.y = Y + (HalfLength + EndPad) * Sin - SidePad * Cos;
1443
375
      End.x = 2.0 * X - Start.x;
1444
375
      End.y = 2.0 * Y - Start.y;
1445
375
      Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin;
1446
375
      Switch1.y = Y + (HalfLength + EndPad) * Sin + SidePad * Cos;
1447
375
      Switch2.x = 2.0 * X - Switch1.x;
1448
375
      Switch2.y = 2.0 * Y - Switch1.y;
1449
1450
375
      if (Switch1.x > Switch2.x) {
1451
262
        S1 = 1;
1452
262
        S2 = 0;
1453
262
      }
1454
1455
      /* translate into bucket positions and deltas */
1456
375
      Filler->X = Bucket8For(Start.x, XS, NB);
1457
375
      Filler->StartDelta = static_cast<int16_t>(
1458
375
          ClipToRange<int>(-IntCastRounded((Sin / Cos) * 256), INT16_MIN, INT16_MAX));
1459
375
      Filler->EndDelta = static_cast<int16_t>(
1460
375
          ClipToRange<int>(IntCastRounded((Cos / Sin) * 256), INT16_MIN, INT16_MAX));
1461
1462
375
      XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
1463
375
      YAdjust = XAdjust * Sin / Cos;
1464
375
      Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
1465
375
      YAdjust = XAdjust * Cos / Sin;
1466
375
      Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
1467
1468
375
      Filler->Switch[S1].Type = EndSwitch;
1469
375
      Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
1470
375
      Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
1471
375
      XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
1472
375
      YAdjust = XAdjust * Sin / Cos;
1473
375
      Filler->Switch[S1].YInit = Bucket16For(Switch1.y + YAdjust, YS, NB * 256);
1474
375
      Filler->Switch[S1].Delta = Filler->StartDelta;
1475
1476
375
      Filler->Switch[S2].Type = StartSwitch;
1477
375
      Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
1478
375
      Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
1479
375
      XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
1480
375
      YAdjust = XAdjust * Cos / Sin;
1481
375
      Filler->Switch[S2].YInit = Bucket16For(Switch2.y - YAdjust, YS, NB * 256);
1482
375
      Filler->Switch[S2].Delta = Filler->EndDelta;
1483
1484
375
      Filler->Switch[2].Type = LastSwitch;
1485
375
      Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
1486
375
    }
1487
726
  }
1488
1.97k
} /* 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
 */
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
 */
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
 */
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
 */
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
 */
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.
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