comparison mupdf-source/thirdparty/zxing-cpp/core/src/GlobalHistogramBinarizer.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
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 /*
2 * Copyright 2016 Nu-book Inc.
3 * Copyright 2016 ZXing authors
4 */
5 // SPDX-License-Identifier: Apache-2.0
6
7 #include "GlobalHistogramBinarizer.h"
8
9 #include "BitMatrix.h"
10 #include "Pattern.h"
11 #include "ZXConfig.h"
12
13 #include <algorithm>
14 #include <array>
15 #include <utility>
16
17 namespace ZXing {
18
19 static constexpr int LUMINANCE_BITS = 5;
20 static constexpr int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
21 static constexpr int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
22
23 using Histogram = std::array<uint16_t, LUMINANCE_BUCKETS>;
24
25 GlobalHistogramBinarizer::GlobalHistogramBinarizer(const ImageView& buffer) : BinaryBitmap(buffer) {}
26
27 GlobalHistogramBinarizer::~GlobalHistogramBinarizer() = default;
28
29 using ImageLineView = Range<StrideIter<const uint8_t*>>;
30
31 inline ImageLineView RowView(const ImageView& iv, int row)
32 {
33 return {{iv.data(0, row), iv.pixStride()}, {iv.data(iv.width(), row), iv.pixStride()}};
34 }
35
36 static void ThresholdSharpened(const ImageLineView in, int threshold, std::vector<uint8_t>& out)
37 {
38 out.resize(in.size());
39 auto i = in.begin();
40 auto o = out.begin();
41
42 *o++ = (*i++ <= threshold) * BitMatrix::SET_V;
43 for (auto end = in.end() - 1; i != end; ++i)
44 *o++ = ((-i[-1] + (int(i[0]) * 4) - i[1]) / 2 <= threshold) * BitMatrix::SET_V;
45 *o++ = (*i++ <= threshold) * BitMatrix::SET_V;
46 }
47
48 static auto GenHistogram(const ImageLineView line)
49 {
50 // This code causes about 20% of the total runtime on an AVX2 system for a EAN13 search on Lum input data.
51 // Trying to increase the performance by performing 2 or 4 "parallel" histograms helped nothing.
52 Histogram res = {};
53 for (auto pix : line)
54 res[pix >> LUMINANCE_SHIFT]++;
55 return res;
56 }
57
58 // Return -1 on error
59 static int EstimateBlackPoint(const Histogram& buckets)
60 {
61 // Find the tallest peak in the histogram.
62 auto firstPeakPos = std::max_element(buckets.begin(), buckets.end());
63 int firstPeak = narrow_cast<int>(firstPeakPos - buckets.begin());
64 int firstPeakSize = *firstPeakPos;
65 int maxBucketCount = firstPeakSize;
66
67 // Find the second-tallest peak which is somewhat far from the tallest peak.
68 int secondPeak = 0;
69 int secondPeakScore = 0;
70 for (int x = 0; x < Size(buckets); x++) {
71 int distanceToBiggest = x - firstPeak;
72 // Encourage more distant second peaks by multiplying by square of distance.
73 int score = buckets[x] * distanceToBiggest * distanceToBiggest;
74 if (score > secondPeakScore) {
75 secondPeak = x;
76 secondPeakScore = score;
77 }
78 }
79
80 // Make sure firstPeak corresponds to the black peak.
81 if (firstPeak > secondPeak) {
82 std::swap(firstPeak, secondPeak);
83 }
84
85 // If there is too little contrast in the image to pick a meaningful black point, throw rather
86 // than waste time trying to decode the image, and risk false positives.
87 if (secondPeak - firstPeak <= LUMINANCE_BUCKETS / 16) {
88 return -1;
89 }
90
91 // Find a valley between them that is low and closer to the white peak.
92 int bestValley = secondPeak - 1;
93 int bestValleyScore = -1;
94 for (int x = secondPeak - 1; x > firstPeak; x--) {
95 int fromFirst = x - firstPeak;
96 int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);
97 if (score > bestValleyScore) {
98 bestValley = x;
99 bestValleyScore = score;
100 }
101 }
102
103 return bestValley << LUMINANCE_SHIFT;
104 }
105
106 bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const
107 {
108 auto buffer = _buffer.rotated(rotation);
109 auto lineView = RowView(buffer, row);
110
111 if (buffer.width() < 3)
112 return false; // special casing the code below for a width < 3 makes no sense
113
114 #if defined(__AVX__) // or defined(__ARM_NEON)
115 // If we are extracting a column (instead of a row), we run into cache misses on every pixel access both
116 // during the histogram calculation and during the sharpen+threshold operation. Additionally, if we
117 // perform the ThresholdSharpened function on pixStride==1 data, the auto-vectorizer makes that part
118 // 8x faster on an AVX2 cpu which easily recovers the extra cost that we pay for the copying.
119 ZX_THREAD_LOCAL std::vector<uint8_t> line;
120 if (std::abs(buffer.pixStride()) > 4) {
121 line.resize(lineView.size());
122 std::copy(lineView.begin(), lineView.end(), line.begin());
123 lineView = {{line.data(), 1}, {line.data() + line.size(), 1}};
124 }
125 #endif
126
127 auto threshold = EstimateBlackPoint(GenHistogram(lineView)) - 1;
128 if (threshold <= 0)
129 return false;
130
131 ZX_THREAD_LOCAL std::vector<uint8_t> binarized;
132 // the optimizer can generate a specialized version for pixStride==1 (non-rotated input) that is about 8x faster on AVX2 hardware
133 if (lineView.begin().stride == 1)
134 ThresholdSharpened(lineView, threshold, binarized);
135 else
136 ThresholdSharpened(lineView, threshold, binarized);
137 GetPatternRow(Range(binarized), res);
138
139 return true;
140 }
141
142 // Does not sharpen the data, as this call is intended to only be used by 2D Readers.
143 std::shared_ptr<const BitMatrix>
144 GlobalHistogramBinarizer::getBlackMatrix() const
145 {
146 // Quickly calculates the histogram by sampling four rows from the image. This proved to be
147 // more robust on the blackbox tests than sampling a diagonal as we used to do.
148 Histogram localBuckets = {};
149 {
150 for (int y = 1; y < 5; y++) {
151 int row = height() * y / 5;
152 const uint8_t* luminances = _buffer.data(0, row);
153 int right = (width() * 4) / 5;
154 for (int x = width() / 5; x < right; x++)
155 localBuckets[luminances[x] >> LUMINANCE_SHIFT]++;
156 }
157 }
158
159 int blackPoint = EstimateBlackPoint(localBuckets);
160 if (blackPoint <= 0)
161 return {};
162
163
164
165 return std::make_shared<const BitMatrix>(binarize(blackPoint));
166 }
167
168 } // ZXing