From 34e2473a5d20176935343a341f7ffcaf97ed357f Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sat, 3 Dec 2022 11:43:27 -0600 Subject: [PATCH] coda_bar reader passes --- Cargo.toml | 1 + one-d-reader-derive/Cargo.toml | 13 + one-d-reader-derive/src/lib.rs | 84 ++++ src/common/global_histogram_binarizer.rs | 4 +- src/oned/CodaBarReader.java | 343 -------------- src/oned/EANManufacturerOrgSupport.java | 171 ------- src/oned/OneDReader.java | 296 ------------ src/oned/coda_bar_reader.rs | 427 ++++++++++++++++++ src/oned/ean_manufacturer_org_support.rs | 183 ++++++++ src/oned/mod.rs | 9 + src/oned/one_d_reader.rs | 284 ++++++++++++ src/rxing_result.rs | 4 + ...e.java => codabar_blackbox_1_test_case.rs} | 24 +- 13 files changed, 1020 insertions(+), 823 deletions(-) create mode 100644 one-d-reader-derive/Cargo.toml create mode 100644 one-d-reader-derive/src/lib.rs delete mode 100644 src/oned/CodaBarReader.java delete mode 100644 src/oned/EANManufacturerOrgSupport.java delete mode 100644 src/oned/OneDReader.java create mode 100644 src/oned/coda_bar_reader.rs create mode 100644 src/oned/ean_manufacturer_org_support.rs create mode 100644 src/oned/one_d_reader.rs rename tests/{CodabarBlackBox1TestCase.java => codabar_blackbox_1_test_case.rs} (55%) diff --git a/Cargo.toml b/Cargo.toml index b6bfaca..d4c2831 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ image = {version = "0.24.3", optional = true} imageproc = {version = "0.23.0", optional = true} unicode-segmentation = "1.10.0" codepage-437 = "0.1.0" +one-d-reader-derive = { path = "one-d-reader-derive" } [dev-dependencies] java-properties = "1.4.1" diff --git a/one-d-reader-derive/Cargo.toml b/one-d-reader-derive/Cargo.toml new file mode 100644 index 0000000..d4e1537 --- /dev/null +++ b/one-d-reader-derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "one-d-reader-derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +syn = "1.0" +quote = "1.0" diff --git a/one-d-reader-derive/src/lib.rs b/one-d-reader-derive/src/lib.rs new file mode 100644 index 0000000..60279c2 --- /dev/null +++ b/one-d-reader-derive/src/lib.rs @@ -0,0 +1,84 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn; + +#[proc_macro_derive(OneDReader)] +pub fn one_d_reader_derive(input: TokenStream) -> TokenStream { + // Construct a representation of Rust code as a syntax tree + // that we can manipulate + let ast = syn::parse(input).unwrap(); + + // Build the trait implementation + impl_one_d_reader_macro(&ast) +} + +fn impl_one_d_reader_macro(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + let gen = quote! { + use std::collections::HashMap; + use crate::result_point::ResultPoint; + use crate::DecodeHintType; + use crate::DecodingHintDictionary; + use crate::RXingResultMetadataType; + use crate::RXingResultMetadataValue; + use crate::RXingResultPoint; + use crate::Reader; + + impl Reader for #name { + fn decode(&mut self, image: &crate::BinaryBitmap) -> Result { + self.decode_with_hints(image, &HashMap::new()) + } + + // Note that we don't try rotation without the try harder flag, even if rotation was supported. + fn decode_with_hints( + &mut self, + image: &crate::BinaryBitmap, + hints: &DecodingHintDictionary, + ) -> Result { + if let Ok(res) = self.doDecode(image, hints) { + Ok(res) + }else { + let tryHarder = hints.contains_key(&DecodeHintType::TRY_HARDER); + if tryHarder && image.isRotateSupported() { + let rotatedImage = image.rotateCounterClockwise(); + let mut result = self.doDecode(&rotatedImage, hints)?; + // Record that we found it rotated 90 degrees CCW / 270 degrees CW + let metadata = result.getRXingResultMetadata(); + let mut orientation = 270; + if metadata.contains_key(&RXingResultMetadataType::ORIENTATION) { + // But if we found it reversed in doDecode(), add in that result here: + orientation = (orientation + + if let Some(crate::RXingResultMetadataValue::Orientation(or)) = metadata.get(&RXingResultMetadataType::ORIENTATION) { + *or + }else { + 0 + }) % 360; + } + result.putMetadata(RXingResultMetadataType::ORIENTATION, RXingResultMetadataValue::Orientation(orientation)); + // Update result points + // let points = result.getRXingResultPoints(); + // if points != null { + let height = rotatedImage.getHeight(); + // for point in result.getRXingResultPointsMut().iter_mut() { + let total_points = result.getRXingResultPoints().len(); + let points = result.getRXingResultPointsMut(); + for i in 0..total_points{ + // for (int i = 0; i < points.length; i++) { + points[i] = RXingResultPoint::new(height as f32- points[i].getY() - 1.0, points[i].getX()); + } + // } + + Ok(result) + } else { + return Err(Exceptions::NotFoundException("".to_owned())) + } + } + } + + fn reset(&mut self) { + // do nothing + } + } + }; + gen.into() +} \ No newline at end of file diff --git a/src/common/global_histogram_binarizer.rs b/src/common/global_histogram_binarizer.rs index 2d59d1d..d18a9d7 100644 --- a/src/common/global_histogram_binarizer.rs +++ b/src/common/global_histogram_binarizer.rs @@ -85,9 +85,9 @@ impl Binarizer for GlobalHistogramBinarizer { let mut center = localLuminances[1]; // & 0xff; for x in 1..width - 1 { // for (int x = 1; x < width - 1; x++) { - let right = localLuminances[x + 1] & 0xff; + let right = localLuminances[x + 1]; // A simple -1 4 -1 box filter with a weight of 2. - if ((center * 4) - left - right) as u32 / 2 < blackPoint { + if ((center as i64 * 4) - left as i64 - right as i64) / 2 < blackPoint as i64 { row.set(x); } left = center; diff --git a/src/oned/CodaBarReader.java b/src/oned/CodaBarReader.java deleted file mode 100644 index 404bc71..0000000 --- a/src/oned/CodaBarReader.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright 2008 ZXing authors - * - * 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. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.DecodeHintType; -import com.google.zxing.NotFoundException; -import com.google.zxing.RXingResult; -import com.google.zxing.RXingResultMetadataType; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.common.BitArray; - -import java.util.Arrays; -import java.util.Map; - -/** - *

Decodes Codabar barcodes.

