Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/tesseract/src/ccstruct/coutln.cpp @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/thirdparty/tesseract/src/ccstruct/coutln.cpp Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1062 @@ +/********************************************************************** + * File: coutln.cpp (Formerly coutline.c) + * Description: Code for the C_OUTLINE class. + * Author: Ray Smith + * + * (C) Copyright 1991, Hewlett-Packard Ltd. + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** http://www.apache.org/licenses/LICENSE-2.0 + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + * + **********************************************************************/ + +// Include automatically generated configuration file if running autoconf. +#ifdef HAVE_CONFIG_H +# include "config_auto.h" +#endif + +#include "coutln.h" + +#include "arrayaccess.h" // for GET_DATA_BYTE +#include "blobs.h" // for TPOINT +#include "crakedge.h" // for CRACKEDGE +#include "environ.h" // for l_uint32 +#include "errcode.h" // for ASSERT_HOST +#include "normalis.h" // for DENORM + +#include "helpers.h" // for ClipToRange, IntCastRounded, Modulo + +#include <allheaders.h> // for pixSetPixel, pixGetData, pixRasterop, pixGe... +#include "pix.h" // for Pix (ptr only), PIX_DST, PIX_NOT + +#include <algorithm> // for max, min +#include <cmath> // for abs +#include <cstdlib> // for abs +#include <cstring> // for memset, memcpy, memmove + +namespace tesseract { + +ICOORD C_OUTLINE::step_coords[4] = {ICOORD(-1, 0), ICOORD(0, -1), ICOORD(1, 0), ICOORD(0, 1)}; + +/** + * @name C_OUTLINE::C_OUTLINE + * + * Constructor to build a C_OUTLINE from a CRACKEDGE LOOP. + * @param startpt outline to convert + * @param bot_left bounding box + * @param top_right bounding box + * @param length length of loop + */ + +C_OUTLINE::C_OUTLINE(CRACKEDGE *startpt, ICOORD bot_left, ICOORD top_right, int16_t length) + : box(bot_left, top_right), start(startpt->pos), offsets(nullptr) { + int16_t stepindex; // index to step + CRACKEDGE *edgept; // current point + + stepcount = length; // no of steps + if (length == 0) { + return; + } + // get memory + steps.resize(step_mem()); + edgept = startpt; + + for (stepindex = 0; stepindex < length; stepindex++) { + // set compact step + set_step(stepindex, edgept->stepdir); + edgept = edgept->next; + } +} + +/** + * @name C_OUTLINE::C_OUTLINE + * + * Constructor to build a C_OUTLINE from a C_OUTLINE_FRAG. + */ +C_OUTLINE::C_OUTLINE( + // constructor + // steps to copy + ICOORD startpt, DIR128 *new_steps, + int16_t length // length of loop + ) + : start(startpt), offsets(nullptr) { + int8_t dirdiff; // direction difference + DIR128 prevdir; // previous direction + DIR128 dir; // current direction + DIR128 lastdir; // dir of last step + TBOX new_box; // easy bounding + int16_t stepindex; // index to step + int16_t srcindex; // source steps + ICOORD pos; // current position + + pos = startpt; + stepcount = length; // No. of steps. + ASSERT_HOST(length >= 0); + steps.resize(step_mem()); // Get memory. + + lastdir = new_steps[length - 1]; + prevdir = lastdir; + for (stepindex = 0, srcindex = 0; srcindex < length; stepindex++, srcindex++) { + new_box = TBOX(pos, pos); + box += new_box; + // copy steps + dir = new_steps[srcindex]; + set_step(stepindex, dir); + dirdiff = dir - prevdir; + pos += step(stepindex); + if ((dirdiff == 64 || dirdiff == -64) && stepindex > 0) { + stepindex -= 2; // cancel there-and-back + prevdir = stepindex >= 0 ? step_dir(stepindex) : lastdir; + } else { + prevdir = dir; + } + } + ASSERT_HOST(pos.x() == startpt.x() && pos.y() == startpt.y()); + do { + dirdiff = step_dir(stepindex - 1) - step_dir(0); + if (dirdiff == 64 || dirdiff == -64) { + start += step(0); + stepindex -= 2; // cancel there-and-back + for (int i = 0; i < stepindex; ++i) { + set_step(i, step_dir(i + 1)); + } + } + } while (stepindex > 1 && (dirdiff == 64 || dirdiff == -64)); + stepcount = stepindex; + ASSERT_HOST(stepcount >= 4); +} + +/** + * @name C_OUTLINE::C_OUTLINE + * + * Constructor to build a C_OUTLINE from a rotation of a C_OUTLINE. + * @param srcline outline to rotate + * @param rotation rotate to coord + */ + +C_OUTLINE::C_OUTLINE(C_OUTLINE *srcline, FCOORD rotation) : offsets(nullptr) { + TBOX new_box; // easy bounding + int16_t stepindex; // index to step + int16_t dirdiff; // direction change + ICOORD pos; // current position + ICOORD prevpos; // previous dest point + + ICOORD destpos; // destination point + int16_t destindex = INT16_MAX; // index to step + DIR128 dir; // coded direction + uint8_t new_step; + + stepcount = srcline->stepcount * 2; + if (stepcount == 0) { + box = srcline->box; + box.rotate(rotation); + return; + } + // get memory + steps.resize(step_mem()); + + for (int iteration = 0; iteration < 2; ++iteration) { + DIR128 round1 = iteration == 0 ? 32 : 0; + DIR128 round2 = iteration != 0 ? 32 : 0; + pos = srcline->start; + prevpos = pos; + prevpos.rotate(rotation); + start = prevpos; + box = TBOX(start, start); + destindex = 0; + for (stepindex = 0; stepindex < srcline->stepcount; stepindex++) { + pos += srcline->step(stepindex); + destpos = pos; + destpos.rotate(rotation); + // tprintf("%i %i %i %i ", destpos.x(), destpos.y(), pos.x(), pos.y()); + while (destpos.x() != prevpos.x() || destpos.y() != prevpos.y()) { + dir = DIR128(FCOORD(destpos - prevpos)); + dir += 64; // turn to step style + new_step = dir.get_dir(); + // tprintf(" %i\n", new_step); + if (new_step & 31) { + set_step(destindex++, dir + round1); + prevpos += step(destindex - 1); + if (destindex < 2 || + ((dirdiff = step_dir(destindex - 1) - step_dir(destindex - 2)) != -64 && + dirdiff != 64)) { + set_step(destindex++, dir + round2); + prevpos += step(destindex - 1); + } else { + prevpos -= step(destindex - 1); + destindex--; + prevpos -= step(destindex - 1); + set_step(destindex - 1, dir + round2); + prevpos += step(destindex - 1); + } + } else { + set_step(destindex++, dir); + prevpos += step(destindex - 1); + } + while (destindex >= 2 && + ((dirdiff = step_dir(destindex - 1) - step_dir(destindex - 2)) == -64 || + dirdiff == 64)) { + prevpos -= step(destindex - 1); + prevpos -= step(destindex - 2); + destindex -= 2; // Forget u turn + } + // ASSERT_HOST(prevpos.x() == destpos.x() && prevpos.y() == + // destpos.y()); + new_box = TBOX(destpos, destpos); + box += new_box; + } + } + ASSERT_HOST(destpos.x() == start.x() && destpos.y() == start.y()); + while (destindex > 1) { + dirdiff = step_dir(destindex - 1) - step_dir(0); + if (dirdiff != 64 && dirdiff != -64) { + break; + } + start += step(0); + destindex -= 2; + for (int i = 0; i < destindex; ++i) { + set_step(i, step_dir(i + 1)); + } + } + if (destindex >= 4) { + break; + } + } + ASSERT_HOST(destindex <= stepcount); + stepcount = destindex; + destpos = start; + for (stepindex = 0; stepindex < stepcount; stepindex++) { + destpos += step(stepindex); + } + ASSERT_HOST(destpos.x() == start.x() && destpos.y() == start.y()); +} + +// Build a fake outline, given just a bounding box and append to the list. +void C_OUTLINE::FakeOutline(const TBOX &box, C_OUTLINE_LIST *outlines) { + C_OUTLINE_IT ol_it(outlines); + // Make a C_OUTLINE from the bounds. This is a bit of a hack, + // as there is no outline, just a bounding box, but it works nicely. + CRACKEDGE start; + start.pos = box.topleft(); + auto *outline = new C_OUTLINE(&start, box.topleft(), box.botright(), 0); + ol_it.add_to_end(outline); +} + +/** + * @name C_OUTLINE::area + * + * Compute the area of the outline. + */ + +int32_t C_OUTLINE::area() const { + int stepindex; // current step + int32_t total_steps; // steps to do + int32_t total; // total area + ICOORD pos; // position of point + ICOORD next_step; // step to next pix + // We aren't going to modify the list, or its contents, but there is + // no const iterator. + C_OUTLINE_IT it(const_cast<C_OUTLINE_LIST *>(&children)); + + pos = start_pos(); + total_steps = pathlength(); + total = 0; + for (stepindex = 0; stepindex < total_steps; stepindex++) { + // all intersected + next_step = step(stepindex); + if (next_step.x() < 0) { + total += pos.y(); + } else if (next_step.x() > 0) { + total -= pos.y(); + } + pos += next_step; + } + for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { + total += it.data()->area(); // add areas of children + } + + return total; +} + +/** + * @name C_OUTLINE::perimeter + * + * Compute the perimeter of the outline and its first level children. + */ + +int32_t C_OUTLINE::perimeter() const { + int32_t total_steps; // Return value. + // We aren't going to modify the list, or its contents, but there is + // no const iterator. + C_OUTLINE_IT it(const_cast<C_OUTLINE_LIST *>(&children)); + + total_steps = pathlength(); + for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { + total_steps += it.data()->pathlength(); // Add perimeters of children. + } + + return total_steps; +} + +/** + * @name C_OUTLINE::outer_area + * + * Compute the area of the outline. + */ + +int32_t C_OUTLINE::outer_area() const { + int stepindex; // current step + int32_t total_steps; // steps to do + int32_t total; // total area + ICOORD pos; // position of point + ICOORD next_step; // step to next pix + + pos = start_pos(); + total_steps = pathlength(); + if (total_steps == 0) { + return box.area(); + } + total = 0; + for (stepindex = 0; stepindex < total_steps; stepindex++) { + // all intersected + next_step = step(stepindex); + if (next_step.x() < 0) { + total += pos.y(); + } else if (next_step.x() > 0) { + total -= pos.y(); + } + pos += next_step; + } + + return total; +} + +/** + * @name C_OUTLINE::count_transitions + * + * Compute the number of x and y maxes and mins in the outline. + * @param threshold winding number on size + */ + +int32_t C_OUTLINE::count_transitions(int32_t threshold) { + bool first_was_max_x; // what was first + bool first_was_max_y; + bool looking_for_max_x; // what is next + bool looking_for_min_x; + bool looking_for_max_y; // what is next + bool looking_for_min_y; + int stepindex; // current step + int32_t total_steps; // steps to do + // current limits + int32_t max_x, min_x, max_y, min_y; + int32_t initial_x, initial_y; // initial limits + int32_t total; // total changes + ICOORD pos; // position of point + ICOORD next_step; // step to next pix + + pos = start_pos(); + total_steps = pathlength(); + total = 0; + max_x = min_x = pos.x(); + max_y = min_y = pos.y(); + looking_for_max_x = true; + looking_for_min_x = true; + looking_for_max_y = true; + looking_for_min_y = true; + first_was_max_x = false; + first_was_max_y = false; + initial_x = pos.x(); + initial_y = pos.y(); // stop uninit warning + for (stepindex = 0; stepindex < total_steps; stepindex++) { + // all intersected + next_step = step(stepindex); + pos += next_step; + if (next_step.x() < 0) { + if (looking_for_max_x && pos.x() < min_x) { + min_x = pos.x(); + } + if (looking_for_min_x && max_x - pos.x() > threshold) { + if (looking_for_max_x) { + initial_x = max_x; + first_was_max_x = false; + } + total++; + looking_for_max_x = true; + looking_for_min_x = false; + min_x = pos.x(); // reset min + } + } else if (next_step.x() > 0) { + if (looking_for_min_x && pos.x() > max_x) { + max_x = pos.x(); + } + if (looking_for_max_x && pos.x() - min_x > threshold) { + if (looking_for_min_x) { + initial_x = min_x; // remember first min + first_was_max_x = true; + } + total++; + looking_for_max_x = false; + looking_for_min_x = true; + max_x = pos.x(); + } + } else if (next_step.y() < 0) { + if (looking_for_max_y && pos.y() < min_y) { + min_y = pos.y(); + } + if (looking_for_min_y && max_y - pos.y() > threshold) { + if (looking_for_max_y) { + initial_y = max_y; // remember first max + first_was_max_y = false; + } + total++; + looking_for_max_y = true; + looking_for_min_y = false; + min_y = pos.y(); // reset min + } + } else { + if (looking_for_min_y && pos.y() > max_y) { + max_y = pos.y(); + } + if (looking_for_max_y && pos.y() - min_y > threshold) { + if (looking_for_min_y) { + initial_y = min_y; // remember first min + first_was_max_y = true; + } + total++; + looking_for_max_y = false; + looking_for_min_y = true; + max_y = pos.y(); + } + } + } + if (first_was_max_x && looking_for_min_x) { + if (max_x - initial_x > threshold) { + total++; + } else { + total--; + } + } else if (!first_was_max_x && looking_for_max_x) { + if (initial_x - min_x > threshold) { + total++; + } else { + total--; + } + } + if (first_was_max_y && looking_for_min_y) { + if (max_y - initial_y > threshold) { + total++; + } else { + total--; + } + } else if (!first_was_max_y && looking_for_max_y) { + if (initial_y - min_y > threshold) { + total++; + } else { + total--; + } + } + + return total; +} + +/** + * @name C_OUTLINE::operator< + * + * @return true if the left operand is inside the right one. + * @param other other outline + */ + +bool C_OUTLINE::operator<(const C_OUTLINE &other) const { + int16_t count = 0; // winding count + ICOORD pos; // position of point + int32_t stepindex; // index to cstep + + if (!box.overlap(other.box)) { + return false; // can't be contained + } + if (stepcount == 0) { + return other.box.contains(this->box); + } + + pos = start; + for (stepindex = 0; stepindex < stepcount && (count = other.winding_number(pos)) == INTERSECTING; + stepindex++) { + pos += step(stepindex); // try all points + } + if (count == INTERSECTING) { + // all intersected + pos = other.start; + for (stepindex = 0; + stepindex < other.stepcount && (count = winding_number(pos)) == INTERSECTING; + stepindex++) { + // try other way round + pos += other.step(stepindex); + } + return count == INTERSECTING || count == 0; + } + return count != 0; +} + +/** + * @name C_OUTLINE::winding_number + * + * @return the winding number of the outline around the given point. + * @param point point to wind around + */ + +int16_t C_OUTLINE::winding_number(ICOORD point) const { + int16_t stepindex; // index to cstep + int16_t count; // winding count + ICOORD vec; // to current point + ICOORD stepvec; // step vector + int32_t cross; // cross product + + vec = start - point; // vector to it + count = 0; + for (stepindex = 0; stepindex < stepcount; stepindex++) { + stepvec = step(stepindex); // get the step + // crossing the line + if (vec.y() <= 0 && vec.y() + stepvec.y() > 0) { + cross = vec * stepvec; // cross product + if (cross > 0) { + count++; // crossing right half + } else if (cross == 0) { + return INTERSECTING; // going through point + } + } else if (vec.y() > 0 && vec.y() + stepvec.y() <= 0) { + cross = vec * stepvec; + if (cross < 0) { + count--; // crossing back + } else if (cross == 0) { + return INTERSECTING; // illegal + } + } + vec += stepvec; // sum vectors + } + return count; // winding number +} + +/** + * C_OUTLINE::turn_direction + * + * @return the sum direction delta of the outline. + */ + +int16_t C_OUTLINE::turn_direction() const { // winding number + DIR128 prevdir; // previous direction + DIR128 dir; // current direction + int16_t stepindex; // index to cstep + int8_t dirdiff; // direction difference + int16_t count; // winding count + + if (stepcount == 0) { + return 128; + } + count = 0; + prevdir = step_dir(stepcount - 1); + for (stepindex = 0; stepindex < stepcount; stepindex++) { + dir = step_dir(stepindex); + dirdiff = dir - prevdir; + ASSERT_HOST(dirdiff == 0 || dirdiff == 32 || dirdiff == -32); + count += dirdiff; + prevdir = dir; + } + ASSERT_HOST(count == 128 || count == -128); + return count; // winding number +} + +/** + * @name C_OUTLINE::reverse + * + * Reverse the direction of an outline. + */ + +void C_OUTLINE::reverse() { // reverse drection + DIR128 halfturn = MODULUS / 2; // amount to shift + DIR128 stepdir; // direction of step + int16_t stepindex; // index to cstep + int16_t farindex; // index to other side + int16_t halfsteps; // half of stepcount + + halfsteps = (stepcount + 1) / 2; + for (stepindex = 0; stepindex < halfsteps; stepindex++) { + farindex = stepcount - stepindex - 1; + stepdir = step_dir(stepindex); + set_step(stepindex, step_dir(farindex) + halfturn); + set_step(farindex, stepdir + halfturn); + } +} + +/** + * @name C_OUTLINE::move + * + * Move C_OUTLINE by vector + * @param vec vector to reposition OUTLINE by + */ + +void C_OUTLINE::move(const ICOORD vec) { + C_OUTLINE_IT it(&children); // iterator + + box.move(vec); + start += vec; + + for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { + it.data()->move(vec); // move child outlines + } +} + +/** + * Returns true if *this and its children are legally nested. + * The outer area of a child should have the opposite sign to the + * parent. If not, it means we have discarded an outline in between + * (probably due to excessive length). + */ +bool C_OUTLINE::IsLegallyNested() const { + if (stepcount == 0) { + return true; + } + int64_t parent_area = outer_area(); + // We aren't going to modify the list, or its contents, but there is + // no const iterator. + C_OUTLINE_IT child_it(const_cast<C_OUTLINE_LIST *>(&children)); + for (child_it.mark_cycle_pt(); !child_it.cycled_list(); child_it.forward()) { + const C_OUTLINE *child = child_it.data(); + if (child->outer_area() * parent_area > 0 || !child->IsLegallyNested()) { + return false; + } + } + return true; +} + +/** + * If this outline is smaller than the given min_size, delete this and + * remove from its list, via *it, after checking that *it points to this. + * Otherwise, if any children of this are too small, delete them. + * On entry, *it must be an iterator pointing to this. If this gets deleted + * then this is extracted from *it, so an iteration can continue. + * @param min_size minimum size for outline + * @param it outline iterator + */ +void C_OUTLINE::RemoveSmallRecursive(int min_size, C_OUTLINE_IT *it) { + if (box.width() < min_size || box.height() < min_size) { + ASSERT_HOST(this == it->data()); + delete it->extract(); // Too small so get rid of it and any children. + } else if (!children.empty()) { + // Search the children of this, deleting any that are too small. + C_OUTLINE_IT child_it(&children); + for (child_it.mark_cycle_pt(); !child_it.cycled_list(); child_it.forward()) { + C_OUTLINE *child = child_it.data(); + child->RemoveSmallRecursive(min_size, &child_it); + } + } +} + +// Factored out helpers below are used only by ComputeEdgeOffsets to operate +// on data from an 8-bit Pix, and assume that any input x and/or y are already +// constrained to be legal Pix coordinates. + +/** + * Helper computes the local 2-D gradient (dx, dy) from the 2x2 cell centered + * on the given (x,y). If the cell would go outside the image, it is padded + * with white. + */ +static void ComputeGradient(const l_uint32 *data, int wpl, int x, int y, int width, int height, + ICOORD *gradient) { + const l_uint32 *line = data + y * wpl; + int pix_x_y = x < width && y < height ? GET_DATA_BYTE(line, x) : 255; + int pix_x_prevy = x < width && y > 0 ? GET_DATA_BYTE(line - wpl, x) : 255; + int pix_prevx_prevy = x > 0 && y > 0 ? GET_DATA_BYTE(line - wpl, x - 1) : 255; + int pix_prevx_y = x > 0 && y < height ? GET_DATA_BYTE(line, x - 1) : 255; + gradient->set_x(pix_x_y + pix_x_prevy - (pix_prevx_y + pix_prevx_prevy)); + gradient->set_y(pix_x_prevy + pix_prevx_prevy - (pix_x_y + pix_prevx_y)); +} + +/** + * Helper evaluates a vertical difference, (x,y) - (x,y-1), returning true if + * the difference, matches diff_sign and updating the best_diff, best_sum, + * best_y if a new max. + */ +static bool EvaluateVerticalDiff(const l_uint32 *data, int wpl, int diff_sign, int x, int y, + int height, int *best_diff, int *best_sum, int *best_y) { + if (y <= 0 || y >= height) { + return false; + } + const l_uint32 *line = data + y * wpl; + int pixel1 = GET_DATA_BYTE(line - wpl, x); + int pixel2 = GET_DATA_BYTE(line, x); + int diff = (pixel2 - pixel1) * diff_sign; + if (diff > *best_diff) { + *best_diff = diff; + *best_sum = pixel1 + pixel2; + *best_y = y; + } + return diff > 0; +} + +/** + * Helper evaluates a horizontal difference, (x,y) - (x-1,y), where y is implied + * by the input image line, returning true if the difference matches diff_sign + * and updating the best_diff, best_sum, best_x if a new max. + */ +static bool EvaluateHorizontalDiff(const l_uint32 *line, int diff_sign, int x, int width, + int *best_diff, int *best_sum, int *best_x) { + if (x <= 0 || x >= width) { + return false; + } + int pixel1 = GET_DATA_BYTE(line, x - 1); + int pixel2 = GET_DATA_BYTE(line, x); + int diff = (pixel2 - pixel1) * diff_sign; + if (diff > *best_diff) { + *best_diff = diff; + *best_sum = pixel1 + pixel2; + *best_x = x; + } + return diff > 0; +} + +/** + * Adds sub-pixel resolution EdgeOffsets for the outline if the supplied + * pix is 8-bit. Does nothing otherwise. + * Operation: Consider the following near-horizontal line: + * @verbatim + * _________ + * |________ + * |________ + * @endverbatim + * At *every* position along this line, the gradient direction will be close + * to vertical. Extrapoaltion/interpolation of the position of the threshold + * that was used to binarize the image gives a more precise vertical position + * for each horizontal step, and the conflict in step direction and gradient + * direction can be used to ignore the vertical steps. + */ +void C_OUTLINE::ComputeEdgeOffsets(int threshold, Image pix) { + if (pixGetDepth(pix) != 8) { + return; + } + const l_uint32 *data = pixGetData(pix); + int wpl = pixGetWpl(pix); + int width = pixGetWidth(pix); + int height = pixGetHeight(pix); + bool negative = flag(COUT_INVERSE); + delete[] offsets; + offsets = new EdgeOffset[stepcount]; + ICOORD pos = start; + ICOORD prev_gradient; + ComputeGradient(data, wpl, pos.x(), height - pos.y(), width, height, &prev_gradient); + for (int s = 0; s < stepcount; ++s) { + ICOORD step_vec = step(s); + TPOINT pt1(pos); + pos += step_vec; + TPOINT pt2(pos); + ICOORD next_gradient; + ComputeGradient(data, wpl, pos.x(), height - pos.y(), width, height, &next_gradient); + // Use the sum of the prev and next as the working gradient. + ICOORD gradient = prev_gradient + next_gradient; + // best_diff will be manipulated to be always positive. + int best_diff = 0; + // offset will be the extrapolation of the location of the greyscale + // threshold from the edge with the largest difference, relative to the + // location of the binary edge. + int offset = 0; + if (pt1.y == pt2.y && abs(gradient.y()) * 2 >= abs(gradient.x())) { + // Horizontal step. diff_sign == 1 indicates black above. + int diff_sign = (pt1.x > pt2.x) == negative ? 1 : -1; + int x = std::min(pt1.x, pt2.x); + int y = height - pt1.y; + int best_sum = 0; + int best_y = y; + EvaluateVerticalDiff(data, wpl, diff_sign, x, y, height, &best_diff, &best_sum, &best_y); + // Find the strongest edge. + int test_y = y; + do { + ++test_y; + } while (EvaluateVerticalDiff(data, wpl, diff_sign, x, test_y, height, &best_diff, &best_sum, + &best_y)); + test_y = y; + do { + --test_y; + } while (EvaluateVerticalDiff(data, wpl, diff_sign, x, test_y, height, &best_diff, &best_sum, + &best_y)); + offset = diff_sign * (best_sum / 2 - threshold) + (y - best_y) * best_diff; + } else if (pt1.x == pt2.x && abs(gradient.x()) * 2 >= abs(gradient.y())) { + // Vertical step. diff_sign == 1 indicates black on the left. + int diff_sign = (pt1.y > pt2.y) == negative ? 1 : -1; + int x = pt1.x; + int y = height - std::max(pt1.y, pt2.y); + const l_uint32 *line = pixGetData(pix) + y * wpl; + int best_sum = 0; + int best_x = x; + EvaluateHorizontalDiff(line, diff_sign, x, width, &best_diff, &best_sum, &best_x); + // Find the strongest edge. + int test_x = x; + do { + ++test_x; + } while ( + EvaluateHorizontalDiff(line, diff_sign, test_x, width, &best_diff, &best_sum, &best_x)); + test_x = x; + do { + --test_x; + } while ( + EvaluateHorizontalDiff(line, diff_sign, test_x, width, &best_diff, &best_sum, &best_x)); + offset = diff_sign * (threshold - best_sum / 2) + (best_x - x) * best_diff; + } + offsets[s].offset_numerator = ClipToRange<int>(offset, -INT8_MAX, INT8_MAX); + offsets[s].pixel_diff = ClipToRange<int>(best_diff, 0, UINT8_MAX); + if (negative) { + gradient = -gradient; + } + // Compute gradient angle quantized to 256 directions, rotated by 64 (pi/2) + // to convert from gradient direction to edge direction. + offsets[s].direction = Modulo(FCOORD::binary_angle_plus_pi(gradient.angle()) + 64, 256); + prev_gradient = next_gradient; + } +} + +/** + * Adds sub-pixel resolution EdgeOffsets for the outline using only + * a binary image source. + * + * Runs a sliding window of 5 edge steps over the outline, maintaining a count + * of the number of steps in each of the 4 directions in the window, and a + * sum of the x or y position of each step (as appropriate to its direction.) + * Ignores single-count steps EXCEPT the sharp U-turn and smoothes out the + * perpendicular direction. Eg + * @verbatim + * ___ ___ Chain code from the left: + * |___ ___ ___| 222122212223221232223000 + * |___| |_| Corresponding counts of each direction: + * 0 00000000000000000123 + * 1 11121111001111100000 + * 2 44434443443333343321 + * 3 00000001111111112111 + * Count of direction at center 41434143413313143313 + * Step gets used? YNYYYNYYYNYYNYNYYYyY (y= U-turn exception) + * Path redrawn showing only the used points: + * ___ ___ + * ___ ___ ___| + * ___ _ + * @endverbatim + * Sub-pixel edge position cannot be shown well with ASCII-art, but each + * horizontal step's y position is the mean of the y positions of the steps + * in the same direction in the sliding window, which makes a much smoother + * outline, without losing important detail. + */ +void C_OUTLINE::ComputeBinaryOffsets() { + delete[] offsets; + offsets = new EdgeOffset[stepcount]; + // Count of the number of steps in each direction in the sliding window. + int dir_counts[4]; + // Sum of the positions (y for a horizontal step, x for vertical) in each + // direction in the sliding window. + int pos_totals[4]; + memset(dir_counts, 0, sizeof(dir_counts)); + memset(pos_totals, 0, sizeof(pos_totals)); + ICOORD pos = start; + ICOORD tail_pos = pos; + // tail_pos is the trailing position, with the next point to be lost from + // the window. + tail_pos -= step(stepcount - 1); + tail_pos -= step(stepcount - 2); + // head_pos is the leading position, with the next point to be added to the + // window. + ICOORD head_pos = tail_pos; + // Set up the initial window with 4 points in [-2, 2) + for (int s = -2; s < 2; ++s) { + increment_step(s, 1, &head_pos, dir_counts, pos_totals); + } + for (int s = 0; s < stepcount; pos += step(s++)) { + // At step s, s in the middle of [s-2, s+2]. + increment_step(s + 2, 1, &head_pos, dir_counts, pos_totals); + int dir_index = chain_code(s); + ICOORD step_vec = step(s); + int best_diff = 0; + int offset = 0; + // Use only steps that have a count of >=2 OR the strong U-turn with a + // single d and 2 at d-1 and 2 at d+1 (mod 4). + if (dir_counts[dir_index] >= 2 || + (dir_counts[dir_index] == 1 && dir_counts[Modulo(dir_index - 1, 4)] == 2 && + dir_counts[Modulo(dir_index + 1, 4)] == 2)) { + // Valid step direction. + best_diff = dir_counts[dir_index]; + int edge_pos = step_vec.x() == 0 ? pos.x() : pos.y(); + // The offset proposes that the actual step should be positioned at + // the mean position of the steps in the window of the same direction. + // See ASCII art above. + offset = pos_totals[dir_index] - best_diff * edge_pos; + } + offsets[s].offset_numerator = ClipToRange<int>(offset, -INT8_MAX, INT8_MAX); + offsets[s].pixel_diff = ClipToRange<int>(best_diff, 0, UINT8_MAX); + // The direction is just the vector from start to end of the window. + FCOORD direction(head_pos.x() - tail_pos.x(), head_pos.y() - tail_pos.y()); + offsets[s].direction = direction.to_direction(); + increment_step(s - 2, -1, &tail_pos, dir_counts, pos_totals); + } +} + +/** + * Renders the outline to the given pix, with left and top being + * the coords of the upper-left corner of the pix. + */ +void C_OUTLINE::render(int left, int top, Image pix) const { + ICOORD pos = start; + for (int stepindex = 0; stepindex < stepcount; ++stepindex) { + ICOORD next_step = step(stepindex); + if (next_step.y() < 0) { + pixRasterop(pix, 0, top - pos.y(), pos.x() - left, 1, PIX_NOT(PIX_DST), nullptr, 0, 0); + } else if (next_step.y() > 0) { + pixRasterop(pix, 0, top - pos.y() - 1, pos.x() - left, 1, PIX_NOT(PIX_DST), nullptr, 0, 0); + } + pos += next_step; + } +} + +/** + * Renders just the outline to the given pix (no fill), with left and top + * being the coords of the upper-left corner of the pix. + * @param left coord + * @param top coord + * @param pix the pix to outline + */ +void C_OUTLINE::render_outline(int left, int top, Image pix) const { + ICOORD pos = start; + for (int stepindex = 0; stepindex < stepcount; ++stepindex) { + ICOORD next_step = step(stepindex); + if (next_step.y() < 0) { + pixSetPixel(pix, pos.x() - left, top - pos.y(), 1); + } else if (next_step.y() > 0) { + pixSetPixel(pix, pos.x() - left - 1, top - pos.y() - 1, 1); + } else if (next_step.x() < 0) { + pixSetPixel(pix, pos.x() - left - 1, top - pos.y(), 1); + } else if (next_step.x() > 0) { + pixSetPixel(pix, pos.x() - left, top - pos.y() - 1, 1); + } + pos += next_step; + } +} + +/** + * @name C_OUTLINE::plot + * + * Draw the outline in the given colour. + * @param window window to draw in + * @param colour colour to draw in + */ + +#ifndef GRAPHICS_DISABLED +void C_OUTLINE::plot(ScrollView *window, ScrollView::Color colour) const { + int16_t stepindex; // index to cstep + ICOORD pos; // current position + DIR128 stepdir; // direction of step + + pos = start; // current position + window->Pen(colour); + if (stepcount == 0) { + window->Rectangle(box.left(), box.top(), box.right(), box.bottom()); + return; + } + window->SetCursor(pos.x(), pos.y()); + + stepindex = 0; + while (stepindex < stepcount) { + pos += step(stepindex); // step to next + stepdir = step_dir(stepindex); + stepindex++; // count steps + // merge straight lines + while (stepindex < stepcount && stepdir.get_dir() == step_dir(stepindex).get_dir()) { + pos += step(stepindex); + stepindex++; + } + window->DrawTo(pos.x(), pos.y()); + } +} + +/** + * Draws the outline in the given colour, normalized using the given denorm, + * making use of sub-pixel accurate information if available. + */ +void C_OUTLINE::plot_normed(const DENORM &denorm, ScrollView::Color colour, + ScrollView *window) const { + window->Pen(colour); + if (stepcount == 0) { + window->Rectangle(box.left(), box.top(), box.right(), box.bottom()); + return; + } + const DENORM *root_denorm = denorm.RootDenorm(); + ICOORD pos = start; // current position + FCOORD f_pos = sub_pixel_pos_at_index(pos, 0); + FCOORD pos_normed; + denorm.NormTransform(root_denorm, f_pos, &pos_normed); + window->SetCursor(IntCastRounded(pos_normed.x()), IntCastRounded(pos_normed.y())); + for (int s = 0; s < stepcount; pos += step(s++)) { + int edge_weight = edge_strength_at_index(s); + if (edge_weight == 0) { + // This point has conflicting gradient and step direction, so ignore it. + continue; + } + FCOORD f_pos = sub_pixel_pos_at_index(pos, s); + FCOORD pos_normed; + denorm.NormTransform(root_denorm, f_pos, &pos_normed); + window->DrawTo(IntCastRounded(pos_normed.x()), IntCastRounded(pos_normed.y())); + } +} +#endif + +/** + * @name C_OUTLINE::operator= + * + * Assignment - deep copy data + * @param source assign from this + */ + +C_OUTLINE &C_OUTLINE::operator=(const C_OUTLINE &source) { + box = source.box; + start = source.start; + if (!children.empty()) { + children.clear(); + } + children.deep_copy(&source.children, &deep_copy); + delete[] offsets; + offsets = nullptr; + stepcount = source.stepcount; + if (stepcount > 0) { + steps.resize(step_mem()); + memmove(&steps[0], &source.steps[0], step_mem()); + if (source.offsets != nullptr) { + offsets = new EdgeOffset[stepcount]; + memcpy(offsets, source.offsets, stepcount * sizeof(*offsets)); + } + } + return *this; +} + +/** + * Helper for ComputeBinaryOffsets. Increments pos, dir_counts, pos_totals + * by the step, increment, and vertical step ? x : y position * increment + * at step s Mod stepcount respectively. Used to add or subtract the + * direction and position to/from accumulators of a small neighbourhood. + */ +void C_OUTLINE::increment_step(int s, int increment, ICOORD *pos, int *dir_counts, + int *pos_totals) const { + int step_index = Modulo(s, stepcount); + int dir_index = chain_code(step_index); + dir_counts[dir_index] += increment; + ICOORD step_vec = step(step_index); + if (step_vec.x() == 0) { + pos_totals[dir_index] += pos->x() * increment; + } else { + pos_totals[dir_index] += pos->y() * increment; + } + *pos += step_vec; +} + +ICOORD C_OUTLINE::chain_step(int chaindir) { + return step_coords[chaindir % 4]; +} + +} // namespace tesseract
