rxing/oned/itf_reader.rs
1/*
2 * Copyright 2008 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 rxing_one_d_proc_derive::OneDReader;
18
19use crate::{RXingResultMetadataType, RXingResultMetadataValue};
20
21use crate::{
22 BarcodeFormat, Exceptions, RXingResult,
23 common::{BitArray, Result},
24 point,
25};
26
27use super::{OneDReader, one_d_reader};
28
29const MAX_AVG_VARIANCE: f32 = 0.38;
30const MAX_INDIVIDUAL_VARIANCE: f32 = 0.5;
31
32const W: u32 = 3; // Pixel width of a 3x wide line
33const W_LOWER: u32 = 2; // Pixel width of a 2x wide line
34const N: u32 = 1; // Pixed width of a narrow line
35
36/** Valid ITF lengths. Anything longer than the largest value is also allowed. */
37const DEFAULT_ALLOWED_LENGTHS: [u32; 5] = [6, 8, 10, 12, 14];
38
39/**
40 * Start/end guard pattern.
41 *
42 * Note: The end pattern is reversed because the row is reversed before
43 * searching for the END_PATTERN
44 */
45const START_PATTERN: [u32; 4] = [N, N, N, N];
46const END_PATTERN_REVERSED: [[u32; 3]; 2] = [
47 [N, N, W_LOWER], // 2x
48 [N, N, W], // 3x
49];
50
51// See ITFWriter.PATTERNS
52
53/**
54 * Patterns of Wide / Narrow lines to indicate each digit
55 */
56const PATTERNS: [[u32; 5]; 20] = [
57 [N, N, W_LOWER, W_LOWER, N], // 0
58 [W_LOWER, N, N, N, W_LOWER], // 1
59 [N, W_LOWER, N, N, W_LOWER], // 2
60 [W_LOWER, W_LOWER, N, N, N], // 3
61 [N, N, W_LOWER, N, W_LOWER], // 4
62 [W_LOWER, N, W_LOWER, N, N], // 5
63 [N, W_LOWER, W_LOWER, N, N], // 6
64 [N, N, N, W_LOWER, W_LOWER], // 7
65 [W_LOWER, N, N, W_LOWER, N], // 8
66 [N, W_LOWER, N, W_LOWER, N], // 9
67 [N, N, W, W, N], // 0
68 [W, N, N, N, W], // 1
69 [N, W, N, N, W], // 2
70 [W, W, N, N, N], // 3
71 [N, N, W, N, W], // 4
72 [W, N, W, N, N], // 5
73 [N, W, W, N, N], // 6
74 [N, N, N, W, W], // 7
75 [W, N, N, W, N], // 8
76 [N, W, N, W, N], // 9
77];
78
79/**
80 * <p>Implements decoding of the ITF format, or Interleaved Two of Five.</p>
81 *
82 * <p>This Reader will scan ITF barcodes of certain lengths only.
83 * At the moment it reads length 6, 8, 10, 12, 14, 16, 18, 20, 24, and 44 as these have appeared "in the wild". Not all
84 * lengths are scanned, especially shorter ones, to avoid false positives. This in turn is due to a lack of
85 * required checksum function.</p>
86 *
87 * <p>The checksum is optional and is not applied by this Reader. The consumer of the decoded
88 * value will have to apply a checksum if required.</p>
89 *
90 * <p><a href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.org/wiki/Interleaved_2_of_5</a>
91 * is a great reference for Interleaved 2 of 5 information.</p>
92 *
93 * @author kevin.osullivan@sita.aero, SITA Lab.
94 */
95#[derive(OneDReader)]
96pub struct ITFReader {
97 // Stores the actual narrow line width of the image being decoded.
98 narrowLineWidth: i32,
99}
100
101impl Default for ITFReader {
102 fn default() -> Self {
103 Self {
104 narrowLineWidth: -1,
105 }
106 }
107}
108
109impl OneDReader for ITFReader {
110 fn decode_row(
111 &mut self,
112 rowNumber: u32,
113 row: &crate::common::BitArray,
114 hints: &crate::DecodeHints,
115 ) -> Result<crate::RXingResult> {
116 // Find out where the Middle section (payload) starts & ends
117 let mut row = row.clone();
118 let startRange = self.decodeStart(&row)?;
119 let endRange = self.decodeEnd(&mut row)?;
120
121 let mut result = String::with_capacity(20); //new StringBuilder(20);
122 self.decodeMiddle(&row, startRange[1], endRange[0], &mut result)?;
123 let resultString = result; //.toString();
124
125 let allowedLengths = if let Some(al) = &hints.AllowedLengths {
126 al.clone()
127 } else {
128 DEFAULT_ALLOWED_LENGTHS.to_vec()
129 };
130
131 // To avoid false positives with 2D barcodes (and other patterns), make
132 // an assumption that the decoded string must be a 'standard' length if it's short
133 let length = resultString.chars().count();
134 let mut lengthOK = false;
135 let mut maxAllowedLength = 0;
136 for allowedLength in allowedLengths {
137 if length == allowedLength as usize {
138 lengthOK = true;
139 break;
140 }
141 maxAllowedLength = std::cmp::max(allowedLength, maxAllowedLength);
142 }
143 if !lengthOK && length > maxAllowedLength as usize {
144 lengthOK = true;
145 }
146 if !lengthOK {
147 return Err(Exceptions::FORMAT);
148 }
149
150 let mut resultObject = RXingResult::new(
151 &resultString,
152 Vec::new(), // no natural byte representation for these barcodes
153 vec![
154 point(startRange[1] as f32, rowNumber as f32),
155 point(endRange[0] as f32, rowNumber as f32),
156 ],
157 BarcodeFormat::ITF,
158 );
159
160 resultObject.putMetadata(
161 RXingResultMetadataType::SYMBOLOGY_IDENTIFIER,
162 RXingResultMetadataValue::SymbologyIdentifier("]I0".to_owned()),
163 );
164
165 Ok(resultObject)
166 }
167}
168impl ITFReader {
169 /**
170 * @param row row of black/white values to search
171 * @param payloadStart offset of start pattern
172 * @param resultString {@link StringBuilder} to append decoded chars to
173 * @throws NotFoundException if decoding could not complete successfully
174 */
175 fn decodeMiddle(
176 &self,
177 row: &BitArray,
178 payloadStart: usize,
179 payloadEnd: usize,
180 resultString: &mut String,
181 ) -> Result<()> {
182 let mut payloadStart = payloadStart;
183 // Digits are interleaved in pairs - 5 black lines for one digit, and the
184 // 5 interleaved white lines for the second digit.
185 // Therefore, need to scan 10 lines and then
186 // split these into two arrays
187 let mut counterDigitPair = [0_u32; 10]; //new int[10];
188 let mut counterBlack = [0_u32; 5]; //new int[5];
189 let mut counterWhite = [0_u32; 5]; //new int[5];
190
191 while payloadStart < payloadEnd {
192 // Get 10 runs of black/white.
193 one_d_reader::record_pattern(row, payloadStart, &mut counterDigitPair)?;
194 // Split them into each array
195 for k in 0..5 {
196 let twoK = 2 * k;
197 counterBlack[k] = counterDigitPair[twoK];
198 counterWhite[k] = counterDigitPair[twoK + 1];
199 }
200
201 let mut bestMatch = self.decodeDigit(&counterBlack)?;
202 resultString.push(char::from_u32('0' as u32 + bestMatch).ok_or(Exceptions::PARSE)?);
203 bestMatch = self.decodeDigit(&counterWhite)?;
204 resultString.push(char::from_u32('0' as u32 + bestMatch).ok_or(Exceptions::PARSE)?);
205
206 payloadStart += counterDigitPair.iter().sum::<u32>() as usize;
207 }
208
209 Ok(())
210 }
211
212 /**
213 * Identify where the start of the middle / payload section starts.
214 *
215 * @param row row of black/white values to search
216 * @return Array, containing index of start of 'start block' and end of
217 * 'start block'
218 */
219 fn decodeStart(&mut self, row: &BitArray) -> Result<[usize; 2]> {
220 let endStart = Self::skipWhiteSpace(row)?;
221 let startPattern = self.findGuardPattern(row, endStart, &START_PATTERN)?;
222
223 // Determine the width of a narrow line in pixels. We can do this by
224 // getting the width of the start pattern and dividing by 4 because its
225 // made up of 4 narrow lines.
226 self.narrowLineWidth = (startPattern[1] - startPattern[0]) as i32 / 4;
227
228 self.validateQuietZone(row, startPattern[0])?;
229
230 Ok(startPattern)
231 }
232
233 /**
234 * The start & end patterns must be pre/post fixed by a quiet zone. This
235 * zone must be at least 10 times the width of a narrow line. Scan back until
236 * we either get to the start of the barcode or match the necessary number of
237 * quiet zone pixels.
238 *
239 * Note: Its assumed the row is reversed when using this method to find
240 * quiet zone after the end pattern.
241 *
242 * ref: http://www.barcode-1.net/i25code.html
243 *
244 * @param row bit array representing the scanned barcode.
245 * @param startPattern index into row of the start or end pattern.
246 * @throws NotFoundException if the quiet zone cannot be found
247 */
248 fn validateQuietZone(&self, row: &BitArray, startPattern: usize) -> Result<()> {
249 let mut quietCount = self.narrowLineWidth * 10; // expect to find this many pixels of quiet zone
250
251 // if there are not so many pixel at all let's try as many as possible
252 quietCount = quietCount.min(startPattern as i32);
253
254 let mut i = startPattern as isize - 1;
255 while quietCount > 0 && i >= 0 {
256 if row.get(i as usize) {
257 break;
258 }
259 quietCount -= 1;
260 i -= 1;
261 }
262
263 if quietCount != 0 {
264 // Unable to find the necessary number of quiet zone pixels.
265 Err(Exceptions::NOT_FOUND)
266 } else {
267 Ok(())
268 }
269 }
270
271 /**
272 * Skip all whitespace until we get to the first black line.
273 *
274 * @param row row of black/white values to search
275 * @return index of the first black line.
276 * @throws NotFoundException Throws exception if no black lines are found in the row
277 */
278 fn skipWhiteSpace(row: &BitArray) -> Result<usize> {
279 let width = row.get_size();
280 let endStart = row.getNextSet(0);
281 if endStart == width {
282 return Err(Exceptions::NOT_FOUND);
283 }
284
285 Ok(endStart)
286 }
287
288 /**
289 * Identify where the end of the middle / payload section ends.
290 *
291 * @param row row of black/white values to search
292 * @return Array, containing index of start of 'end block' and end of 'end
293 * block'
294 */
295 fn decodeEnd(&self, row: &mut BitArray) -> Result<[usize; 2]> {
296 // For convenience, reverse the row and then
297 // search from 'the start' for the end block
298 row.reverse();
299 let interim_function = || -> Result<[usize; 2]> {
300 let endStart = Self::skipWhiteSpace(row)?;
301 let mut endPattern =
302 if let Ok(ptrn) = self.findGuardPattern(row, endStart, &END_PATTERN_REVERSED[0]) {
303 ptrn
304 } else {
305 self.findGuardPattern(row, endStart, &END_PATTERN_REVERSED[1])?
306 };
307
308 // The start & end patterns must be pre/post fixed by a quiet zone. This
309 // zone must be at least 10 times the width of a narrow line.
310 // ref: http://www.barcode-1.net/i25code.html
311 self.validateQuietZone(row, endPattern[0])?;
312
313 // Now recalculate the indices of where the 'endblock' starts & stops to
314 // accommodate the reversed nature of the search
315 let temp = endPattern[0];
316 endPattern[0] = row.get_size() - endPattern[1];
317 endPattern[1] = row.get_size() - temp;
318
319 Ok(endPattern)
320 };
321 let res = interim_function();
322 // Put the row back the right way.
323 row.reverse();
324
325 res
326 }
327
328 /**
329 * @param row row of black/white values to search
330 * @param rowOffset position to start search
331 * @param pattern pattern of counts of number of black and white pixels that are
332 * being searched for as a pattern
333 * @return start/end horizontal offset of guard pattern, as an array of two
334 * ints
335 * @throws NotFoundException if pattern is not found
336 */
337 fn findGuardPattern<const N: usize>(
338 &self,
339 row: &BitArray,
340 rowOffset: usize,
341 pattern: &[u32; N],
342 ) -> Result<[usize; 2]> {
343 let patternLength = N;
344 let mut counters = [0u32; N]; //new int[patternLength];
345 let width = row.get_size();
346 let mut isWhite = false;
347
348 let mut counterPosition = 0;
349 let mut patternStart = rowOffset;
350 for x in rowOffset..width {
351 // for (int x = rowOffset; x < width; x++) {
352 if row.get(x) != isWhite {
353 counters[counterPosition] += 1;
354 } else {
355 if counterPosition == patternLength - 1 {
356 if one_d_reader::pattern_match_variance(
357 &counters,
358 pattern,
359 MAX_INDIVIDUAL_VARIANCE,
360 ) < MAX_AVG_VARIANCE
361 {
362 return Ok([patternStart, x]);
363 }
364 patternStart += (counters[0] + counters[1]) as usize;
365
366 counters.copy_within(2..(counterPosition - 1 + 2), 0);
367 counters[counterPosition - 1] = 0;
368 counters[counterPosition] = 0;
369 counterPosition -= 1;
370 } else {
371 counterPosition += 1;
372 }
373 counters[counterPosition] = 1;
374 isWhite = !isWhite;
375 }
376 }
377 Err(Exceptions::NOT_FOUND)
378 }
379
380 /**
381 * Attempts to decode a sequence of ITF black/white lines into single
382 * digit.
383 *
384 * @param counters the counts of runs of observed black/white/black/... values
385 * @return The decoded digit
386 * @throws NotFoundException if digit cannot be decoded
387 */
388 fn decodeDigit(&self, counters: &[u32; 5]) -> Result<u32> {
389 let mut bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
390 let mut bestMatch = -1_isize;
391 for (i, pattern) in PATTERNS.iter().enumerate() {
392 let variance =
393 one_d_reader::pattern_match_variance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
394 if variance < bestVariance {
395 bestVariance = variance;
396 bestMatch = i as isize;
397 } else if variance == bestVariance {
398 // if we find a second 'best match' with the same variance, we can not reliably report to have a suitable match
399 bestMatch = -1;
400 continue;
401 }
402 }
403 if bestMatch >= 0 {
404 Ok(bestMatch as u32 % 10)
405 } else {
406 Err(Exceptions::NOT_FOUND)
407 }
408 }
409}