rxing/common/
global_histogram_binarizer.rs1use std::borrow::Cow;
24
25use once_cell::sync::OnceCell;
26
27use crate::common::Result;
28use crate::{Binarizer, Exceptions, LuminanceSource};
29
30use super::{BitArray, BitMatrix, LineOrientation};
31
32const LUMINANCE_BITS: usize = 5;
33const LUMINANCE_SHIFT: usize = 8 - LUMINANCE_BITS;
34const LUMINANCE_BUCKETS: usize = 1 << LUMINANCE_BITS;
35
36pub struct GlobalHistogramBinarizer<LS: LuminanceSource> {
48 width: usize,
50 height: usize,
51 source: LS,
52 black_matrix: OnceCell<BitMatrix>,
53 black_row_cache: Vec<OnceCell<BitArray>>,
54 black_column_cache: Vec<OnceCell<BitArray>>,
55}
56
57impl<LS: LuminanceSource> Binarizer for GlobalHistogramBinarizer<LS> {
58 type Source = LS;
59
60 fn get_luminance_source(&self) -> &Self::Source {
61 &self.source
62 }
63
64 fn get_black_row(&'_ self, y: usize) -> Result<Cow<'_, BitArray>> {
66 let row = self.black_row_cache[y].get_or_try_init(|| {
67 let source = self.get_luminance_source();
68 let width = source.get_width();
69 let mut row = BitArray::with_size(width);
70
71 let localLuminances = source
73 .get_row(y)
74 .ok_or(Exceptions::index_out_of_bounds_with("row out of bounds"))?;
75 let mut localBuckets = [0; LUMINANCE_BUCKETS]; for x in 0..width {
77 localBuckets[((localLuminances[x]) >> LUMINANCE_SHIFT) as usize] += 1;
79 }
80 let blackPoint = Self::estimateBlackPoint(&localBuckets)?;
81
82 if width < 3 {
83 for (x, &lum) in localLuminances.iter().enumerate().take(width) {
85 if (lum as u32) < blackPoint {
88 row.set(x);
89 }
90 }
91 } else {
92 let mut left = localLuminances[0]; let mut center = localLuminances[1]; for x in 1..width - 1 {
95 let right = localLuminances[x + 1];
97 if ((center as i64 * 4) - left as i64 - right as i64) / 2 < blackPoint as i64 {
99 row.set(x);
100 }
101 left = center;
102 center = right;
103 }
104 }
105
106 Ok(row)
107 })?;
108
109 Ok(Cow::Borrowed(row))
110 }
111
112 fn get_black_line(&'_ self, l: usize, lt: LineOrientation) -> Result<Cow<'_, BitArray>> {
113 if lt == LineOrientation::Row {
114 self.get_black_row(l)
115 } else {
116 let col = self.black_column_cache[l].get_or_try_init(|| {
117 let source = self.get_luminance_source();
118 let width = source.get_height();
119 let mut col = BitArray::with_size(width);
120
121 let localLuminances = source.get_column(l);
123 let mut localBuckets = [0; LUMINANCE_BUCKETS]; for x in 0..width {
125 localBuckets[((localLuminances[x]) >> LUMINANCE_SHIFT) as usize] += 1;
127 }
128 let blackPoint = Self::estimateBlackPoint(&localBuckets)?;
129
130 if width < 3 {
131 for (x, lum) in localLuminances.iter().enumerate().take(width) {
133 if (*lum as u32) < blackPoint {
136 col.set(x);
137 }
138 }
139 } else {
140 let mut left = localLuminances[0]; let mut center = localLuminances[1]; for x in 1..width - 1 {
143 let right = localLuminances[x + 1];
145 if ((center as i64 * 4) - left as i64 - right as i64) / 2
147 < blackPoint as i64
148 {
149 col.set(x);
150 }
151 left = center;
152 center = right;
153 }
154 }
155
156 Ok(col)
157 })?;
158
159 Ok(Cow::Borrowed(col))
160 }
161 }
162
163 fn get_black_matrix(&self) -> Result<&BitMatrix> {
165 let matrix = self
166 .black_matrix
167 .get_or_try_init(|| Self::build_black_matrix(&self.source))?;
168 Ok(matrix)
169 }
170
171 fn create_binarizer(&self, source: LS) -> Self {
172 Self::new(source)
173 }
174
175 fn get_width(&self) -> usize {
176 self.width
177 }
178
179 fn get_height(&self) -> usize {
180 self.height
181 }
182
183 fn get_black_row_from_matrix(&'_ self, y: usize) -> Result<Cow<'_, BitArray>> {
184 if let Some(matrix) = self.black_matrix.get() {
185 Ok(Cow::Owned(matrix.getRow(y as u32)))
186 } else {
187 self.get_black_row(y)
188 }
189 }
190}
191
192impl<LS: LuminanceSource> GlobalHistogramBinarizer<LS> {
193 pub fn new(source: LS) -> Self {
196 Self {
197 width: source.get_width(),
199 height: source.get_height(),
200 black_matrix: OnceCell::new(),
201 black_row_cache: vec![OnceCell::default(); source.get_height()],
202 black_column_cache: vec![OnceCell::default(); source.get_width()],
203 source,
204 }
205 }
206
207 fn build_black_matrix(source: &LS) -> Result<BitMatrix> {
208 let width = source.get_width();
210 let height = source.get_height();
211 let mut matrix = BitMatrix::new(width as u32, height as u32)?;
212
213 let mut localBuckets = [0; LUMINANCE_BUCKETS]; for y in 1..5 {
218 let row = height * y / 5;
220 let localLuminances = source
221 .get_row(row)
222 .ok_or(Exceptions::index_out_of_bounds_with("row out of bounds"))?;
223 let right = (width * 4) / 5;
224 for pixel in &localLuminances[(width / 5)..right] {
225 localBuckets[(pixel >> LUMINANCE_SHIFT) as usize] += 1;
228 }
229 }
230 let blackPoint = Self::estimateBlackPoint(&localBuckets)?;
231
232 let localLuminances = source.get_matrix();
236 for y in 0..height {
237 let offset = y * width;
238 for x in 0..width {
239 let pixel = localLuminances[offset + x];
240 if (pixel as u32) < blackPoint {
241 matrix.set(x as u32, y as u32);
242 }
243 }
244 }
245
246 Ok(matrix)
247 }
248
249 fn estimateBlackPoint<const BUCKET_COUNT: usize>(buckets: &[u32; BUCKET_COUNT]) -> Result<u32> {
250 let mut maxBucketCount = 0;
252 let mut firstPeak = 0;
253 let mut firstPeakSize = 0;
254 for (x, &bucket) in buckets.iter().enumerate() {
255 if bucket > firstPeakSize {
256 firstPeak = x;
257 firstPeakSize = bucket;
258 }
259 if bucket > maxBucketCount {
260 maxBucketCount = bucket;
261 }
262 }
263
264 let mut secondPeak = 0;
266 let mut secondPeakScore = 0;
267 for (x, bucket) in buckets.iter().enumerate() {
268 let distanceToBiggest = (x as i32 - firstPeak as i32).unsigned_abs();
269 let score = *bucket * distanceToBiggest * distanceToBiggest;
271 if score > secondPeakScore {
272 secondPeak = x;
273 secondPeakScore = score;
274 }
275 }
276
277 if firstPeak > secondPeak {
279 std::mem::swap(&mut firstPeak, &mut secondPeak);
280 }
281
282 if secondPeak - firstPeak <= BUCKET_COUNT / 16 {
285 return Err(Exceptions::not_found_with(
286 "secondPeak - firstPeak <= numBuckets / 16 ",
287 ));
288 }
289
290 let mut bestValley = secondPeak as isize - 1;
292 let mut bestValleyScore = -1;
293
294 let mut x = secondPeak as isize;
295 while x > firstPeak as isize {
296 let fromFirst = x - firstPeak as isize;
297 let score = fromFirst
298 * fromFirst
299 * (secondPeak as isize - x)
300 * (maxBucketCount - buckets[x as usize]) as isize;
301 if score as i32 > bestValleyScore {
302 bestValley = x;
303 bestValleyScore = score as i32;
304 }
305 x -= 1;
306 }
307
308 Ok((bestValley as u32) << LUMINANCE_SHIFT)
309 }
310}