1 /**********************************************************************
2  * File:        normalis.cpp  (Formerly denorm.c)
3  * Description: Code for the DENORM class.
4  * Author:      Ray Smith
5  *
6  * (C) Copyright 1992, Hewlett-Packard Ltd.
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 
19 #include "normalis.h"
20 
21 #include <allheaders.h>
22 #include "blobs.h"
23 #include "helpers.h"
24 #include "matrix.h"
25 #include "ocrblock.h"
26 #include "unicharset.h"
27 #include "werd.h"
28 
29 #include <cfloat> // for FLT_MAX
30 #include <cstdlib>
31 
32 namespace tesseract {
33 
34 // Tolerance in pixels used for baseline and xheight on non-upper/lower scripts.
35 const int kSloppyTolerance = 4;
36 // Final tolerance in pixels added to the computed xheight range.
37 const float kFinalPixelTolerance = 0.125f;
38 
DENORM()39 DENORM::DENORM() {
40   Init();
41 }
42 
DENORM(const DENORM & src)43 DENORM::DENORM(const DENORM &src) {
44   rotation_ = nullptr;
45   *this = src;
46 }
47 
operator =(const DENORM & src)48 DENORM &DENORM::operator=(const DENORM &src) {
49   Clear();
50   inverse_ = src.inverse_;
51   predecessor_ = src.predecessor_;
52   pix_ = src.pix_;
53   block_ = src.block_;
54   if (src.rotation_ == nullptr) {
55     rotation_ = nullptr;
56   } else {
57     rotation_ = new FCOORD(*src.rotation_);
58   }
59   x_origin_ = src.x_origin_;
60   y_origin_ = src.y_origin_;
61   x_scale_ = src.x_scale_;
62   y_scale_ = src.y_scale_;
63   final_xshift_ = src.final_xshift_;
64   final_yshift_ = src.final_yshift_;
65   return *this;
66 }
67 
~DENORM()68 DENORM::~DENORM() {
69   Clear();
70 }
71 
72 // Initializes the denorm for a transformation. For details see the large
73 // comment in normalis.h.
74 // Arguments:
75 // block: if not nullptr, then this is the first transformation, and
76 //        block->re_rotation() needs to be used after the Denorm
77 //        transformation to get back to the image coords.
78 // rotation: if not nullptr, apply this rotation after translation to the
79 //           origin and scaling. (Usually a classify rotation.)
80 // predecessor: if not nullptr, then predecessor has been applied to the
81 //              input space and needs to be undone to complete the inverse.
82 // The above pointers are not owned by this DENORM and are assumed to live
83 // longer than this denorm, except rotation, which is deep copied on input.
84 //
85 // x_origin: The x origin which will be mapped to final_xshift in the result.
86 // y_origin: The y origin which will be mapped to final_yshift in the result.
87 //           Added to result of row->baseline(x) if not nullptr.
88 //
89 // x_scale: scale factor for the x-coordinate.
90 // y_scale: scale factor for the y-coordinate. Ignored if segs is given.
91 // Note that these scale factors apply to the same x and y system as the
92 // x-origin and y-origin apply, ie after any block rotation, but before
93 // the rotation argument is applied.
94 //
95 // final_xshift: The x component of the final translation.
96 // final_yshift: The y component of the final translation.
SetupNormalization(const BLOCK * block,const FCOORD * rotation,const DENORM * predecessor,float x_origin,float y_origin,float x_scale,float y_scale,float final_xshift,float final_yshift)97 void DENORM::SetupNormalization(const BLOCK *block, const FCOORD *rotation,
98                                 const DENORM *predecessor, float x_origin, float y_origin,
99                                 float x_scale, float y_scale, float final_xshift,
100                                 float final_yshift) {
101   Clear();
102   block_ = block;
103   if (rotation == nullptr) {
104     rotation_ = nullptr;
105   } else {
106     rotation_ = new FCOORD(*rotation);
107   }
108   predecessor_ = predecessor;
109   x_origin_ = x_origin;
110   y_origin_ = y_origin;
111   x_scale_ = x_scale;
112   y_scale_ = y_scale;
113   final_xshift_ = final_xshift;
114   final_yshift_ = final_yshift;
115 }
116 
117 // Helper for SetupNonLinear computes an image of shortest run-lengths from
118 // the x/y edges provided.
119 // Based on "A nonlinear normalization method for handprinted Kanji character
120 // recognition -- line density equalization" by Hiromitsu Yamada et al.
121 // Eg below is an O in a 1-pixel margin-ed bounding box and the corresponding
122 //  ______________     input x_coords and y_coords.
123 // |  _________  |     <empty>
124 // | |    _    | |     1, 6
125 // | |   | |   | |     1, 3, 4, 6
126 // | |   | |   | |     1, 3, 4, 6
127 // | |   | |   | |     1, 3, 4, 6
128 // | |   |_|   | |     1, 3, 4, 6
129 // | |_________| |     1, 6
130 // |_____________|     <empty>
131 //  E 1 1 1 1 1 E
132 //  m 7 7 2 7 7 m
133 //  p     6     p
134 //  t     7     t
135 //  y           y
136 // The output image contains the min of the x and y run-length (distance
137 // between edges) at each coordinate in the image thus:
138 //  ______________
139 // |7 1_1_1_1_1 7|
140 // |1|5 5 1 5 5|1|
141 // |1|2 2|1|2 2|1|
142 // |1|2 2|1|2 2|1|
143 // |1|2 2|1|2 2|1|
144 // |1|2 2|1|2 2|1|
145 // |1|5_5_1_5_5|1|
146 // |7_1_1_1_1_1_7|
147 // Note that the input coords are all integer, so all partial pixels are dealt
148 // with elsewhere. Although it is nice for outlines to be properly connected
149 // and continuous, there is no requirement that they be as such, so they could
150 // have been derived from a flaky source, such as greyscale.
151 // This function works only within the provided box, and it is assumed that the
152 // input x_coords and y_coords have already been translated to have the bottom-
153 // left of box as the origin. Although an output, the minruns should have been
154 // pre-initialized to be the same size as box. Each element will contain the
155 // minimum of x and y run-length as shown above.
ComputeRunlengthImage(const TBOX & box,const std::vector<std::vector<int>> & x_coords,const std::vector<std::vector<int>> & y_coords,GENERIC_2D_ARRAY<int> * minruns)156 static void ComputeRunlengthImage(const TBOX &box,
157                                   const std::vector<std::vector<int>> &x_coords,
158                                   const std::vector<std::vector<int>> &y_coords,
159                                   GENERIC_2D_ARRAY<int> *minruns) {
160   int width = box.width();
161   int height = box.height();
162   ASSERT_HOST(minruns->dim1() == width);
163   ASSERT_HOST(minruns->dim2() == height);
164   // Set a 2-d image array to the run lengths at each pixel.
165   for (int ix = 0; ix < width; ++ix) {
166     int y = 0;
167     for (auto y_coord : y_coords[ix]) {
168       int y_edge = ClipToRange(y_coord, 0, height);
169       int gap = y_edge - y;
170       // Every pixel between the last and current edge get set to the gap.
171       while (y < y_edge) {
172         (*minruns)(ix, y) = gap;
173         ++y;
174       }
175     }
176     // Pretend there is a bounding box of edges all around the image.
177     int gap = height - y;
178     while (y < height) {
179       (*minruns)(ix, y) = gap;
180       ++y;
181     }
182   }
183   // Now set the image pixels the the MIN of the x and y runlengths.
184   for (int iy = 0; iy < height; ++iy) {
185     int x = 0;
186     for (auto x_coord : x_coords[iy]) {
187       int x_edge = ClipToRange(x_coord, 0, width);
188       int gap = x_edge - x;
189       while (x < x_edge) {
190         if (gap < (*minruns)(x, iy)) {
191           (*minruns)(x, iy) = gap;
192         }
193         ++x;
194       }
195     }
196     int gap = width - x;
197     while (x < width) {
198       if (gap < (*minruns)(x, iy)) {
199         (*minruns)(x, iy) = gap;
200       }
201       ++x;
202     }
203   }
204 }
205 // Converts the run-length image (see above to the edge density profiles used
206 // for scaling, thus:
207 //  ______________
208 // |7 1_1_1_1_1 7|  = 5.28
209 // |1|5 5 1 5 5|1|  = 3.8
210 // |1|2 2|1|2 2|1|  = 5
211 // |1|2 2|1|2 2|1|  = 5
212 // |1|2 2|1|2 2|1|  = 5
213 // |1|2 2|1|2 2|1|  = 5
214 // |1|5_5_1_5_5|1|  = 3.8
215 // |7_1_1_1_1_1_7|  = 5.28
216 //  6 4 4 8 4 4 6
217 //  . . . . . . .
218 //  2 4 4 0 4 4 2
219 //  8           8
220 // Each profile is the sum of the reciprocals of the pixels in the image in
221 // the appropriate row or column, and these are then normalized to sum to 1.
222 // On output hx, hy contain an extra element, which will eventually be used
223 // to guarantee that the top/right edge of the box (and anything beyond) always
224 // gets mapped to the maximum target coordinate.
ComputeEdgeDensityProfiles(const TBOX & box,const GENERIC_2D_ARRAY<int> & minruns,std::vector<float> & hx,std::vector<float> & hy)225 static void ComputeEdgeDensityProfiles(const TBOX &box, const GENERIC_2D_ARRAY<int> &minruns,
226                                        std::vector<float> &hx, std::vector<float> &hy) {
227   int width = box.width();
228   int height = box.height();
229   hx.clear();
230   hx.resize(width + 1);
231   hy.clear();
232   hy.resize(height + 1);
233   double total = 0.0;
234   for (int iy = 0; iy < height; ++iy) {
235     for (int ix = 0; ix < width; ++ix) {
236       int run = minruns(ix, iy);
237       if (run == 0) {
238         run = 1;
239       }
240       float density = 1.0f / run;
241       hx[ix] += density;
242       hy[iy] += density;
243     }
244     total += hy[iy];
245   }
246   // Normalize each profile to sum to 1.
247   if (total > 0.0) {
248     for (int ix = 0; ix < width; ++ix) {
249       hx[ix] /= total;
250     }
251     for (int iy = 0; iy < height; ++iy) {
252       hy[iy] /= total;
253     }
254   }
255   // There is an extra element in each array, so initialize to 1.
256   hx[width] = 1.0f;
257   hy[height] = 1.0f;
258 }
259 
260 // Sets up the DENORM to execute a non-linear transformation based on
261 // preserving an even distribution of stroke edges. The transformation
262 // operates only within the given box.
263 // x_coords is a collection of the x-coords of vertical edges for each
264 // y-coord starting at box.bottom().
265 // y_coords is a collection of the y-coords of horizontal edges for each
266 // x-coord starting at box.left().
267 // Eg x_coords[0] is a collection of the x-coords of edges at y=bottom.
268 // Eg x_coords[1] is a collection of the x-coords of edges at y=bottom + 1.
269 // The second-level vectors must all be sorted in ascending order.
270 // See comments on the helper functions above for more details.
SetupNonLinear(const DENORM * predecessor,const TBOX & box,float target_width,float target_height,float final_xshift,float final_yshift,const std::vector<std::vector<int>> & x_coords,const std::vector<std::vector<int>> & y_coords)271 void DENORM::SetupNonLinear(const DENORM *predecessor, const TBOX &box, float target_width,
272                             float target_height, float final_xshift, float final_yshift,
273                             const std::vector<std::vector<int>> &x_coords,
274                             const std::vector<std::vector<int>> &y_coords) {
275   Clear();
276   predecessor_ = predecessor;
277   // x_map_ and y_map_ store a mapping from input x and y coordinate to output
278   // x and y coordinate, based on scaling to the supplied target_width and
279   // target_height.
280   x_map_ = new std::vector<float>;
281   y_map_ = new std::vector<float>;
282   // Set a 2-d image array to the run lengths at each pixel.
283   int width = box.width();
284   int height = box.height();
285   GENERIC_2D_ARRAY<int> minruns(width, height, 0);
286   ComputeRunlengthImage(box, x_coords, y_coords, &minruns);
287   // Edge density is the sum of the inverses of the run lengths. Compute
288   // edge density projection profiles.
289   ComputeEdgeDensityProfiles(box, minruns, *x_map_, *y_map_);
290   // Convert the edge density profiles to the coordinates by multiplying by
291   // the desired size and accumulating.
292   (*x_map_)[width] = target_width;
293   for (int x = width - 1; x >= 0; --x) {
294     (*x_map_)[x] = (*x_map_)[x + 1] - (*x_map_)[x] * target_width;
295   }
296   (*y_map_)[height] = target_height;
297   for (int y = height - 1; y >= 0; --y) {
298     (*y_map_)[y] = (*y_map_)[y + 1] - (*y_map_)[y] * target_height;
299   }
300   x_origin_ = box.left();
301   y_origin_ = box.bottom();
302   final_xshift_ = final_xshift;
303   final_yshift_ = final_yshift;
304 }
305 
306 // Transforms the given coords one step forward to normalized space, without
307 // using any block rotation or predecessor.
LocalNormTransform(const TPOINT & pt,TPOINT * transformed) const308 void DENORM::LocalNormTransform(const TPOINT &pt, TPOINT *transformed) const {
309   FCOORD src_pt(pt.x, pt.y);
310   FCOORD float_result;
311   LocalNormTransform(src_pt, &float_result);
312   transformed->x = IntCastRounded(float_result.x());
313   transformed->y = IntCastRounded(float_result.y());
314 }
LocalNormTransform(const FCOORD & pt,FCOORD * transformed) const315 void DENORM::LocalNormTransform(const FCOORD &pt, FCOORD *transformed) const {
316   FCOORD translated(pt.x() - x_origin_, pt.y() - y_origin_);
317   if (x_map_ != nullptr && y_map_ != nullptr) {
318     int x = ClipToRange(IntCastRounded(translated.x()), 0, static_cast<int>(x_map_->size() - 1));
319     translated.set_x((*x_map_)[x]);
320     int y = ClipToRange(IntCastRounded(translated.y()), 0, static_cast<int>(y_map_->size() - 1));
321     translated.set_y((*y_map_)[y]);
322   } else {
323     translated.set_x(translated.x() * x_scale_);
324     translated.set_y(translated.y() * y_scale_);
325     if (rotation_ != nullptr) {
326       translated.rotate(*rotation_);
327     }
328   }
329   transformed->set_x(translated.x() + final_xshift_);
330   transformed->set_y(translated.y() + final_yshift_);
331 }
332 
333 // Transforms the given coords forward to normalized space using the
334 // full transformation sequence defined by the block rotation, the
335 // predecessors, deepest first, and finally this. If first_norm is not nullptr,
336 // then the first and deepest transformation used is first_norm, ending
337 // with this, and the block rotation will not be applied.
NormTransform(const DENORM * first_norm,const TPOINT & pt,TPOINT * transformed) const338 void DENORM::NormTransform(const DENORM *first_norm, const TPOINT &pt, TPOINT *transformed) const {
339   FCOORD src_pt(pt.x, pt.y);
340   FCOORD float_result;
341   NormTransform(first_norm, src_pt, &float_result);
342   transformed->x = IntCastRounded(float_result.x());
343   transformed->y = IntCastRounded(float_result.y());
344 }
NormTransform(const DENORM * first_norm,const FCOORD & pt,FCOORD * transformed) const345 void DENORM::NormTransform(const DENORM *first_norm, const FCOORD &pt, FCOORD *transformed) const {
346   FCOORD src_pt(pt);
347   if (first_norm != this) {
348     if (predecessor_ != nullptr) {
349       predecessor_->NormTransform(first_norm, pt, &src_pt);
350     } else if (block_ != nullptr) {
351       FCOORD fwd_rotation(block_->re_rotation().x(), -block_->re_rotation().y());
352       src_pt.rotate(fwd_rotation);
353     }
354   }
355   LocalNormTransform(src_pt, transformed);
356 }
357 
358 // Transforms the given coords one step back to source space, without
359 // using to any block rotation or predecessor.
LocalDenormTransform(const TPOINT & pt,TPOINT * original) const360 void DENORM::LocalDenormTransform(const TPOINT &pt, TPOINT *original) const {
361   FCOORD src_pt(pt.x, pt.y);
362   FCOORD float_result;
363   LocalDenormTransform(src_pt, &float_result);
364   original->x = IntCastRounded(float_result.x());
365   original->y = IntCastRounded(float_result.y());
366 }
367 
LocalDenormTransform(const FCOORD & pt,FCOORD * original) const368 void DENORM::LocalDenormTransform(const FCOORD &pt, FCOORD *original) const {
369   FCOORD rotated(pt.x() - final_xshift_, pt.y() - final_yshift_);
370   if (x_map_ != nullptr && y_map_ != nullptr) {
371     auto pos = std::upper_bound(x_map_->begin(), x_map_->end(), rotated.x());
372     if (pos > x_map_->begin()) {
373       --pos;
374     }
375     auto x = pos - x_map_->begin();
376     original->set_x(x + x_origin_);
377     pos = std::upper_bound(y_map_->begin(), y_map_->end(), rotated.y());
378     if (pos > y_map_->begin()) {
379       --pos;
380     }
381     auto y = pos - y_map_->begin();
382     original->set_y(y + y_origin_);
383   } else {
384     if (rotation_ != nullptr) {
385       FCOORD inverse_rotation(rotation_->x(), -rotation_->y());
386       rotated.rotate(inverse_rotation);
387     }
388     original->set_x(rotated.x() / x_scale_ + x_origin_);
389     float y_scale = y_scale_;
390     original->set_y(rotated.y() / y_scale + y_origin_);
391   }
392 }
393 
394 // Transforms the given coords all the way back to source image space using
395 // the full transformation sequence defined by this and its predecessors
396 // recursively, shallowest first, and finally any block re_rotation.
397 // If last_denorm is not nullptr, then the last transformation used will
398 // be last_denorm, and the block re_rotation will never be executed.
DenormTransform(const DENORM * last_denorm,const TPOINT & pt,TPOINT * original) const399 void DENORM::DenormTransform(const DENORM *last_denorm, const TPOINT &pt, TPOINT *original) const {
400   FCOORD src_pt(pt.x, pt.y);
401   FCOORD float_result;
402   DenormTransform(last_denorm, src_pt, &float_result);
403   original->x = IntCastRounded(float_result.x());
404   original->y = IntCastRounded(float_result.y());
405 }
DenormTransform(const DENORM * last_denorm,const FCOORD & pt,FCOORD * original) const406 void DENORM::DenormTransform(const DENORM *last_denorm, const FCOORD &pt, FCOORD *original) const {
407   LocalDenormTransform(pt, original);
408   if (last_denorm != this) {
409     if (predecessor_ != nullptr) {
410       predecessor_->DenormTransform(last_denorm, *original, original);
411     } else if (block_ != nullptr) {
412       original->rotate(block_->re_rotation());
413     }
414   }
415 }
416 
417 // Normalize a blob using blob transformations. Less accurate, but
418 // more accurately copies the old way.
LocalNormBlob(TBLOB * blob) const419 void DENORM::LocalNormBlob(TBLOB *blob) const {
420   ICOORD translation(-IntCastRounded(x_origin_), -IntCastRounded(y_origin_));
421   blob->Move(translation);
422   if (y_scale_ != 1.0f) {
423     blob->Scale(y_scale_);
424   }
425   if (rotation_ != nullptr) {
426     blob->Rotate(*rotation_);
427   }
428   translation.set_x(IntCastRounded(final_xshift_));
429   translation.set_y(IntCastRounded(final_yshift_));
430   blob->Move(translation);
431 }
432 
433 // Fills in the x-height range accepted by the given unichar_id, given its
434 // bounding box in the usual baseline-normalized coordinates, with some
435 // initial crude x-height estimate (such as word size) and this denoting the
436 // transformation that was used.
XHeightRange(int unichar_id,const UNICHARSET & unicharset,const TBOX & bbox,float * min_xht,float * max_xht,float * yshift) const437 void DENORM::XHeightRange(int unichar_id, const UNICHARSET &unicharset, const TBOX &bbox,
438                           float *min_xht, float *max_xht, float *yshift) const {
439   // Default return -- accept anything.
440   *yshift = 0.0f;
441   *min_xht = 0.0f;
442   *max_xht = FLT_MAX;
443 
444   if (!unicharset.top_bottom_useful()) {
445     return;
446   }
447 
448   // Clip the top and bottom to the limit of normalized feature space.
449   int top = ClipToRange<int>(bbox.top(), 0, kBlnCellHeight - 1);
450   int bottom = ClipToRange<int>(bbox.bottom(), 0, kBlnCellHeight - 1);
451   // A tolerance of yscale corresponds to 1 pixel in the image.
452   double tolerance = y_scale();
453   // If the script doesn't have upper and lower-case characters, widen the
454   // tolerance to allow sloppy baseline/x-height estimates.
455   if (!unicharset.script_has_upper_lower()) {
456     tolerance = y_scale() * kSloppyTolerance;
457   }
458 
459   int min_bottom, max_bottom, min_top, max_top;
460   unicharset.get_top_bottom(unichar_id, &min_bottom, &max_bottom, &min_top, &max_top);
461 
462   // Calculate the scale factor we'll use to get to image y-pixels
463   double midx = (bbox.left() + bbox.right()) / 2.0;
464   double ydiff = (bbox.top() - bbox.bottom()) + 2.0;
465   FCOORD mid_bot(midx, bbox.bottom()), tmid_bot;
466   FCOORD mid_high(midx, bbox.bottom() + ydiff), tmid_high;
467   DenormTransform(nullptr, mid_bot, &tmid_bot);
468   DenormTransform(nullptr, mid_high, &tmid_high);
469 
470   // bln_y_measure * yscale = image_y_measure
471   double yscale = tmid_high.pt_to_pt_dist(tmid_bot) / ydiff;
472 
473   // Calculate y-shift
474   int bln_yshift = 0, bottom_shift = 0, top_shift = 0;
475   if (bottom < min_bottom - tolerance) {
476     bottom_shift = bottom - min_bottom;
477   } else if (bottom > max_bottom + tolerance) {
478     bottom_shift = bottom - max_bottom;
479   }
480   if (top < min_top - tolerance) {
481     top_shift = top - min_top;
482   } else if (top > max_top + tolerance) {
483     top_shift = top - max_top;
484   }
485   if ((top_shift >= 0 && bottom_shift > 0) || (top_shift < 0 && bottom_shift < 0)) {
486     bln_yshift = (top_shift + bottom_shift) / 2;
487   }
488   *yshift = bln_yshift * yscale;
489 
490   // To help very high cap/xheight ratio fonts accept the correct x-height,
491   // and to allow the large caps in small caps to accept the xheight of the
492   // small caps, add kBlnBaselineOffset to chars with a maximum max, and have
493   // a top already at a significantly high position.
494   if (max_top == kBlnCellHeight - 1 && top > kBlnCellHeight - kBlnBaselineOffset / 2) {
495     max_top += kBlnBaselineOffset;
496   }
497   top -= bln_yshift;
498   int height = top - kBlnBaselineOffset;
499   double min_height = min_top - kBlnBaselineOffset - tolerance;
500   double max_height = max_top - kBlnBaselineOffset + tolerance;
501 
502   // We shouldn't try calculations if the characters are very short (for example
503   // for punctuation).
504   if (min_height > kBlnXHeight / 8 && height > 0) {
505     float result = height * kBlnXHeight * yscale / min_height;
506     *max_xht = result + kFinalPixelTolerance;
507     result = height * kBlnXHeight * yscale / max_height;
508     *min_xht = result - kFinalPixelTolerance;
509   }
510 }
511 
512 // Prints the content of the DENORM for debug purposes.
Print() const513 void DENORM::Print() const {
514   if (pix_ != nullptr) {
515     tprintf("Pix dimensions %d x %d x %d\n", pixGetWidth(pix_), pixGetHeight(pix_),
516             pixGetDepth(pix_));
517   }
518   if (inverse_) {
519     tprintf("Inverse\n");
520   }
521   if (block_ && block_->re_rotation().x() != 1.0f) {
522     tprintf("Block rotation %g, %g\n", block_->re_rotation().x(), block_->re_rotation().y());
523   }
524   tprintf("Input Origin = (%g, %g)\n", x_origin_, y_origin_);
525   if (x_map_ != nullptr && y_map_ != nullptr) {
526     tprintf("x map:\n");
527     for (auto x : *x_map_) {
528       tprintf("%g ", x);
529     }
530     tprintf("\ny map:\n");
531     for (auto y : *y_map_) {
532       tprintf("%g ", y);
533     }
534     tprintf("\n");
535   } else {
536     tprintf("Scale = (%g, %g)\n", x_scale_, y_scale_);
537     if (rotation_ != nullptr) {
538       tprintf("Rotation = (%g, %g)\n", rotation_->x(), rotation_->y());
539     }
540   }
541   tprintf("Final Origin = (%g, %g)\n", final_xshift_, final_xshift_);
542   if (predecessor_ != nullptr) {
543     tprintf("Predecessor:\n");
544     predecessor_->Print();
545   }
546 }
547 
548 // ============== Private Code ======================
549 
550 // Free allocated memory and clear pointers.
Clear()551 void DENORM::Clear() {
552   delete x_map_;
553   x_map_ = nullptr;
554   delete y_map_;
555   y_map_ = nullptr;
556   delete rotation_;
557   rotation_ = nullptr;
558 }
559 
560 // Setup default values.
Init()561 void DENORM::Init() {
562   inverse_ = false;
563   pix_ = nullptr;
564   block_ = nullptr;
565   rotation_ = nullptr;
566   predecessor_ = nullptr;
567   x_map_ = nullptr;
568   y_map_ = nullptr;
569   x_origin_ = 0.0f;
570   y_origin_ = 0.0f;
571   x_scale_ = 1.0f;
572   y_scale_ = 1.0f;
573   final_xshift_ = 0.0f;
574   final_yshift_ = static_cast<float>(kBlnBaselineOffset);
575 }
576 
577 } // namespace tesseract
578