- * - * @author Bas Vijfwinkel - * @author David Walker - */ -public final class CodaBarReader extends OneDReader { - - // These values are critical for determining how permissive the decoding - // will be. All stripe sizes must be within the window these define, as - // compared to the average stripe size. - private static final float MAX_ACCEPTABLE = 2.0f; - private static final float PADDING = 1.5f; - - private static final String ALPHABET_STRING = "0123456789-$:/.+ABCD"; - static final char[] ALPHABET = ALPHABET_STRING.toCharArray(); - - /** - * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of - * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. - */ - static final int[] CHARACTER_ENCODINGS = { - 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 - 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD - }; - - // minimal number of characters that should be present (including start and stop characters) - // under normal circumstances this should be set to 3, but can be set higher - // as a last-ditch attempt to reduce false positives. - private static final int MIN_CHARACTER_LENGTH = 3; - - // official start and end patterns - private static final char[] STARTEND_ENCODING = {'A', 'B', 'C', 'D'}; - // some Codabar generator allow the Codabar string to be closed by every - // character. This will cause lots of false positives! - - // some industries use a checksum standard but this is not part of the original Codabar standard - // for more information see : http://www.mecsw.com/specs/codabar.html - - // Keep some instance variables to avoid reallocations - private final StringBuilder decodeRowRXingResult; - private int[] counters; - private int counterLength; - - public CodaBarReader() { - decodeRowRXingResult = new StringBuilder(20); - counters = new int[80]; - counterLength = 0; - } - - @Override - public RXingResult decodeRow(int rowNumber, BitArray row, Map hints) throws NotFoundException { - - Arrays.fill(counters, 0); - setCounters(row); - int startOffset = findStartPattern(); - int nextStart = startOffset; - - decodeRowRXingResult.setLength(0); - do { - int charOffset = toNarrowWidePattern(nextStart); - if (charOffset == -1) { - throw NotFoundException.getNotFoundInstance(); - } - // Hack: We store the position in the alphabet table into a - // StringBuilder, so that we can access the decoded patterns in - // validatePattern. We'll translate to the actual characters later. - decodeRowRXingResult.append((char) charOffset); - nextStart += 8; - // Stop as soon as we see the end character. - if (decodeRowRXingResult.length() > 1 && - arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) { - break; - } - } while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available - - // Look for whitespace after pattern: - int trailingWhitespace = counters[nextStart - 1]; - int lastPatternSize = 0; - for (int i = -8; i < -1; i++) { - lastPatternSize += counters[nextStart + i]; - } - - // We need to see whitespace equal to 50% of the last pattern size, - // otherwise this is probably a false positive. The exception is if we are - // at the end of the row. (I.e. the barcode barely fits.) - if (nextStart < counterLength && trailingWhitespace < lastPatternSize / 2) { - throw NotFoundException.getNotFoundInstance(); - } - - validatePattern(startOffset); - - // Translate character table offsets to actual characters. - for (int i = 0; i < decodeRowRXingResult.length(); i++) { - decodeRowRXingResult.setCharAt(i, ALPHABET[decodeRowRXingResult.charAt(i)]); - } - // Ensure a valid start and end character - char startchar = decodeRowRXingResult.charAt(0); - if (!arrayContains(STARTEND_ENCODING, startchar)) { - throw NotFoundException.getNotFoundInstance(); - } - char endchar = decodeRowRXingResult.charAt(decodeRowRXingResult.length() - 1); - if (!arrayContains(STARTEND_ENCODING, endchar)) { - throw NotFoundException.getNotFoundInstance(); - } - - // remove stop/start characters character and check if a long enough string is contained - if (decodeRowRXingResult.length() <= MIN_CHARACTER_LENGTH) { - // Almost surely a false positive ( start + stop + at least 1 character) - throw NotFoundException.getNotFoundInstance(); - } - - if (hints == null || !hints.containsKey(DecodeHintType.RETURN_CODABAR_START_END)) { - decodeRowRXingResult.deleteCharAt(decodeRowRXingResult.length() - 1); - decodeRowRXingResult.deleteCharAt(0); - } - - int runningCount = 0; - for (int i = 0; i < startOffset; i++) { - runningCount += counters[i]; - } - float left = runningCount; - for (int i = startOffset; i < nextStart - 1; i++) { - runningCount += counters[i]; - } - float right = runningCount; - - RXingResult result = new RXingResult( - decodeRowRXingResult.toString(), - null, - new RXingResultPoint[]{ - new RXingResultPoint(left, rowNumber), - new RXingResultPoint(right, rowNumber)}, - BarcodeFormat.CODABAR); - result.putMetadata(RXingResultMetadataType.SYMBOLOGY_IDENTIFIER, "]F0"); - return result; - } - - private void validatePattern(int start) throws NotFoundException { - // First, sum up the total size of our four categories of stripe sizes; - int[] sizes = {0, 0, 0, 0}; - int[] counts = {0, 0, 0, 0}; - int end = decodeRowRXingResult.length() - 1; - - // We break out of this loop in the middle, in order to handle - // inter-character spaces properly. - int pos = start; - for (int i = 0; i <= end; i++) { - int pattern = CHARACTER_ENCODINGS[decodeRowRXingResult.charAt(i)]; - for (int j = 6; j >= 0; j--) { - // Even j = bars, while odd j = spaces. Categories 2 and 3 are for - // long stripes, while 0 and 1 are for short stripes. - int category = (j & 1) + (pattern & 1) * 2; - sizes[category] += counters[pos + j]; - counts[category]++; - pattern >>= 1; - } - // We ignore the inter-character space - it could be of any size. - pos += 8; - } - - // Calculate our allowable size thresholds using fixed-point math. - float[] maxes = new float[4]; - float[] mins = new float[4]; - // Define the threshold of acceptability to be the midpoint between the - // average small stripe and the average large stripe. No stripe lengths - // should be on the "wrong" side of that line. - for (int i = 0; i < 2; i++) { - mins[i] = 0.0f; // Accept arbitrarily small "short" stripes. - mins[i + 2] = ((float) sizes[i] / counts[i] + (float) sizes[i + 2] / counts[i + 2]) / 2.0f; - maxes[i] = mins[i + 2]; - maxes[i + 2] = (sizes[i + 2] * MAX_ACCEPTABLE + PADDING) / counts[i + 2]; - } - - // Now verify that all of the stripes are within the thresholds. - pos = start; - for (int i = 0; i <= end; i++) { - int pattern = CHARACTER_ENCODINGS[decodeRowRXingResult.charAt(i)]; - for (int j = 6; j >= 0; j--) { - // Even j = bars, while odd j = spaces. Categories 2 and 3 are for - // long stripes, while 0 and 1 are for short stripes. - int category = (j & 1) + (pattern & 1) * 2; - int size = counters[pos + j]; - if (size < mins[category] || size > maxes[category]) { - throw NotFoundException.getNotFoundInstance(); - } - pattern >>= 1; - } - pos += 8; - } - } - - /** - * Records the size of all runs of white and black pixels, starting with white. - * This is just like recordPattern, except it records all the counters, and - * uses our builtin "counters" member for storage. - * @param row row to count from - */ - private void setCounters(BitArray row) throws NotFoundException { - counterLength = 0; - // Start from the first white bit. - int i = row.getNextUnset(0); - int end = row.getSize(); - if (i >= end) { - throw NotFoundException.getNotFoundInstance(); - } - boolean isWhite = true; - int count = 0; - while (i < end) { - if (row.get(i) != isWhite) { - count++; - } else { - counterAppend(count); - count = 1; - isWhite = !isWhite; - } - i++; - } - counterAppend(count); - } - - private void counterAppend(int e) { - counters[counterLength] = e; - counterLength++; - if (counterLength >= counters.length) { - int[] temp = new int[counterLength * 2]; - System.arraycopy(counters, 0, temp, 0, counterLength); - counters = temp; - } - } - - private int findStartPattern() throws NotFoundException { - for (int i = 1; i < counterLength; i += 2) { - int charOffset = toNarrowWidePattern(i); - if (charOffset != -1 && arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) { - // Look for whitespace before start pattern, >= 50% of width of start pattern - // We make an exception if the whitespace is the first element. - int patternSize = 0; - for (int j = i; j < i + 7; j++) { - patternSize += counters[j]; - } - if (i == 1 || counters[i - 1] >= patternSize / 2) { - return i; - } - } - } - throw NotFoundException.getNotFoundInstance(); - } - - static boolean arrayContains(char[] array, char key) { - if (array != null) { - for (char c : array) { - if (c == key) { - return true; - } - } - } - return false; - } - - // Assumes that counters[position] is a bar. - private int toNarrowWidePattern(int position) { - int end = position + 7; - if (end >= counterLength) { - return -1; - } - - int[] theCounters = counters; - - int maxBar = 0; - int minBar = Integer.MAX_VALUE; - for (int j = position; j < end; j += 2) { - int currentCounter = theCounters[j]; - if (currentCounter < minBar) { - minBar = currentCounter; - } - if (currentCounter > maxBar) { - maxBar = currentCounter; - } - } - int thresholdBar = (minBar + maxBar) / 2; - - int maxSpace = 0; - int minSpace = Integer.MAX_VALUE; - for (int j = position + 1; j < end; j += 2) { - int currentCounter = theCounters[j]; - if (currentCounter < minSpace) { - minSpace = currentCounter; - } - if (currentCounter > maxSpace) { - maxSpace = currentCounter; - } - } - int thresholdSpace = (minSpace + maxSpace) / 2; - - int bitmask = 1 << 7; - int pattern = 0; - for (int i = 0; i < 7; i++) { - int threshold = (i & 1) == 0 ? thresholdBar : thresholdSpace; - bitmask >>= 1; - if (theCounters[position + i] > threshold) { - pattern |= bitmask; - } - } - - for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) { - if (CHARACTER_ENCODINGS[i] == pattern) { - return i; - } - } - return -1; - } - -} diff --git a/src/oned/EANManufacturerOrgSupport.java b/src/oned/EANManufacturerOrgSupport.java deleted file mode 100644 index 805dbd8..0000000 --- a/src/oned/EANManufacturerOrgSupport.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2010 ZXing authors - * - * 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. - */ - -package com.google.zxing.oned; - -import java.util.ArrayList; -import java.util.List; - -/** - * Records EAN prefix to GS1 Member Organization, where the member organization - * correlates strongly with a country. This is an imperfect means of identifying - * a country of origin by EAN-13 barcode value. See - * - * http://en.wikipedia.org/wiki/List_of_GS1_country_codes. - * - * @author Sean Owen - */ -final class EANManufacturerOrgSupport { - - private final List ranges = new ArrayList<>(); - private final List countryIdentifiers = new ArrayList<>(); - - String lookupCountryIdentifier(String productCode) { - initIfNeeded(); - int prefix = Integer.parseInt(productCode.substring(0, 3)); - int max = ranges.size(); - for (int i = 0; i < max; i++) { - int[] range = ranges.get(i); - int start = range[0]; - if (prefix < start) { - return null; - } - int end = range.length == 1 ? start : range[1]; - if (prefix <= end) { - return countryIdentifiers.get(i); - } - } - return null; - } - - private void add(int[] range, String id) { - ranges.add(range); - countryIdentifiers.add(id); - } - - private synchronized void initIfNeeded() { - if (!ranges.isEmpty()) { - return; - } - add(new int[] {0,19}, "US/CA"); - add(new int[] {30,39}, "US"); - add(new int[] {60,139}, "US/CA"); - add(new int[] {300,379}, "FR"); - add(new int[] {380}, "BG"); - add(new int[] {383}, "SI"); - add(new int[] {385}, "HR"); - add(new int[] {387}, "BA"); - add(new int[] {400,440}, "DE"); - add(new int[] {450,459}, "JP"); - add(new int[] {460,469}, "RU"); - add(new int[] {471}, "TW"); - add(new int[] {474}, "EE"); - add(new int[] {475}, "LV"); - add(new int[] {476}, "AZ"); - add(new int[] {477}, "LT"); - add(new int[] {478}, "UZ"); - add(new int[] {479}, "LK"); - add(new int[] {480}, "PH"); - add(new int[] {481}, "BY"); - add(new int[] {482}, "UA"); - add(new int[] {484}, "MD"); - add(new int[] {485}, "AM"); - add(new int[] {486}, "GE"); - add(new int[] {487}, "KZ"); - add(new int[] {489}, "HK"); - add(new int[] {490,499}, "JP"); - add(new int[] {500,509}, "GB"); - add(new int[] {520}, "GR"); - add(new int[] {528}, "LB"); - add(new int[] {529}, "CY"); - add(new int[] {531}, "MK"); - add(new int[] {535}, "MT"); - add(new int[] {539}, "IE"); - add(new int[] {540,549}, "BE/LU"); - add(new int[] {560}, "PT"); - add(new int[] {569}, "IS"); - add(new int[] {570,579}, "DK"); - add(new int[] {590}, "PL"); - add(new int[] {594}, "RO"); - add(new int[] {599}, "HU"); - add(new int[] {600,601}, "ZA"); - add(new int[] {603}, "GH"); - add(new int[] {608}, "BH"); - add(new int[] {609}, "MU"); - add(new int[] {611}, "MA"); - add(new int[] {613}, "DZ"); - add(new int[] {616}, "KE"); - add(new int[] {618}, "CI"); - add(new int[] {619}, "TN"); - add(new int[] {621}, "SY"); - add(new int[] {622}, "EG"); - add(new int[] {624}, "LY"); - add(new int[] {625}, "JO"); - add(new int[] {626}, "IR"); - add(new int[] {627}, "KW"); - add(new int[] {628}, "SA"); - add(new int[] {629}, "AE"); - add(new int[] {640,649}, "FI"); - add(new int[] {690,695}, "CN"); - add(new int[] {700,709}, "NO"); - add(new int[] {729}, "IL"); - add(new int[] {730,739}, "SE"); - add(new int[] {740}, "GT"); - add(new int[] {741}, "SV"); - add(new int[] {742}, "HN"); - add(new int[] {743}, "NI"); - add(new int[] {744}, "CR"); - add(new int[] {745}, "PA"); - add(new int[] {746}, "DO"); - add(new int[] {750}, "MX"); - add(new int[] {754,755}, "CA"); - add(new int[] {759}, "VE"); - add(new int[] {760,769}, "CH"); - add(new int[] {770}, "CO"); - add(new int[] {773}, "UY"); - add(new int[] {775}, "PE"); - add(new int[] {777}, "BO"); - add(new int[] {779}, "AR"); - add(new int[] {780}, "CL"); - add(new int[] {784}, "PY"); - add(new int[] {785}, "PE"); - add(new int[] {786}, "EC"); - add(new int[] {789,790}, "BR"); - add(new int[] {800,839}, "IT"); - add(new int[] {840,849}, "ES"); - add(new int[] {850}, "CU"); - add(new int[] {858}, "SK"); - add(new int[] {859}, "CZ"); - add(new int[] {860}, "YU"); - add(new int[] {865}, "MN"); - add(new int[] {867}, "KP"); - add(new int[] {868,869}, "TR"); - add(new int[] {870,879}, "NL"); - add(new int[] {880}, "KR"); - add(new int[] {885}, "TH"); - add(new int[] {888}, "SG"); - add(new int[] {890}, "IN"); - add(new int[] {893}, "VN"); - add(new int[] {896}, "PK"); - add(new int[] {899}, "ID"); - add(new int[] {900,919}, "AT"); - add(new int[] {930,939}, "AU"); - add(new int[] {940,949}, "AZ"); - add(new int[] {955}, "MY"); - add(new int[] {958}, "MO"); - } - -} diff --git a/src/oned/OneDReader.java b/src/oned/OneDReader.java deleted file mode 100644 index 1f68102..0000000 --- a/src/oned/OneDReader.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright 2008 ZXing authors - * - * 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. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BinaryBitmap; -import com.google.zxing.ChecksumException; -import com.google.zxing.DecodeHintType; -import com.google.zxing.FormatException; -import com.google.zxing.NotFoundException; -import com.google.zxing.Reader; -import com.google.zxing.ReaderException; -import com.google.zxing.RXingResult; -import com.google.zxing.RXingResultMetadataType; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.common.BitArray; - -import java.util.Arrays; -import java.util.EnumMap; -import java.util.Map; - -/** - * Encapsulates functionality and implementation that is common to all families - * of one-dimensional barcodes. - * - * @author dswitkin@google.com (Daniel Switkin) - * @author Sean Owen - */ -public abstract class OneDReader implements Reader { - - @Override - public RXingResult decode(BinaryBitmap image) throws NotFoundException, FormatException { - return decode(image, null); - } - - // Note that we don't try rotation without the try harder flag, even if rotation was supported. - @Override - public RXingResult decode(BinaryBitmap image, - Map hints) throws NotFoundException, FormatException { - try { - return doDecode(image, hints); - } catch (NotFoundException nfe) { - boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); - if (tryHarder && image.isRotateSupported()) { - BinaryBitmap rotatedImage = image.rotateCounterClockwise(); - RXingResult result = doDecode(rotatedImage, hints); - // Record that we found it rotated 90 degrees CCW / 270 degrees CW - Map metadata = result.getRXingResultMetadata(); - int orientation = 270; - if (metadata != null && metadata.containsKey(RXingResultMetadataType.ORIENTATION)) { - // But if we found it reversed in doDecode(), add in that result here: - orientation = (orientation + - (Integer) metadata.get(RXingResultMetadataType.ORIENTATION)) % 360; - } - result.putMetadata(RXingResultMetadataType.ORIENTATION, orientation); - // Update result points - RXingResultPoint[] points = result.getRXingResultPoints(); - if (points != null) { - int height = rotatedImage.getHeight(); - for (int i = 0; i < points.length; i++) { - points[i] = new RXingResultPoint(height - points[i].getY() - 1, points[i].getX()); - } - } - return result; - } else { - throw nfe; - } - } - } - - @Override - public void reset() { - // do nothing - } - - /** - * We're going to examine rows from the middle outward, searching alternately above and below the - * middle, and farther out each time. rowStep is the number of rows between each successive - * attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then - * middle + rowStep, then middle - (2 * rowStep), etc. - * rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily - * decided that moving up and down by about 1/16 of the image is pretty good; we try more of the - * image if "trying harder". - * - * @param image The image to decode - * @param hints Any hints that were requested - * @return The contents of the decoded barcode - * @throws NotFoundException Any spontaneous errors which occur - */ - private RXingResult doDecode(BinaryBitmap image, - Map hints) throws NotFoundException { - int width = image.getWidth(); - int height = image.getHeight(); - BitArray row = new BitArray(width); - - boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); - int rowStep = Math.max(1, height >> (tryHarder ? 8 : 5)); - int maxLines; - if (tryHarder) { - maxLines = height; // Look at the whole image, not just the center - } else { - maxLines = 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image - } - - int middle = height / 2; - for (int x = 0; x < maxLines; x++) { - - // Scanning from the middle out. Determine which row we're looking at next: - int rowStepsAboveOrBelow = (x + 1) / 2; - boolean isAbove = (x & 0x01) == 0; // i.e. is x even? - int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow); - if (rowNumber < 0 || rowNumber >= height) { - // Oops, if we run off the top or bottom, stop - break; - } - - // Estimate black point for this row and load it: - try { - row = image.getBlackRow(rowNumber, row); - } catch (NotFoundException ignored) { - continue; - } - - // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to - // handle decoding upside down barcodes. - for (int attempt = 0; attempt < 2; attempt++) { - if (attempt == 1) { // trying again? - row.reverse(); // reverse the row and continue - // This means we will only ever draw result points *once* in the life of this method - // since we want to avoid drawing the wrong points after flipping the row, and, - // don't want to clutter with noise from every single row scan -- just the scans - // that start on the center line. - if (hints != null && hints.containsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) { - Map newHints = new EnumMap<>(DecodeHintType.class); - newHints.putAll(hints); - newHints.remove(DecodeHintType.NEED_RESULT_POINT_CALLBACK); - hints = newHints; - } - } - try { - // Look for a barcode - RXingResult result = decodeRow(rowNumber, row, hints); - // We found our barcode - if (attempt == 1) { - // But it was upside down, so note that - result.putMetadata(RXingResultMetadataType.ORIENTATION, 180); - // And remember to flip the result points horizontally. - RXingResultPoint[] points = result.getRXingResultPoints(); - if (points != null) { - points[0] = new RXingResultPoint(width - points[0].getX() - 1, points[0].getY()); - points[1] = new RXingResultPoint(width - points[1].getX() - 1, points[1].getY()); - } - } - return result; - } catch (ReaderException re) { - // continue -- just couldn't decode this row - } - } - } - - throw NotFoundException.getNotFoundInstance(); - } - - /** - * Records the size of successive runs of white and black pixels in a row, starting at a given point. - * The values are recorded in the given array, and the number of runs recorded is equal to the size - * of the array. If the row starts on a white pixel at the given start point, then the first count - * recorded is the run of white pixels starting from that point; likewise it is the count of a run - * of black pixels if the row begin on a black pixels at that point. - * - * @param row row to count from - * @param start offset into row to start at - * @param counters array into which to record counts - * @throws NotFoundException if counters cannot be filled entirely from row before running out - * of pixels - */ - protected static void recordPattern(BitArray row, - int start, - int[] counters) throws NotFoundException { - int numCounters = counters.length; - Arrays.fill(counters, 0, numCounters, 0); - int end = row.getSize(); - if (start >= end) { - throw NotFoundException.getNotFoundInstance(); - } - boolean isWhite = !row.get(start); - int counterPosition = 0; - int i = start; - while (i < end) { - if (row.get(i) != isWhite) { - counters[counterPosition]++; - } else { - if (++counterPosition == numCounters) { - break; - } else { - counters[counterPosition] = 1; - isWhite = !isWhite; - } - } - i++; - } - // If we read fully the last section of pixels and filled up our counters -- or filled - // the last counter but ran off the side of the image, OK. Otherwise, a problem. - if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && i == end))) { - throw NotFoundException.getNotFoundInstance(); - } - } - - protected static void recordPatternInReverse(BitArray row, int start, int[] counters) - throws NotFoundException { - // This could be more efficient I guess - int numTransitionsLeft = counters.length; - boolean last = row.get(start); - while (start > 0 && numTransitionsLeft >= 0) { - if (row.get(--start) != last) { - numTransitionsLeft--; - last = !last; - } - } - if (numTransitionsLeft >= 0) { - throw NotFoundException.getNotFoundInstance(); - } - recordPattern(row, start + 1, counters); - } - - /** - * Determines how closely a set of observed counts of runs of black/white values matches a given - * target pattern. This is reported as the ratio of the total variance from the expected pattern - * proportions across all pattern elements, to the length of the pattern. - * - * @param counters observed counters - * @param pattern expected pattern - * @param maxIndividualVariance The most any counter can differ before we give up - * @return ratio of total variance between counters and pattern compared to total pattern size - */ - protected static float patternMatchVariance(int[] counters, - int[] pattern, - float maxIndividualVariance) { - int numCounters = counters.length; - int total = 0; - int patternLength = 0; - for (int i = 0; i < numCounters; i++) { - total += counters[i]; - patternLength += pattern[i]; - } - if (total < patternLength) { - // If we don't even have one pixel per unit of bar width, assume this is too small - // to reliably match, so fail: - return Float.POSITIVE_INFINITY; - } - - float unitBarWidth = (float) total / patternLength; - maxIndividualVariance *= unitBarWidth; - - float totalVariance = 0.0f; - for (int x = 0; x < numCounters; x++) { - int counter = counters[x]; - float scaledPattern = pattern[x] * unitBarWidth; - float variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter; - if (variance > maxIndividualVariance) { - return Float.POSITIVE_INFINITY; - } - totalVariance += variance; - } - return totalVariance / total; - } - - /** - *

Attempts to decode a one-dimensional barcode format given a single row of - * an image.

- * - * @param rowNumber row number from top of the row - * @param row the black/white pixel data of the row - * @param hints decode hints - * @return {@link RXingResult} containing encoded string and start/end of barcode - * @throws NotFoundException if no potential barcode is found - * @throws ChecksumException if a potential barcode is found but does not pass its checksum - * @throws FormatException if a potential barcode is found but format is invalid - */ - public abstract RXingResult decodeRow(int rowNumber, BitArray row, Map hints) - throws NotFoundException, ChecksumException, FormatException; - -} diff --git a/src/oned/coda_bar_reader.rs b/src/oned/coda_bar_reader.rs new file mode 100644 index 0000000..6ee2450 --- /dev/null +++ b/src/oned/coda_bar_reader.rs @@ -0,0 +1,427 @@ +/* + * Copyright 2008 ZXing authors + * + * 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. + */ + +use one_d_reader_derive::OneDReader; + +use crate::common::BitArray; +use crate::BarcodeFormat; +use crate::Exceptions; +use crate::RXingResult; + +use super::OneDReader; + +/** + *

Decodes Codabar barcodes.

+ * + * @author Bas Vijfwinkel + * @author David Walker + */ +#[derive(OneDReader)] +pub struct CodaBarReader { + // Keep some instance variables to avoid reallocations + decodeRowRXingResult: String, + counters: Vec, + counterLength: usize, +} + +impl Default for CodaBarReader { + fn default() -> Self { + Self { + decodeRowRXingResult: Default::default(), + counters: Default::default(), + counterLength: Default::default(), + } + } +} + +impl OneDReader for CodaBarReader { + fn decodeRow( + &mut self, + rowNumber: u32, + row: &crate::common::BitArray, + hints: &crate::DecodingHintDictionary, + ) -> Result { + self.counters.fill(0); + // Arrays.fill(counters, 0); + self.setCounters(row)?; + let startOffset = self.findStartPattern()? as usize; + let mut nextStart = startOffset; + + self.decodeRowRXingResult.clear(); + loop { + let charOffset = self.toNarrowWidePattern(nextStart); + if charOffset == -1 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + // Hack: We store the position in the alphabet table into a + // StringBuilder, so that we can access the decoded patterns in + // validatePattern. We'll translate to the actual characters later. + self.decodeRowRXingResult + .push(char::from_u32(charOffset as u32).unwrap()); + nextStart += 8; + // Stop as soon as we see the end character. + if self.decodeRowRXingResult.chars().count() > 1 + && Self::arrayContains( + &Self::STARTEND_ENCODING, + Self::ALPHABET[charOffset as usize], + ) + { + break; + } + if !(nextStart < self.counterLength) { + break; + } // no fixed end pattern so keep on reading while data is available + } //while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available + + // Look for whitespace after pattern: + let trailingWhitespace = self.counters[nextStart - 1]; + let mut lastPatternSize = 0; + for i in -8isize..-1isize { + // for (int i = -8; i < -1; i++) { + lastPatternSize += self.counters[(nextStart as isize + i) as usize]; + } + + // We need to see whitespace equal to 50% of the last pattern size, + // otherwise this is probably a false positive. The exception is if we are + // at the end of the row. (I.e. the barcode barely fits.) + if nextStart < self.counterLength && trailingWhitespace < lastPatternSize / 2 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + self.validatePattern(startOffset)?; + + // Translate character table offsets to actual characters. + for i in 0..self.decodeRowRXingResult.chars().count() { + // for (int i = 0; i < decodeRowRXingResult.length(); i++) { + self.decodeRowRXingResult.replace_range( + i..=i, + &Self::ALPHABET[self.decodeRowRXingResult.chars().nth(i).unwrap() as usize] + .to_string(), + ); + // self.decodeRowRXingResult.setCharAt(i, Self::ALPHABET[self.decodeRowRXingResult.chars().nth(i).unwrap() as usize]); + } + // Ensure a valid start and end character + let startchar = self.decodeRowRXingResult.chars().nth(0).unwrap(); + if !Self::arrayContains(&Self::STARTEND_ENCODING, startchar) { + return Err(Exceptions::NotFoundException("".to_owned())); + } + let endchar = self + .decodeRowRXingResult + .chars() + .nth(self.decodeRowRXingResult.chars().count() - 1) + .unwrap(); + if !Self::arrayContains(&Self::STARTEND_ENCODING, endchar) { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + // remove stop/start characters character and check if a long enough string is contained + if (self.decodeRowRXingResult.chars().count()) <= Self::MIN_CHARACTER_LENGTH as usize { + // Almost surely a false positive ( start + stop + at least 1 character) + return Err(Exceptions::NotFoundException("".to_owned())); + } + + if !hints.contains_key(&DecodeHintType::RETURN_CODABAR_START_END) { + // self.decodeRowRXingResult.deleteCharAt(self.decodeRowRXingResult.chars().count() - 1); + // self.decodeRowRXingResult.deleteCharAt(0); + self.decodeRowRXingResult = + self.decodeRowRXingResult[1..self.decodeRowRXingResult.len()-1].to_owned(); + } + + let mut runningCount = 0; + for i in 0..startOffset { + // for (int i = 0; i < startOffset; i++) { + runningCount += self.counters[i]; + } + let left: f32 = runningCount as f32; + for i in startOffset..(nextStart - 1) { + // for (int i = startOffset; i < nextStart - 1; i++) { + runningCount += self.counters[i]; + } + let right: f32 = runningCount as f32; + + let mut result = RXingResult::new( + &self.decodeRowRXingResult, + Vec::new(), + vec![ + RXingResultPoint::new(left, rowNumber as f32), + RXingResultPoint::new(right, rowNumber as f32), + ], + BarcodeFormat::CODABAR, + ); + + result.putMetadata( + RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, + RXingResultMetadataValue::SymbologyIdentifier("]F0".to_owned()), + ); + + Ok(result) + } +} +impl CodaBarReader { + // These values are critical for determining how permissive the decoding + // will be. All stripe sizes must be within the window these define, as + // compared to the average stripe size. + const MAX_ACCEPTABLE: f32 = 2.0; + const PADDING: f32 = 1.5; + + // const ALPHABET_STRING : &str= "0123456789-$:/.+ABCD"; + const ALPHABET: [char; 20] = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '$', ':', '/', '.', '+', 'A', 'B', + 'C', 'D', + ]; + + /** + * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of + * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. + */ + const CHARACTER_ENCODINGS: [u32; 20] = [ + 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 + 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD + ]; + + // minimal number of characters that should be present (including start and stop characters) + // under normal circumstances this should be set to 3, but can be set higher + // as a last-ditch attempt to reduce false positives. + const MIN_CHARACTER_LENGTH: u32 = 3; + + // official start and end patterns + const STARTEND_ENCODING: [char; 4] = ['A', 'B', 'C', 'D']; + // some Codabar generator allow the Codabar string to be closed by every + // character. This will cause lots of false positives! + + // some industries use a checksum standard but this is not part of the original Codabar standard + // for more information see : http://www.mecsw.com/specs/codabar.html + + pub fn new() -> Self { + Self { + decodeRowRXingResult: String::with_capacity(20), + counters: vec![0; 80], //Vec::with_capacity(80), + counterLength: 0, + } + } + + fn validatePattern(&self, start: usize) -> Result<(), Exceptions> { + // First, sum up the total size of our four categories of stripe sizes; + let mut sizes = [0, 0, 0, 0]; + let mut counts = [0, 0, 0, 0]; + let end = self.decodeRowRXingResult.chars().count() - 1; + + // We break out of this loop in the middle, in order to handle + // inter-character spaces properly. + let mut pos = start; + for i in 0..=end { + // for (int i = 0; i <= end; i++) { + let mut pattern = Self::CHARACTER_ENCODINGS + [self.decodeRowRXingResult.chars().nth(i).unwrap() as usize]; + for j in (0_usize..=6).rev() { + // for (int j = 6; j >= 0; j--) { + // Even j = bars, while odd j = spaces. Categories 2 and 3 are for + // long stripes, while 0 and 1 are for short stripes. + let category = (j & 1) + ((pattern as usize) & 1) * 2; + sizes[category] += self.counters[(pos + j) as usize]; + counts[category] += 1; + pattern >>= 1; + } + // We ignore the inter-character space - it could be of any size. + pos += 8; + } + + // Calculate our allowable size thresholds using fixed-point math. + let mut maxes = [0.0; 4]; //new float[4]; + let mut mins = [0.0; 4]; //new float[4]; + // Define the threshold of acceptability to be the midpoint between the + // average small stripe and the average large stripe. No stripe lengths + // should be on the "wrong" side of that line. + for i in 0..2 { + // for (int i = 0; i < 2; i++) { + mins[i] = 0.0; // Accept arbitrarily small "short" stripes. + mins[i + 2] = ((sizes[i] as f32) / (counts[i] as f32) + + (sizes[i + 2] as f32) / (counts[i + 2] as f32)) + / 2.0; + maxes[i] = mins[i + 2]; + maxes[i + 2] = ((sizes[i + 2] as f32) * Self::MAX_ACCEPTABLE + Self::PADDING) + / (counts[i + 2] as f32); + } + + // Now verify that all of the stripes are within the thresholds. + pos = start; + for i in 0..=end { + // for (int i = 0; i <= end; i++) { + let mut pattern = Self::CHARACTER_ENCODINGS + [self.decodeRowRXingResult.chars().nth(i).unwrap() as usize]; + for j in (0usize..=6).rev() { + // for (int j = 6; j >= 0; j--) { + // Even j = bars, while odd j = spaces. Categories 2 and 3 are for + // long stripes, while 0 and 1 are for short stripes. + let category = (j & 1) + ((pattern as usize) & 1) * 2; + let size = self.counters[(pos + j)]; + if (size as f32) < mins[category] || (size as f32) > maxes[category] { + return Err(Exceptions::NotFoundException("".to_owned())); + } + pattern >>= 1; + } + pos += 8; + } + Ok(()) + } + + /** + * Records the size of all runs of white and black pixels, starting with white. + * This is just like recordPattern, except it records all the counters, and + * uses our builtin "counters" member for storage. + * @param row row to count from + */ + fn setCounters(&mut self, row: &BitArray) -> Result<(), Exceptions> { + self.counterLength = 0; + // Start from the first white bit. + let mut i = row.getNextUnset(0); + let end = row.getSize(); + if i >= end { + return Err(Exceptions::NotFoundException("".to_owned())); + } + let mut isWhite = true; + let mut count = 0; + while i < end { + if row.get(i) != isWhite { + count += 1; + } else { + self.counterAppend(count); + count = 1; + isWhite = !isWhite; + } + i += 1; + } + self.counterAppend(count); + Ok(()) + } + + fn counterAppend(&mut self, e: u32) { + self.counters[self.counterLength] = e; + self.counterLength += 1; + if self.counterLength >= self.counters.len() { + let mut temp = vec![0; self.counterLength * 2]; //new int[counterLength * 2]; + temp[0..self.counterLength].clone_from_slice(&self.counters[..]); + // System.arraycopy(counters, 0, temp, 0, counterLength); + self.counters = temp; + } + } + + fn findStartPattern(&mut self) -> Result { + let mut i = 1; + while i < self.counterLength { + // for (int i = 1; i < counterLength; i += 2) { + let charOffset = self.toNarrowWidePattern(i); + if charOffset != -1 + && Self::arrayContains( + &Self::STARTEND_ENCODING, + Self::ALPHABET[charOffset as usize], + ) + { + // Look for whitespace before start pattern, >= 50% of width of start pattern + // We make an exception if the whitespace is the first element. + let mut patternSize = 0; + for j in i..(i + 7) { + // for (int j = i; j < i + 7; j++) { + patternSize += self.counters[j]; + } + if i == 1 || self.counters[i - 1] >= patternSize / 2 { + return Ok(i as u32); + } + } + + i += 2; + } + Err(Exceptions::NotFoundException("".to_owned())) + } + + pub fn arrayContains(array: &[char], key: char) -> bool { + // if (array != null) { + for c in array { + if c == &key { + return true; + } + } + // } + return false; + } + + // Assumes that counters[position] is a bar. + fn toNarrowWidePattern(&mut self, position: usize) -> i32 { + let end = position + 7; + if end >= self.counterLength { + return -1; + } + + let theCounters = &self.counters; + + let mut maxBar = 0; + let mut minBar = u32::MAX; + let mut j = position; + while j < end { + // for (int j = position; j < end; j += 2) { + let currentCounter = theCounters[j]; + if currentCounter < minBar { + minBar = currentCounter; + } + if currentCounter > maxBar { + maxBar = currentCounter; + } + + j += 2; + } + let thresholdBar = (minBar + maxBar) / 2; + + let mut maxSpace = 0; + let mut minSpace = u32::MAX; + let mut j = position + 1; + while j < end { + // for (int j = position + 1; j < end; j += 2) { + let currentCounter = theCounters[j]; + if currentCounter < minSpace { + minSpace = currentCounter; + } + if currentCounter > maxSpace { + maxSpace = currentCounter; + } + + j += 2; + } + let thresholdSpace = (minSpace + maxSpace) / 2; + + let mut bitmask = 1 << 7; + let mut pattern = 0; + for i in 0..7 { + // for (int i = 0; i < 7; i++) { + let threshold = if (i & 1) == 0 { + thresholdBar + } else { + thresholdSpace + }; + bitmask >>= 1; + if theCounters[position + i] > threshold { + pattern |= bitmask; + } + } + + for i in 0..Self::CHARACTER_ENCODINGS.len() { + // for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) { + if Self::CHARACTER_ENCODINGS[i] == pattern { + return i as i32; + } + } + return -1; + } +} diff --git a/src/oned/ean_manufacturer_org_support.rs b/src/oned/ean_manufacturer_org_support.rs new file mode 100644 index 0000000..16388a3 --- /dev/null +++ b/src/oned/ean_manufacturer_org_support.rs @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * 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. + */ + +/** + * Records EAN prefix to GS1 Member Organization, where the member organization + * correlates strongly with a country. This is an imperfect means of identifying + * a country of origin by EAN-13 barcode value. See + * + * http://en.wikipedia.org/wiki/List_of_GS1_country_codes. + * + * @author Sean Owen + */ +pub struct EANManufacturerOrgSupport { + ranges: Vec>, //= new ArrayList<>(); + countryIdentifiers: Vec, // = new ArrayList<>(); +} + +impl Default for EANManufacturerOrgSupport { + fn default() -> Self { + Self { + ranges: Default::default(), + countryIdentifiers: Default::default(), + } + } +} + +impl EANManufacturerOrgSupport { + pub fn lookupCountryIdentifier(&mut self, productCode: &str) -> Option { + self.initIfNeeded(); + let prefix = productCode[0..3].parse::().expect("must parse prefix"); + // let prefix = Integer.parseInt(productCode.substring(0, 3)); + let max = self.ranges.len(); + for i in 0..max { + // for (int i = 0; i < max; i++) { + let range = self.ranges.get(i).expect("must have index i or fail"); + let start = range[0]; + if prefix < start { + return None; + } + let end = if range.len() == 1 { start } else { range[1] }; + if prefix <= end { + return Some( + self.countryIdentifiers + .get(i) + .expect("must have index i or fail") + .clone(), + ); + } + } + + None + } + + fn add(&mut self, range: Vec, id: String) { + self.ranges.push(range); + self.countryIdentifiers.push(id); + } + + fn initIfNeeded(&mut self) { + if !self.ranges.is_empty() { + return; + } + self.add(vec![0, 19], "US/CA".to_owned()); + self.add(vec![30, 39], "US".to_owned()); + self.add(vec![60, 139], "US/CA".to_owned()); + self.add(vec![300, 379], "FR".to_owned()); + self.add(vec![380], "BG".to_owned()); + self.add(vec![383], "SI".to_owned()); + self.add(vec![385], "HR".to_owned()); + self.add(vec![387], "BA".to_owned()); + self.add(vec![400, 440], "DE".to_owned()); + self.add(vec![450, 459], "JP".to_owned()); + self.add(vec![460, 469], "RU".to_owned()); + self.add(vec![471], "TW".to_owned()); + self.add(vec![474], "EE".to_owned()); + self.add(vec![475], "LV".to_owned()); + self.add(vec![476], "AZ".to_owned()); + self.add(vec![477], "LT".to_owned()); + self.add(vec![478], "UZ".to_owned()); + self.add(vec![479], "LK".to_owned()); + self.add(vec![480], "PH".to_owned()); + self.add(vec![481], "BY".to_owned()); + self.add(vec![482], "UA".to_owned()); + self.add(vec![484], "MD".to_owned()); + self.add(vec![485], "AM".to_owned()); + self.add(vec![486], "GE".to_owned()); + self.add(vec![487], "KZ".to_owned()); + self.add(vec![489], "HK".to_owned()); + self.add(vec![490, 499], "JP".to_owned()); + self.add(vec![500, 509], "GB".to_owned()); + self.add(vec![520], "GR".to_owned()); + self.add(vec![528], "LB".to_owned()); + self.add(vec![529], "CY".to_owned()); + self.add(vec![531], "MK".to_owned()); + self.add(vec![535], "MT".to_owned()); + self.add(vec![539], "IE".to_owned()); + self.add(vec![540, 549], "BE/LU".to_owned()); + self.add(vec![560], "PT".to_owned()); + self.add(vec![569], "IS".to_owned()); + self.add(vec![570, 579], "DK".to_owned()); + self.add(vec![590], "PL".to_owned()); + self.add(vec![594], "RO".to_owned()); + self.add(vec![599], "HU".to_owned()); + self.add(vec![600, 601], "ZA".to_owned()); + self.add(vec![603], "GH".to_owned()); + self.add(vec![608], "BH".to_owned()); + self.add(vec![609], "MU".to_owned()); + self.add(vec![611], "MA".to_owned()); + self.add(vec![613], "DZ".to_owned()); + self.add(vec![616], "KE".to_owned()); + self.add(vec![618], "CI".to_owned()); + self.add(vec![619], "TN".to_owned()); + self.add(vec![621], "SY".to_owned()); + self.add(vec![622], "EG".to_owned()); + self.add(vec![624], "LY".to_owned()); + self.add(vec![625], "JO".to_owned()); + self.add(vec![626], "IR".to_owned()); + self.add(vec![627], "KW".to_owned()); + self.add(vec![628], "SA".to_owned()); + self.add(vec![629], "AE".to_owned()); + self.add(vec![640, 649], "FI".to_owned()); + self.add(vec![690, 695], "CN".to_owned()); + self.add(vec![700, 709], "NO".to_owned()); + self.add(vec![729], "IL".to_owned()); + self.add(vec![730, 739], "SE".to_owned()); + self.add(vec![740], "GT".to_owned()); + self.add(vec![741], "SV".to_owned()); + self.add(vec![742], "HN".to_owned()); + self.add(vec![743], "NI".to_owned()); + self.add(vec![744], "CR".to_owned()); + self.add(vec![745], "PA".to_owned()); + self.add(vec![746], "DO".to_owned()); + self.add(vec![750], "MX".to_owned()); + self.add(vec![754, 755], "CA".to_owned()); + self.add(vec![759], "VE".to_owned()); + self.add(vec![760, 769], "CH".to_owned()); + self.add(vec![770], "CO".to_owned()); + self.add(vec![773], "UY".to_owned()); + self.add(vec![775], "PE".to_owned()); + self.add(vec![777], "BO".to_owned()); + self.add(vec![779], "AR".to_owned()); + self.add(vec![780], "CL".to_owned()); + self.add(vec![784], "PY".to_owned()); + self.add(vec![785], "PE".to_owned()); + self.add(vec![786], "EC".to_owned()); + self.add(vec![789, 790], "BR".to_owned()); + self.add(vec![800, 839], "IT".to_owned()); + self.add(vec![840, 849], "ES".to_owned()); + self.add(vec![850], "CU".to_owned()); + self.add(vec![858], "SK".to_owned()); + self.add(vec![859], "CZ".to_owned()); + self.add(vec![860], "YU".to_owned()); + self.add(vec![865], "MN".to_owned()); + self.add(vec![867], "KP".to_owned()); + self.add(vec![868, 869], "TR".to_owned()); + self.add(vec![870, 879], "NL".to_owned()); + self.add(vec![880], "KR".to_owned()); + self.add(vec![885], "TH".to_owned()); + self.add(vec![888], "SG".to_owned()); + self.add(vec![890], "IN".to_owned()); + self.add(vec![893], "VN".to_owned()); + self.add(vec![896], "PK".to_owned()); + self.add(vec![899], "ID".to_owned()); + self.add(vec![900, 919], "AT".to_owned()); + self.add(vec![930, 939], "AU".to_owned()); + self.add(vec![940, 949], "AZ".to_owned()); + self.add(vec![955], "MY".to_owned()); + self.add(vec![958], "MO".to_owned()); + } +} diff --git a/src/oned/mod.rs b/src/oned/mod.rs index aaa67c0..a91ecac 100644 --- a/src/oned/mod.rs +++ b/src/oned/mod.rs @@ -1 +1,10 @@ +mod one_d_reader; pub mod rss; + +pub use one_d_reader::*; + +mod ean_manufacturer_org_support; +pub use ean_manufacturer_org_support::*; + +mod coda_bar_reader; +pub use coda_bar_reader::*; diff --git a/src/oned/one_d_reader.rs b/src/oned/one_d_reader.rs new file mode 100644 index 0000000..3186ad8 --- /dev/null +++ b/src/oned/one_d_reader.rs @@ -0,0 +1,284 @@ +/* + * Copyright 2008 ZXing authors + * + * 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. + */ + +use crate::{ + common::BitArray, BinaryBitmap, DecodeHintType, DecodingHintDictionary, Exceptions, + RXingResult, RXingResultMetadataType, RXingResultMetadataValue, RXingResultPoint, Reader, + ResultPoint, +}; + +/** + * Encapsulates functionality and implementation that is common to all families + * of one-dimensional barcodes. + * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + */ +pub trait OneDReader: Reader { + /** + * We're going to examine rows from the middle outward, searching alternately above and below the + * middle, and farther out each time. rowStep is the number of rows between each successive + * attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then + * middle + rowStep, then middle - (2 * rowStep), etc. + * rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily + * decided that moving up and down by about 1/16 of the image is pretty good; we try more of the + * image if "trying harder". + * + * @param image The image to decode + * @param hints Any hints that were requested + * @return The contents of the decoded barcode + * @throws NotFoundException Any spontaneous errors which occur + */ + fn doDecode( + &mut self, + image: &BinaryBitmap, + hints: &DecodingHintDictionary, + ) -> Result { + let mut hints = hints.clone(); + let width = image.getWidth(); + let height = image.getHeight(); + let mut row = BitArray::with_size(width); + + let tryHarder = hints.contains_key(&DecodeHintType::TRY_HARDER); + let rowStep = 1.max(height >> (if tryHarder { 8 } else { 5 })); + let maxLines; + if tryHarder { + maxLines = height; // Look at the whole image, not just the center + } else { + maxLines = 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image + } + + let middle = height / 2; + for x in 0..maxLines { + // for (int x = 0; x < maxLines; x++) { + + // Scanning from the middle out. Determine which row we're looking at next: + let rowStepsAboveOrBelow = (x + 1) / 2; + let isAbove = (x & 0x01) == 0; // i.e. is x even? + let rowNumber: isize = middle as isize + + rowStep as isize + * (if isAbove { + rowStepsAboveOrBelow as isize + } else { + -(rowStepsAboveOrBelow as isize) + }); + if rowNumber < 0 || rowNumber >= height as isize { + // Oops, if we run off the top or bottom, stop + break; + } + + // Estimate black point for this row and load it: + let mut row = if let Ok(res) = image.getBlackRow(rowNumber as usize, &mut row) { + res + } else { + continue; + }; + // try { + // row = image.getBlackRow(rowNumber, row); + // } catch (NotFoundException ignored) { + // continue; + // } + + // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to + // handle decoding upside down barcodes. + for attempt in 0..2 { + // for (int attempt = 0; attempt < 2; attempt++) { + if attempt == 1 { + // trying again? + row.reverse(); // reverse the row and continue + // This means we will only ever draw result points *once* in the life of this method + // since we want to avoid drawing the wrong points after flipping the row, and, + // don't want to clutter with noise from every single row scan -- just the scans + // that start on the center line. + if hints.contains_key(&DecodeHintType::NEED_RESULT_POINT_CALLBACK) { + // let newHints = HashMap::new(); + // newHints.putAll(hints); + // newHints.remove(&DecodeHintType::NEED_RESULT_POINT_CALLBACK); + hints.remove(&DecodeHintType::NEED_RESULT_POINT_CALLBACK); + // hints = newHints; + } + } + //try { + // Look for a barcode + let Ok(mut result) = self.decodeRow(rowNumber as u32, &row, &hints) else { + continue + }; + // We found our barcode + if attempt == 1 { + // But it was upside down, so note that + result.putMetadata( + RXingResultMetadataType::ORIENTATION, + RXingResultMetadataValue::Orientation(180), + ); + // And remember to flip the result points horizontally. + let points = result.getRXingResultPointsMut(); + if !points.is_empty() && points.len() >= 2 { + points[0] = RXingResultPoint::new( + width as f32 - points[0].getX() - 1.0, + points[0].getY(), + ); + points[1] = RXingResultPoint::new( + width as f32 - points[1].getX() - 1.0, + points[1].getY(), + ); + } + } + return Ok(result); + // } catch (ReaderException re) { + // // continue -- just couldn't decode this row + // } + } + } + + return Err(Exceptions::NotFoundException("".to_owned())); + } + + /** + * Records the size of successive runs of white and black pixels in a row, starting at a given point. + * The values are recorded in the given array, and the number of runs recorded is equal to the size + * of the array. If the row starts on a white pixel at the given start point, then the first count + * recorded is the run of white pixels starting from that point; likewise it is the count of a run + * of black pixels if the row begin on a black pixels at that point. + * + * @param row row to count from + * @param start offset into row to start at + * @param counters array into which to record counts + * @throws NotFoundException if counters cannot be filled entirely from row before running out + * of pixels + */ + fn recordPattern(row: &BitArray, start: usize, counters: &mut [u32]) -> Result<(), Exceptions> { + let numCounters = counters.len(); + // Arrays.fill(counters, 0, numCounters, 0); + counters.fill(0); + let end = row.getSize(); + if start >= end { + return Err(Exceptions::NotFoundException("".to_owned())); + } + let mut isWhite = !row.get(start); + let mut counterPosition = 0; + let mut i = start; + while i < end { + if row.get(i) != isWhite { + counters[counterPosition] += 1; + } else { + counterPosition += 1; + if counterPosition == numCounters { + break; + } else { + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + i += 1; + } + // If we read fully the last section of pixels and filled up our counters -- or filled + // the last counter but ran off the side of the image, OK. Otherwise, a problem. + if !(counterPosition == numCounters || (counterPosition == numCounters - 1 && i == end)) { + return Err(Exceptions::NotFoundException("".to_owned())); + } + Ok(()) + } + + fn recordPatternInReverse( + row: &BitArray, + start: usize, + counters: &mut [u32], + ) -> Result<(), Exceptions> { + let mut start = start; + // This could be more efficient I guess + let mut numTransitionsLeft = counters.len() as isize; + let mut last = row.get(start); + while start > 0 && numTransitionsLeft >= 0 { + start -= 1; + if row.get(start) != last { + numTransitionsLeft -= 1; + last = !last; + } + } + if numTransitionsLeft >= 0 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + Self::recordPattern(row, start + 1, counters)?; + + Ok(()) + } + + /** + * Determines how closely a set of observed counts of runs of black/white values matches a given + * target pattern. This is reported as the ratio of the total variance from the expected pattern + * proportions across all pattern elements, to the length of the pattern. + * + * @param counters observed counters + * @param pattern expected pattern + * @param maxIndividualVariance The most any counter can differ before we give up + * @return ratio of total variance between counters and pattern compared to total pattern size + */ + fn patternMatchVariance(counters: &[u32], pattern: &[u32], maxIndividualVariance: f32) -> f32 { + let mut maxIndividualVariance = maxIndividualVariance; + let numCounters = counters.len(); + let mut total = 0.0; + let mut patternLength = 0; + for i in 0..numCounters { + // for (int i = 0; i < numCounters; i++) { + total += counters[i] as f32; + patternLength += pattern[i]; + } + if total < patternLength as f32 { + // If we don't even have one pixel per unit of bar width, assume this is too small + // to reliably match, so fail: + return f32::INFINITY; + } + + let unitBarWidth = total / patternLength as f32; + maxIndividualVariance *= unitBarWidth as f32; + + let mut totalVariance = 0.0; + for x in 0..numCounters { + // for (int x = 0; x < numCounters; x++) { + let counter = counters[x]; + let scaledPattern = (pattern[x] as f32) * unitBarWidth; + let variance = if (counter as f32) > scaledPattern { + counter as f32 - scaledPattern + } else { + scaledPattern - counter as f32 + }; + if variance > maxIndividualVariance { + return f32::INFINITY; + } + totalVariance += variance; + } + return totalVariance / total; + } + + /** + *

