rxing/pdf417/detector/pdf_417_detector.rs
1/*
2 * Copyright 2009 ZXing authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use crate::{
18 Binarizer, BinaryBitmap, DecodeHints, Exceptions, Point,
19 common::{BitMatrix, Result},
20 point,
21};
22
23use std::borrow::Cow;
24
25use super::PDF417DetectorRXingResult;
26
27/*
28 * <p>Encapsulates logic that can detect a PDF417 Code in an image, even if the
29 * PDF417 Code is rotated or skewed, or partially obscured.</p>
30 *
31 * @author SITA Lab (kevin.osullivan@sita.aero)
32 * @author dswitkin@google.com (Daniel Switkin)
33 * @author Guenther Grau
34 */
35
36const INDEXES_START_PATTERN: [u32; 4] = [0, 4, 1, 5];
37const INDEXES_STOP_PATTERN: [u32; 4] = [6, 2, 7, 3];
38const MAX_AVG_VARIANCE: f64 = 0.42;
39const MAX_INDIVIDUAL_VARIANCE: f64 = 0.8;
40
41// B S B S B S B S Bar/Space pattern
42// 11111111 0 1 0 1 0 1 000
43const START_PATTERN: [u32; 8] = [8, 1, 1, 1, 1, 1, 1, 3];
44// 1111111 0 1 000 1 0 1 00 1
45const STOP_PATTERN: [u32; 9] = [7, 1, 1, 3, 1, 1, 1, 2, 1];
46const MAX_PIXEL_DRIFT: u32 = 3;
47const MAX_PATTERN_DRIFT: u32 = 5;
48// if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged.
49// if we set the value too high, then we might detect the start pattern from a neighbor barcode.
50const SKIPPED_ROW_COUNT_MAX: u32 = 25;
51// A PDF471 barcode should have at least 3 rows, with each row being >= 3 times the module width.
52// Therefore it should be at least 9 pixels tall. To be conservative, we use about half the size to
53// ensure we don't miss it.
54const ROW_STEP: u32 = 5;
55const BARCODE_MIN_HEIGHT: u32 = 10;
56const ROTATIONS: [u32; 4] = [0, 180, 270, 90];
57
58/**
59 * <p>Detects a PDF417 Code in an image. Checks 0, 90, 180, and 270 degree rotations.</p>
60 *
61 * @param image barcode image to decode
62 * @param hints optional hints to detector
63 * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will
64 * be found and returned
65 * @return {@link PDF417DetectorRXingResult} encapsulating results of detecting a PDF417 code
66 * @throws NotFoundException if no PDF417 Code can be found
67 */
68pub fn detect_with_hints<B: Binarizer>(
69 image: &mut BinaryBitmap<B>,
70 _hints: &DecodeHints,
71 multiple: bool,
72) -> Result<PDF417DetectorRXingResult> {
73 // TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even
74 // different binarizers
75 //boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
76 //let try_harder = matches!(hints.get(&DecodeHintType::TRY_HARDER), Some(DecodeHintValue::TryHarder(true)));
77
78 let originalMatrix = image.get_black_matrix();
79 for rotation in ROTATIONS {
80 // for (int rotation : ROTATIONS) {
81 let bitMatrix = applyRotation(originalMatrix, rotation)?;
82 let barcodeCoordinates = detect(multiple, &bitMatrix).ok_or(Exceptions::NOT_FOUND)?;
83 if !barcodeCoordinates.is_empty() {
84 return Ok(PDF417DetectorRXingResult::with_rotation(
85 bitMatrix.into_owned(),
86 barcodeCoordinates,
87 rotation,
88 ));
89 }
90 }
91 Ok(PDF417DetectorRXingResult::with_rotation(
92 originalMatrix.clone(),
93 Vec::new(),
94 0,
95 ))
96}
97
98/**
99 * Applies a rotation to the supplied BitMatrix.
100 * @param matrix bit matrix to apply rotation to
101 * @param rotation the degrees of rotation to apply
102 * @return BitMatrix with applied rotation
103 */
104fn applyRotation(matrix: &'_ BitMatrix, rotation: u32) -> Result<Cow<'_, BitMatrix>> {
105 if rotation % 360 == 0 {
106 Ok(Cow::Borrowed(matrix))
107 } else {
108 let mut newMatrix = matrix.clone();
109 newMatrix.rotate(rotation)?;
110 Ok(Cow::Owned(newMatrix))
111 }
112}
113
114/**
115 * Detects PDF417 codes in an image. Only checks 0 degree rotation
116 * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will
117 * be found and returned
118 * @param bitMatrix bit matrix to detect barcodes in
119 * @return List of Point arrays containing the coordinates of found barcodes
120 */
121pub fn detect(multiple: bool, bitMatrix: &BitMatrix) -> Option<Vec<[Option<Point>; 8]>> {
122 let mut barcodeCoordinates: Vec<[Option<Point>; 8]> = Vec::new();
123 let mut row = 0;
124 let mut column = 0;
125 let mut foundBarcodeInRow = false;
126 while row < bitMatrix.getHeight() {
127 let vertices = findVertices(bitMatrix, row, column)?;
128
129 if vertices[0].is_none() && vertices[3].is_none() {
130 if !foundBarcodeInRow {
131 // we didn't find any barcode so that's the end of searching
132 break;
133 }
134 // we didn't find a barcode starting at the given column and row. Try again from the first column and slightly
135 // below the lowest barcode we found so far.
136 foundBarcodeInRow = false;
137 column = 0;
138 for barcodeCoordinate in &barcodeCoordinates {
139 if let Some(coord_1) = barcodeCoordinate[1] {
140 row = row.max(coord_1.y as u32);
141 }
142 if let Some(coord_3) = barcodeCoordinate[3] {
143 row = row.max(coord_3.y as u32);
144 }
145 }
146 row += ROW_STEP;
147 continue;
148 }
149 foundBarcodeInRow = true;
150 barcodeCoordinates.push(vertices);
151 if !multiple {
152 break;
153 }
154 // if we didn't find a right row indicator column, then continue the search for the next barcode after the
155 // start pattern of the barcode just found.
156 if let Some(vert_2) = vertices[2] {
157 column = vert_2.x as u32;
158 row = vert_2.y as u32;
159 } else {
160 column = vertices[4].as_ref().unwrap().x as u32;
161 row = vertices[4].as_ref().unwrap().y as u32;
162 }
163 }
164 Some(barcodeCoordinates)
165}
166
167/**
168 * Locate the vertices and the codewords area of a black blob using the Start
169 * and Stop patterns as locators.
170 *
171 * @param matrix the scanned barcode image.
172 * @return an array containing the vertices:
173 * vertices[0] x, y top left barcode
174 * vertices[1] x, y bottom left barcode
175 * vertices[2] x, y top right barcode
176 * vertices[3] x, y bottom right barcode
177 * vertices[4] x, y top left codeword area
178 * vertices[5] x, y bottom left codeword area
179 * vertices[6] x, y top right codeword area
180 * vertices[7] x, y bottom right codeword area
181 */
182fn findVertices(matrix: &BitMatrix, startRow: u32, startColumn: u32) -> Option<[Option<Point>; 8]> {
183 let height = matrix.getHeight();
184 let width = matrix.getWidth();
185 let mut startRow = startRow;
186 let mut startColumn = startColumn;
187
188 let mut result = [None::<Point>; 8]; //Point[8];
189 copyToRXingResult(
190 &mut result,
191 &findRowsWithPattern(matrix, height, width, startRow, startColumn, &START_PATTERN)?,
192 &INDEXES_START_PATTERN,
193 );
194
195 if let Some(result_4) = result[4] {
196 startColumn = result_4.x as u32;
197 startRow = result_4.y as u32;
198 }
199 copyToRXingResult(
200 &mut result,
201 &findRowsWithPattern(matrix, height, width, startRow, startColumn, &STOP_PATTERN)?,
202 &INDEXES_STOP_PATTERN,
203 );
204
205 Some(result)
206}
207
208fn copyToRXingResult(
209 result: &mut [Option<Point>],
210 tmpRXingResult: &[Option<Point>],
211 destinationIndexes: &[u32],
212) {
213 for i in 0..destinationIndexes.len() {
214 result[destinationIndexes[i] as usize] = tmpRXingResult[i];
215 }
216}
217
218fn findRowsWithPattern(
219 matrix: &BitMatrix,
220 height: u32,
221 width: u32,
222 startRow: u32,
223 startColumn: u32,
224 pattern: &[u32],
225) -> Option<[Option<Point>; 4]> {
226 let mut startRow = startRow;
227 let mut result = [None; 4];
228 let mut found = false;
229 let mut counters = vec![0_u32; pattern.len()];
230 while startRow < height {
231 let mut loc_store;
232 if let Some(loc) =
233 findGuardPattern(matrix, startColumn, startRow, width, pattern, &mut counters)
234 {
235 loc_store = Some(loc);
236 while startRow > 0 {
237 startRow -= 1;
238 if let Some(previousRowLoc) =
239 findGuardPattern(matrix, startColumn, startRow, width, pattern, &mut counters)
240 {
241 loc_store.replace(previousRowLoc);
242 // loc_store = Some(previousRowLoc);
243 } else {
244 startRow += 1;
245 break;
246 }
247 }
248 result[0] = Some(point(loc_store.as_ref()?[0] as f32, startRow as f32));
249 result[1] = Some(point(loc_store.as_ref()?[1] as f32, startRow as f32));
250 found = true;
251 break;
252 }
253
254 startRow += ROW_STEP;
255 }
256
257 let mut stopRow = startRow + 1;
258 // Last row of the current symbol that contains pattern
259 if found {
260 let mut skippedRowCount = 0;
261 let mut previousRowLoc = [result[0].as_ref()?.x as u32, result[1].as_ref()?.x as u32];
262 while stopRow < height {
263 if let Some(loc) = findGuardPattern(
264 matrix,
265 previousRowLoc[0],
266 stopRow,
267 width,
268 pattern,
269 &mut counters,
270 ) {
271 // a found pattern is only considered to belong to the same barcode if the start and end positions
272 // don't differ too much. Pattern drift should be not bigger than two for consecutive rows. With
273 // a higher number of skipped rows drift could be larger. To keep it simple for now, we allow a slightly
274 // larger drift and don't check for skipped rows.
275 if (previousRowLoc[0] as i32 - loc[0] as i32).unsigned_abs() < MAX_PATTERN_DRIFT
276 && (previousRowLoc[1] as i32 - loc[1] as i32).unsigned_abs() < MAX_PATTERN_DRIFT
277 {
278 previousRowLoc = loc;
279 skippedRowCount = 0;
280 } else if skippedRowCount > SKIPPED_ROW_COUNT_MAX {
281 break;
282 } else {
283 skippedRowCount += 1;
284 }
285 } else if skippedRowCount > SKIPPED_ROW_COUNT_MAX {
286 break;
287 } else {
288 skippedRowCount += 1;
289 }
290
291 stopRow += 1;
292 }
293 stopRow -= skippedRowCount + 1;
294 result[2] = Some(point(previousRowLoc[0] as f32, stopRow as f32));
295 result[3] = Some(point(previousRowLoc[1] as f32, stopRow as f32));
296 }
297 if stopRow - startRow < BARCODE_MIN_HEIGHT {
298 result.fill(None);
299 }
300
301 Some(result)
302}
303
304/**
305 * @param matrix row of black/white values to search
306 * @param column x position to start search
307 * @param row y position to start search
308 * @param width the number of pixels to search on this row
309 * @param pattern pattern of counts of number of black and white pixels that are
310 * being searched for as a pattern
311 * @param counters array of counters, as long as pattern, to re-use
312 * @return start/end horizontal offset of guard pattern, as an array of two ints.
313 */
314fn findGuardPattern(
315 matrix: &BitMatrix,
316 column: u32,
317 row: u32,
318 width: u32,
319 pattern: &[u32],
320 counters: &mut [u32],
321) -> Option<[u32; 2]> {
322 counters.fill(0);
323 let mut patternStart = column;
324 let mut pixelDrift = 0;
325
326 // if there are black pixels left of the current pixel shift to the left, but only for MAX_PIXEL_DRIFT pixels
327 while matrix.get(patternStart, row) && patternStart > 0 && pixelDrift < MAX_PIXEL_DRIFT {
328 pixelDrift += 1;
329 patternStart -= 1;
330 }
331 let mut x = patternStart;
332 let mut counterPosition = 0;
333 let patternLength = pattern.len();
334 let mut isWhite = false;
335 while x < width {
336 // for (boolean isWhite = false; x < width; x++) {
337 let pixel = matrix.get(x, row);
338 if pixel != isWhite {
339 counters[counterPosition] += 1;
340 } else {
341 if counterPosition == patternLength - 1 {
342 if patternMatchVariance(counters, pattern) < MAX_AVG_VARIANCE {
343 return Some([patternStart, x]);
344 }
345 patternStart += counters[0] + counters[1];
346
347 counters.copy_within(2..counterPosition - 1 + 2, 0);
348 // System.arraycopy(counters, 2, counters, 0, counterPosition - 1);
349
350 counters[counterPosition - 1] = 0;
351 counters[counterPosition] = 0;
352 counterPosition -= 1;
353 } else {
354 counterPosition += 1;
355 }
356 counters[counterPosition] = 1;
357 isWhite = !isWhite;
358 }
359 x += 1;
360 }
361 if counterPosition == patternLength - 1
362 && patternMatchVariance(counters, pattern) < MAX_AVG_VARIANCE
363 {
364 return Some([patternStart, x - 1]);
365 }
366
367 None
368}
369
370/**
371 * Determines how closely a set of observed counts of runs of black/white
372 * values matches a given target pattern. This is reported as the ratio of
373 * the total variance from the expected pattern proportions across all
374 * pattern elements, to the length of the pattern.
375 *
376 * @param counters observed counters
377 * @param pattern expected pattern
378 * @return ratio of total variance between counters and pattern compared to total pattern size
379 */
380fn patternMatchVariance(counters: &[u32], pattern: &[u32]) -> f64 {
381 let numCounters = counters.len();
382 let total = counters.iter().take(numCounters).sum::<u32>();
383 let patternLength = pattern.iter().take(numCounters).sum::<u32>();
384 // for i in 0..numCounters {
385 // total += counters[i];
386 // patternLength += pattern[i];
387 // }
388 if total < patternLength {
389 // If we don't even have one pixel per unit of bar width, assume this
390 // is too small to reliably match, so fail:
391 return f64::INFINITY; //Float.POSITIVE_INFINITY;
392 }
393 // We're going to fake floating-point math in integers. We just need to use more bits.
394 // Scale up patternLength so that intermediate values below like scaledCounter will have
395 // more "significant digits".
396 let unitBarWidth: f64 = total as f64 / patternLength as f64;
397 let maxIndividualVariance = MAX_INDIVIDUAL_VARIANCE * unitBarWidth;
398
399 let mut totalVariance = 0.0;
400 for x in 0..numCounters {
401 let counter = counters[x];
402 let scaledPattern: f64 = pattern[x] as f64 * unitBarWidth;
403 let variance: f64 = if counter as f64 > scaledPattern {
404 counter as f64 - scaledPattern
405 } else {
406 scaledPattern - counter as f64
407 };
408 if variance > maxIndividualVariance {
409 return f64::INFINITY;
410 }
411 totalVariance += variance;
412 }
413 totalVariance / total as f64
414}