Attempts to decode a one-dimensional barcode format given a single row of + * an image.

+ * + * @param rowNumber row number from top of the row + * @param row the black/white pixel data of the row + * @param hints decode hints + * @return {@link RXingResult} containing encoded string and start/end of barcode + * @throws NotFoundException if no potential barcode is found + * @throws ChecksumException if a potential barcode is found but does not pass its checksum + * @throws FormatException if a potential barcode is found but format is invalid + */ + fn decodeRow( + &mut self, + rowNumber: u32, + row: &BitArray, + hints: &DecodingHintDictionary, + ) -> Result; +} diff --git a/src/rxing_result.rs b/src/rxing_result.rs index 3f27b7e..ccea0d4 100644 --- a/src/rxing_result.rs +++ b/src/rxing_result.rs @@ -134,6 +134,10 @@ impl RXingResult { return &self.resultPoints; } + pub fn getRXingResultPointsMut(&mut self) -> &mut Vec { + &mut self.resultPoints + } + /** * @return {@link BarcodeFormat} representing the format of the barcode that was decoded */ diff --git a/tests/CodabarBlackBox1TestCase.java b/tests/codabar_blackbox_1_test_case.rs similarity index 55% rename from tests/CodabarBlackBox1TestCase.java rename to tests/codabar_blackbox_1_test_case.rs index 62f3470..e632cbb 100644 --- a/tests/CodabarBlackBox1TestCase.java +++ b/tests/codabar_blackbox_1_test_case.rs @@ -14,21 +14,23 @@ * limitations under the License. */ -package com.google.zxing.oned; +use rxing::{BarcodeFormat, MultiFormatReader, oned::{OneDReader, CodaBarReader}}; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; +mod common; /** * @author Sean Owen */ -public final class CodabarBlackBox1TestCase extends AbstractBlackBoxTestCase { - - public CodabarBlackBox1TestCase() { - super("src/test/resources/blackbox/codabar-1", new MultiFormatReader(), BarcodeFormat.CODABAR); - addTest(11, 11, 0.0f); - addTest(11, 11, 180.0f); - } +#[test] +fn codabar_black_box1_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/codabar-1", + CodaBarReader::new(), + BarcodeFormat::CODABAR, + ); + // super("src/test/resources/blackbox/codabar-1", new MultiFormatReader(), BarcodeFormat.CODABAR); + tester.add_test(11, 11, 0.0); + tester.add_test(11, 11, 180.0); + tester.test_black_box(); }