From 79aafa200d3e41c36db2e6f5cf73c169df13d58b Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 9 Oct 2022 22:01:36 -0500 Subject: [PATCH] qr_code partially working --- src/lib.rs | 4 +- src/qrcode/QRCodeReader.java | 221 --------------- src/qrcode/QRCodeWriter.java | 118 -------- src/qrcode/QRCodeWriterTestCase.java | 136 ---------- src/qrcode/QRCodeWriterTestCase.rs | 162 +++++++++++ src/qrcode/decoder/bit_matrix_parser.rs | 8 +- src/qrcode/decoder/data_block.rs | 3 +- src/qrcode/decoder/error_correction_level.rs | 29 ++ src/qrcode/decoder/version.rs | 5 +- src/qrcode/detector/DetectorTest.rs | 62 +++++ .../detector/alignment_pattern_finder.rs | 8 +- src/qrcode/detector/detector.rs | 30 +-- src/qrcode/detector/finder_pattern_finder.rs | 49 ++-- src/qrcode/detector/mod.rs | 5 +- src/qrcode/encoder/byte_matrix.rs | 16 ++ src/qrcode/encoder/qr_code.rs | 5 +- src/qrcode/mod.rs | 11 +- src/qrcode/qr_code_reader.rs | 252 ++++++++++++++++++ src/qrcode/qr_code_writer.rs | 174 ++++++++++++ ...stCase.java => QRCodeBlackBox1TestCase.rs} | 27 +- ...stCase.java => QRCodeBlackBox2TestCase.rs} | 23 +- ...stCase.java => QRCodeBlackBox3TestCase.rs} | 23 +- ...stCase.java => QRCodeBlackBox4TestCase.rs} | 23 +- ...stCase.java => QRCodeBlackBox5TestCase.rs} | 23 +- ...stCase.java => QRCodeBlackBox6TestCase.rs} | 24 +- tests/common/abstract_black_box_test_case.rs | 2 +- 26 files changed, 839 insertions(+), 604 deletions(-) delete mode 100644 src/qrcode/QRCodeReader.java delete mode 100644 src/qrcode/QRCodeWriter.java delete mode 100644 src/qrcode/QRCodeWriterTestCase.java create mode 100644 src/qrcode/QRCodeWriterTestCase.rs create mode 100644 src/qrcode/detector/DetectorTest.rs create mode 100644 src/qrcode/qr_code_reader.rs create mode 100644 src/qrcode/qr_code_writer.rs rename tests/{QRCodeBlackBox1TestCase.java => QRCodeBlackBox1TestCase.rs} (54%) rename tests/{QRCodeBlackBox2TestCase.java => QRCodeBlackBox2TestCase.rs} (55%) rename tests/{QRCodeBlackBox3TestCase.java => QRCodeBlackBox3TestCase.rs} (56%) rename tests/{QRCodeBlackBox4TestCase.java => QRCodeBlackBox4TestCase.rs} (59%) rename tests/{QRCodeBlackBox5TestCase.java => QRCodeBlackBox5TestCase.rs} (64%) rename tests/{QRCodeBlackBox6TestCase.java => QRCodeBlackBox6TestCase.rs} (59%) diff --git a/src/lib.rs b/src/lib.rs index d237686..c9973e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -962,13 +962,13 @@ pub enum RXingResultMetadataValue { * If the code format supports structured append and the current scanned code is part of one then the * sequence number is given with it. */ - StructuredAppendSequence(u32), + StructuredAppendSequence(i32), /** * If the code format supports structured append and the current scanned code is part of one then the * parity is given with it. */ - StructuredAppendParity(u32), + StructuredAppendParity(i32), /** * Barcode Symbology Identifier. diff --git a/src/qrcode/QRCodeReader.java b/src/qrcode/QRCodeReader.java deleted file mode 100644 index 9165266..0000000 --- a/src/qrcode/QRCodeReader.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2007 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.qrcode; - -import com.google.zxing.BarcodeFormat; -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.RXingResult; -import com.google.zxing.RXingResultMetadataType; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.common.DecoderRXingResult; -import com.google.zxing.common.DetectorRXingResult; -import com.google.zxing.qrcode.decoder.Decoder; -import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData; -import com.google.zxing.qrcode.detector.Detector; - -import java.util.List; -import java.util.Map; - -/** - * This implementation can detect and decode QR Codes in an image. - * - * @author Sean Owen - */ -public class QRCodeReader implements Reader { - - private static final RXingResultPoint[] NO_POINTS = new RXingResultPoint[0]; - - private final Decoder decoder = new Decoder(); - - protected final Decoder getDecoder() { - return decoder; - } - - /** - * Locates and decodes a QR code in an image. - * - * @return a String representing the content encoded by the QR code - * @throws NotFoundException if a QR code cannot be found - * @throws FormatException if a QR code cannot be decoded - * @throws ChecksumException if error correction fails - */ - @Override - public RXingResult decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException { - return decode(image, null); - } - - @Override - public final RXingResult decode(BinaryBitmap image, Map hints) - throws NotFoundException, ChecksumException, FormatException { - DecoderRXingResult decoderRXingResult; - RXingResultPoint[] points; - if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) { - BitMatrix bits = extractPureBits(image.getBlackMatrix()); - decoderRXingResult = decoder.decode(bits, hints); - points = NO_POINTS; - } else { - DetectorRXingResult detectorRXingResult = new Detector(image.getBlackMatrix()).detect(hints); - decoderRXingResult = decoder.decode(detectorRXingResult.getBits(), hints); - points = detectorRXingResult.getPoints(); - } - - // If the code was mirrored: swap the bottom-left and the top-right points. - if (decoderRXingResult.getOther() instanceof QRCodeDecoderMetaData) { - ((QRCodeDecoderMetaData) decoderRXingResult.getOther()).applyMirroredCorrection(points); - } - - RXingResult result = new RXingResult(decoderRXingResult.getText(), decoderRXingResult.getRawBytes(), points, BarcodeFormat.QR_CODE); - List byteSegments = decoderRXingResult.getByteSegments(); - if (byteSegments != null) { - result.putMetadata(RXingResultMetadataType.BYTE_SEGMENTS, byteSegments); - } - String ecLevel = decoderRXingResult.getECLevel(); - if (ecLevel != null) { - result.putMetadata(RXingResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel); - } - if (decoderRXingResult.hasStructuredAppend()) { - result.putMetadata(RXingResultMetadataType.STRUCTURED_APPEND_SEQUENCE, - decoderRXingResult.getStructuredAppendSequenceNumber()); - result.putMetadata(RXingResultMetadataType.STRUCTURED_APPEND_PARITY, - decoderRXingResult.getStructuredAppendParity()); - } - result.putMetadata(RXingResultMetadataType.SYMBOLOGY_IDENTIFIER, "]Q" + decoderRXingResult.getSymbologyModifier()); - return result; - } - - @Override - public void reset() { - // do nothing - } - - /** - * This method detects a code in a "pure" image -- that is, pure monochrome image - * which contains only an unrotated, unskewed, image of a code, with some white border - * around it. This is a specialized method that works exceptionally fast in this special - * case. - */ - private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException { - - int[] leftTopBlack = image.getTopLeftOnBit(); - int[] rightBottomBlack = image.getBottomRightOnBit(); - if (leftTopBlack == null || rightBottomBlack == null) { - throw NotFoundException.getNotFoundInstance(); - } - - float moduleSize = moduleSize(leftTopBlack, image); - - int top = leftTopBlack[1]; - int bottom = rightBottomBlack[1]; - int left = leftTopBlack[0]; - int right = rightBottomBlack[0]; - - // Sanity check! - if (left >= right || top >= bottom) { - throw NotFoundException.getNotFoundInstance(); - } - - if (bottom - top != right - left) { - // Special case, where bottom-right module wasn't black so we found something else in the last row - // Assume it's a square, so use height as the width - right = left + (bottom - top); - if (right >= image.getWidth()) { - // Abort if that would not make sense -- off image - throw NotFoundException.getNotFoundInstance(); - } - } - - int matrixWidth = Math.round((right - left + 1) / moduleSize); - int matrixHeight = Math.round((bottom - top + 1) / moduleSize); - if (matrixWidth <= 0 || matrixHeight <= 0) { - throw NotFoundException.getNotFoundInstance(); - } - if (matrixHeight != matrixWidth) { - // Only possibly decode square regions - throw NotFoundException.getNotFoundInstance(); - } - - // Push in the "border" by half the module width so that we start - // sampling in the middle of the module. Just in case the image is a - // little off, this will help recover. - int nudge = (int) (moduleSize / 2.0f); - top += nudge; - left += nudge; - - // But careful that this does not sample off the edge - // "right" is the farthest-right valid pixel location -- right+1 is not necessarily - // This is positive by how much the inner x loop below would be too large - int nudgedTooFarRight = left + (int) ((matrixWidth - 1) * moduleSize) - right; - if (nudgedTooFarRight > 0) { - if (nudgedTooFarRight > nudge) { - // Neither way fits; abort - throw NotFoundException.getNotFoundInstance(); - } - left -= nudgedTooFarRight; - } - // See logic above - int nudgedTooFarDown = top + (int) ((matrixHeight - 1) * moduleSize) - bottom; - if (nudgedTooFarDown > 0) { - if (nudgedTooFarDown > nudge) { - // Neither way fits; abort - throw NotFoundException.getNotFoundInstance(); - } - top -= nudgedTooFarDown; - } - - // Now just read off the bits - BitMatrix bits = new BitMatrix(matrixWidth, matrixHeight); - for (int y = 0; y < matrixHeight; y++) { - int iOffset = top + (int) (y * moduleSize); - for (int x = 0; x < matrixWidth; x++) { - if (image.get(left + (int) (x * moduleSize), iOffset)) { - bits.set(x, y); - } - } - } - return bits; - } - - private static float moduleSize(int[] leftTopBlack, BitMatrix image) throws NotFoundException { - int height = image.getHeight(); - int width = image.getWidth(); - int x = leftTopBlack[0]; - int y = leftTopBlack[1]; - boolean inBlack = true; - int transitions = 0; - while (x < width && y < height) { - if (inBlack != image.get(x, y)) { - if (++transitions == 5) { - break; - } - inBlack = !inBlack; - } - x++; - y++; - } - if (x == width || y == height) { - throw NotFoundException.getNotFoundInstance(); - } - return (x - leftTopBlack[0]) / 7.0f; - } - -} diff --git a/src/qrcode/QRCodeWriter.java b/src/qrcode/QRCodeWriter.java deleted file mode 100644 index 50fccc6..0000000 --- a/src/qrcode/QRCodeWriter.java +++ /dev/null @@ -1,118 +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.qrcode; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.Writer; -import com.google.zxing.WriterException; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.qrcode.encoder.ByteMatrix; -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; -import com.google.zxing.qrcode.encoder.Encoder; -import com.google.zxing.qrcode.encoder.QRCode; - -import java.util.Map; - -/** - * This object renders a QR Code as a BitMatrix 2D array of greyscale values. - * - * @author dswitkin@google.com (Daniel Switkin) - */ -public final class QRCodeWriter implements Writer { - - private static final int QUIET_ZONE_SIZE = 4; - - @Override - public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) - throws WriterException { - - return encode(contents, format, width, height, null); - } - - @Override - public BitMatrix encode(String contents, - BarcodeFormat format, - int width, - int height, - Map hints) throws WriterException { - - if (contents.isEmpty()) { - throw new IllegalArgumentException("Found empty contents"); - } - - if (format != BarcodeFormat.QR_CODE) { - throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format); - } - - if (width < 0 || height < 0) { - throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' + - height); - } - - ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L; - int quietZone = QUIET_ZONE_SIZE; - if (hints != null) { - if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) { - errorCorrectionLevel = ErrorCorrectionLevel.valueOf(hints.get(EncodeHintType.ERROR_CORRECTION).toString()); - } - if (hints.containsKey(EncodeHintType.MARGIN)) { - quietZone = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString()); - } - } - - QRCode code = Encoder.encode(contents, errorCorrectionLevel, hints); - return renderRXingResult(code, width, height, quietZone); - } - - // Note that the input matrix uses 0 == white, 1 == black, while the output matrix uses - // 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap). - private static BitMatrix renderRXingResult(QRCode code, int width, int height, int quietZone) { - ByteMatrix input = code.getMatrix(); - if (input == null) { - throw new IllegalStateException(); - } - int inputWidth = input.getWidth(); - int inputHeight = input.getHeight(); - int qrWidth = inputWidth + (quietZone * 2); - int qrHeight = inputHeight + (quietZone * 2); - int outputWidth = Math.max(width, qrWidth); - int outputHeight = Math.max(height, qrHeight); - - int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight); - // Padding includes both the quiet zone and the extra white pixels to accommodate the requested - // dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone. - // If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will - // handle all the padding from 100x100 (the actual QR) up to 200x160. - int leftPadding = (outputWidth - (inputWidth * multiple)) / 2; - int topPadding = (outputHeight - (inputHeight * multiple)) / 2; - - BitMatrix output = new BitMatrix(outputWidth, outputHeight); - - for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) { - // Write the contents of this row of the barcode - for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { - if (input.get(inputX, inputY) == 1) { - output.setRegion(outputX, outputY, multiple, multiple); - } - } - } - - return output; - } - -} diff --git a/src/qrcode/QRCodeWriterTestCase.java b/src/qrcode/QRCodeWriterTestCase.java deleted file mode 100644 index 5a5abbc..0000000 --- a/src/qrcode/QRCodeWriterTestCase.java +++ /dev/null @@ -1,136 +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.qrcode; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.Writer; -import com.google.zxing.WriterException; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; -import org.junit.Assert; -import org.junit.Test; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.EnumMap; -import java.util.Map; - -/** - * @author satorux@google.com (Satoru Takabayashi) - creator - * @author dswitkin@google.com (Daniel Switkin) - ported and expanded from C++ - */ -public final class QRCodeWriterTestCase extends Assert { - - private static final Path BASE_IMAGE_PATH = Paths.get("src/test/resources/golden/qrcode/"); - - private static BufferedImage loadImage(String fileName) throws IOException { - Path file = BASE_IMAGE_PATH.resolve(fileName); - if (!Files.exists(file)) { - // try starting with 'core' since the test base is often given as the project root - file = Paths.get("core/").resolve(BASE_IMAGE_PATH).resolve(fileName); - } - assertTrue("Please download and install test images, and run from the 'core' directory", Files.exists(file)); - return ImageIO.read(file.toFile()); - } - - // In case the golden images are not monochromatic, convert the RGB values to greyscale. - private static BitMatrix createMatrixFromImage(BufferedImage image) { - int width = image.getWidth(); - int height = image.getHeight(); - int[] pixels = new int[width * height]; - image.getRGB(0, 0, width, height, pixels, 0, width); - - BitMatrix matrix = new BitMatrix(width, height); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int pixel = pixels[y * width + x]; - int luminance = (306 * ((pixel >> 16) & 0xFF) + - 601 * ((pixel >> 8) & 0xFF) + - 117 * (pixel & 0xFF)) >> 10; - if (luminance <= 0x7F) { - matrix.set(x, y); - } - } - } - return matrix; - } - - @Test - public void testQRCodeWriter() throws WriterException { - // The QR should be multiplied up to fit, with extra padding if necessary - int bigEnough = 256; - Writer writer = new QRCodeWriter(); - BitMatrix matrix = writer.encode("http://www.google.com/", BarcodeFormat.QR_CODE, bigEnough, - bigEnough, null); - assertNotNull(matrix); - assertEquals(bigEnough, matrix.getWidth()); - assertEquals(bigEnough, matrix.getHeight()); - - // The QR will not fit in this size, so the matrix should come back bigger - int tooSmall = 20; - matrix = writer.encode("http://www.google.com/", BarcodeFormat.QR_CODE, tooSmall, - tooSmall, null); - assertNotNull(matrix); - assertTrue(tooSmall < matrix.getWidth()); - assertTrue(tooSmall < matrix.getHeight()); - - // We should also be able to handle non-square requests by padding them - int strangeWidth = 500; - int strangeHeight = 100; - matrix = writer.encode("http://www.google.com/", BarcodeFormat.QR_CODE, strangeWidth, - strangeHeight, null); - assertNotNull(matrix); - assertEquals(strangeWidth, matrix.getWidth()); - assertEquals(strangeHeight, matrix.getHeight()); - } - - private static void compareToGoldenFile(String contents, - ErrorCorrectionLevel ecLevel, - int resolution, - String fileName) throws WriterException, IOException { - - BufferedImage image = loadImage(fileName); - assertNotNull(image); - BitMatrix goldenRXingResult = createMatrixFromImage(image); - assertNotNull(goldenRXingResult); - - Map hints = new EnumMap<>(EncodeHintType.class); - hints.put(EncodeHintType.ERROR_CORRECTION, ecLevel); - Writer writer = new QRCodeWriter(); - BitMatrix generatedRXingResult = writer.encode(contents, BarcodeFormat.QR_CODE, resolution, - resolution, hints); - - assertEquals(resolution, generatedRXingResult.getWidth()); - assertEquals(resolution, generatedRXingResult.getHeight()); - assertEquals(goldenRXingResult, generatedRXingResult); - } - - // Golden images are generated with "qrcode_sample.cc". The images are checked with both eye balls - // and cell phones. We expect pixel-perfect results, because the error correction level is known, - // and the pixel dimensions matches exactly. - @Test - public void testRegressionTest() throws Exception { - compareToGoldenFile("http://www.google.com/", ErrorCorrectionLevel.M, 99, - "renderer-test-01.png"); - } - -} diff --git a/src/qrcode/QRCodeWriterTestCase.rs b/src/qrcode/QRCodeWriterTestCase.rs new file mode 100644 index 0000000..1cebfab --- /dev/null +++ b/src/qrcode/QRCodeWriterTestCase.rs @@ -0,0 +1,162 @@ +/* + * 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 std::{collections::HashMap, hash::Hash, path::{Path, PathBuf}}; + +use image::{DynamicImage, EncodableLayout}; + +use crate::{ + common::BitMatrix, qrcode::QRCodeWriter, BarcodeFormat, EncodeHintType, EncodeHintValue, Writer, +}; + +use super::decoder::ErrorCorrectionLevel; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported and expanded from C++ + */ + +const BASE_IMAGE_PATH: &str = "test_resources/golden/qrcode/"; + +fn loadImage(fileName: &str) -> DynamicImage { + let mut file = PathBuf::from(BASE_IMAGE_PATH); + file.push(fileName); + if !file.exists() { + // try starting with 'core' since the test base is often given as the project root + file = PathBuf::from("core/"); + file.push(BASE_IMAGE_PATH); + file.push(fileName); //Paths.get("core/").resolve(BASE_IMAGE_PATH).resolve(fileName); + } + assert!( + file.exists(), + "Please download and install test images, and run from the 'core' directory" + ); + DynamicImage::from(image::io::Reader::open(file).expect("image should load").decode().expect("decode")) +} + +// In case the golden images are not monochromatic, convert the RGB values to greyscale. +fn createMatrixFromImage(image: DynamicImage) -> BitMatrix { + let width = image.width() as usize; + let height = image.height() as usize; + // let pixels = vec![0u32; width * height]; //new int[width * height]; + // image.getRGB(0, 0, width, height, pixels, 0, width); + let img_src = DynamicImage::from(image).into_rgb8();;//image.as_rgb8().unwrap().as_bytes(); + let pixels = img_src.as_bytes(); + + let mut matrix = BitMatrix::new(width as u32, height as u32).expect("create new bitmatrix"); + for y in 0..height { + // for (int y = 0; y < height; y++) { + for x in 0..width { + // for (int x = 0; x < width; x++) { + let pixel = pixels[y * width + x] as u32; + let luminance = + (306 * ((pixel >> 16) & 0xFF) + 601 * ((pixel >> 8) & 0xFF) + 117 * (pixel & 0xFF)) + >> 10; + if (luminance <= 0x7F) { + matrix.set(x as u32, y as u32); + } + } + } + return matrix; +} + +#[test] +fn testQRCodeWriter() { + // The QR should be multiplied up to fit, with extra padding if necessary + let bigEnough = 256; + let writer = QRCodeWriter {}; + let mut matrix = writer.encode_with_hints( + "http://www.google.com/", + &BarcodeFormat::QR_CODE, + bigEnough, + bigEnough, + &HashMap::new(), + ); + assert!(matrix.is_ok()); + let mut matrix = matrix.unwrap(); + assert_eq!(bigEnough as u32, matrix.getWidth()); + assert_eq!(bigEnough as u32, matrix.getHeight()); + + // The QR will not fit in this size, so the matrix should come back bigger + let tooSmall = 20; + matrix = writer.encode_with_hints( + "http://www.google.com/", + &BarcodeFormat::QR_CODE, + tooSmall, + tooSmall, + &HashMap::new(), + ).expect("should encode"); + // assertNotNull(matrix); + assert!((tooSmall as u32) < matrix.getWidth()); + assert!((tooSmall as u32) < matrix.getHeight()); + + // We should also be able to handle non-square requests by padding them + let strangeWidth = 500; + let strangeHeight = 100; + matrix = writer.encode_with_hints( + "http://www.google.com/", + &BarcodeFormat::QR_CODE, + strangeWidth, + strangeHeight, + &HashMap::new(), + ).expect("should encode"); + // assertNotNull(matrix); + assert_eq!(strangeWidth as u32, matrix.getWidth()); + assert_eq!(strangeHeight as u32, matrix.getHeight()); +} + +fn compareToGoldenFile( + contents: &str, + ecLevel: &ErrorCorrectionLevel, + resolution: u32, + fileName: &str, +) { + let image = loadImage(fileName); + // assertNotNull(image); + let goldenRXingResult = createMatrixFromImage(image); + // assertNotNull(goldenRXingResult); + + let mut hints = HashMap::new(); + hints.insert( + EncodeHintType::ERROR_CORRECTION, + EncodeHintValue::ErrorCorrection(ecLevel.get_value().to_string()), + ); + let writer = QRCodeWriter {}; + let generatedRXingResult = writer.encode_with_hints( + contents, + &BarcodeFormat::QR_CODE, + resolution as i32, + resolution as i32, + &hints, + ).expect("should encode"); + + assert_eq!(resolution, generatedRXingResult.getWidth()); + assert_eq!(resolution, generatedRXingResult.getHeight()); + assert_eq!(goldenRXingResult, generatedRXingResult); +} + +// Golden images are generated with "qrcode_sample.cc". The images are checked with both eye balls +// and cell phones. We expect pixel-perfect results, because the error correction level is known, +// and the pixel dimensions matches exactly. +#[test] +fn testRegressionTest() { + compareToGoldenFile( + "http://www.google.com/", + &ErrorCorrectionLevel::M, + 99, + "renderer-test-01.png", + ); +} diff --git a/src/qrcode/decoder/bit_matrix_parser.rs b/src/qrcode/decoder/bit_matrix_parser.rs index 2e4e325..575266c 100644 --- a/src/qrcode/decoder/bit_matrix_parser.rs +++ b/src/qrcode/decoder/bit_matrix_parser.rs @@ -82,7 +82,7 @@ impl BitMatrixParser { let dimension = self.bitMatrix.getHeight(); let mut formatInfoBits2 = 0; let jMin = dimension - 7; - for j in (jMin..=dimension).rev() { + for j in (jMin..=dimension-1).rev() { // for (int j = dimension - 1; j >= jMin; j--) { formatInfoBits2 = self.copyBit(8, j, formatInfoBits2); } @@ -194,7 +194,7 @@ impl BitMatrixParser { let mut currentByte = 0; let mut bitsRead = 0; // Read columns in pairs, from right to left - let mut j = dimension - 1; + let mut j = dimension as i32 - 1; while j > 0 { // for (int j = dimension - 1; j > 0; j -= 2) { if j == 6 { @@ -213,11 +213,11 @@ impl BitMatrixParser { for col in 0..2 { // for (int col = 0; col < 2; col++) { // Ignore bits covered by the function pattern - if !functionPattern.get(j - col, i) { + if !functionPattern.get(j as u32 - col, i) { // Read a bit bitsRead += 1; currentByte <<= 1; - if self.bitMatrix.get(j - col, i) { + if self.bitMatrix.get(j as u32 - col, i) { currentByte |= 1; } // If we've made a whole byte, save it off diff --git a/src/qrcode/decoder/data_block.rs b/src/qrcode/decoder/data_block.rs index c9fd416..3d01014 100755 --- a/src/qrcode/decoder/data_block.rs +++ b/src/qrcode/decoder/data_block.rs @@ -80,7 +80,8 @@ impl DataBlock { // for (int i = 0; i < ecBlock.getCount(); i++) { let numDataCodewords = ecBlock.getDataCodewords(); let numBlockCodewords = ecBlocks.getECCodewordsPerBlock() + numDataCodewords; - result[numRXingResultBlocks] = DataBlock::new(numDataCodewords, vec![0u8;numBlockCodewords as usize]); + // result[numRXingResultBlocks] = DataBlock::new(numDataCodewords, vec![0u8;numBlockCodewords as usize]); + result.push( DataBlock::new(numDataCodewords, vec![0u8;numBlockCodewords as usize])); numRXingResultBlocks += 1; } } diff --git a/src/qrcode/decoder/error_correction_level.rs b/src/qrcode/decoder/error_correction_level.rs index 908c8a5..6b30954 100644 --- a/src/qrcode/decoder/error_correction_level.rs +++ b/src/qrcode/decoder/error_correction_level.rs @@ -14,6 +14,8 @@ * limitations under the License. */ +use std::str::FromStr; + use crate::Exceptions; /** @@ -85,6 +87,33 @@ impl From for u8 { } } +impl FromStr for ErrorCorrectionLevel { + type Err=Exceptions; + + fn from_str(s: &str) -> Result { + // First try to see if the string is just the name of the value + let as_str = match s.to_uppercase().as_str() { + "L" => Some(ErrorCorrectionLevel::L), + "M" => Some(ErrorCorrectionLevel::M), + "Q" =>Some(ErrorCorrectionLevel::Q), + "H" =>Some(ErrorCorrectionLevel::H), + _ => None, + }; + + // If we find something, cool, return it, otherwise keep trying as numbers + if as_str.is_some() { + return Ok(as_str.unwrap()); + } + + let number_possible = s.parse::(); + if number_possible.is_ok() { + return number_possible.unwrap().try_into(); + } + + return Err(Exceptions::IllegalArgumentException(format!("could not parse {} into an ec level", s))) + } +} + //private static final ErrorCorrectionLevel[] FOR_BITS = {M, L, H, Q}; //private final int bits; diff --git a/src/qrcode/decoder/version.rs b/src/qrcode/decoder/version.rs index 3ecdc50..de8bd38 100755 --- a/src/qrcode/decoder/version.rs +++ b/src/qrcode/decoder/version.rs @@ -45,6 +45,7 @@ const VERSION_DECODE_INFO: [u32; 34] = [ * * @author Sean Owen */ +#[derive(Debug)] pub struct Version { // private static final Version[] VERSIONS = buildVersions(); versionNumber: u32, @@ -930,7 +931,8 @@ impl fmt::Display for Version { * each set of blocks. It also holds the number of error-correction codewords per block since it * will be the same across all blocks within one version.

*/ -pub struct ECBlocks { +#[derive(Debug)] + pub struct ECBlocks { ecCodewordsPerBlock: u32, ecBlocks: Vec, } @@ -970,6 +972,7 @@ impl ECBlocks { * This includes the number of data codewords, and the number of times a block with these * parameters is used consecutively in the QR code version's format.

*/ +#[derive(Debug)] pub struct ECB { count: u32, dataCodewords: u32, diff --git a/src/qrcode/detector/DetectorTest.rs b/src/qrcode/detector/DetectorTest.rs new file mode 100644 index 0000000..3496574 --- /dev/null +++ b/src/qrcode/detector/DetectorTest.rs @@ -0,0 +1,62 @@ +use std::collections::HashMap; + +use crate::{qrcode::{ + decoder::decoder, decoder::ErrorCorrectionLevel, detector::Detector, encoder::encoder, +}, common::BitMatrix}; + +#[test] +fn test_simple() { + test_encode_decode("value"); +} + +#[test] +fn test_uri() { + test_encode_decode("https://google.com"); +} + +#[test] +fn test_unicode() { + test_encode_decode("\u{3484}\u{f8a7}\u{a1e7}"); +} + +fn test_encode_decode(value: &str) { + for ec_level_v in 0..4 { + let ec_level: ErrorCorrectionLevel = + ErrorCorrectionLevel::forBits(ec_level_v).expect("must get level"); + let qr_code = + encoder::encode_with_hints(value, ec_level, &HashMap::new()).expect("must encode"); + // dbg!(&qr_code.to_string()); + let byt_matrix = qr_code.getMatrix().as_ref().unwrap().clone(); + // dbg!(BitMatrix::from(byt_matrix.clone()).to_string()); + // let mut detector = Detector::new(make_larger(&byt_matrix.into(),5)); + let mut detector = Detector::new(byt_matrix.into()); + let _detected_points = detector.detect().expect("must detect"); + let decoded = decoder::decode_bitmatrix( + &qr_code + .getMatrix() + .clone() + .expect("matrix must exist") + .into(), + ) + .expect("must decode"); + assert_eq!(decoded.getText(), value); + } +} + +// Zooms a bit matrix so that each bit is factor x factor +fn make_larger(input: &BitMatrix, factor: u32) -> BitMatrix { + let width = input.getWidth(); + let mut output = BitMatrix::with_single_dimension(width * factor); + for inputY in 0..width { + // for (int inputY = 0; inputY < width; inputY++) { + for inputX in 0..width { + // for (int inputX = 0; inputX < width; inputX++) { + if input.get(inputX, inputY) { + output + .setRegion(inputX * factor, inputY * factor, factor, factor) + .expect("region set should be ok"); + } + } + } + return output; +} \ No newline at end of file diff --git a/src/qrcode/detector/alignment_pattern_finder.rs b/src/qrcode/detector/alignment_pattern_finder.rs index 290537e..1f26b3f 100644 --- a/src/qrcode/detector/alignment_pattern_finder.rs +++ b/src/qrcode/detector/alignment_pattern_finder.rs @@ -95,12 +95,12 @@ impl AlignmentPatternFinder { for iGen in 0..height { // for (int iGen = 0; iGen < height; iGen++) { // Search from middle outwards - let i = middleI + let i = (middleI as i32 + (if (iGen & 0x01) == 0 { - (iGen + 1) / 2 + (iGen as i32 + 1) / 2 } else { - -((iGen as i32 + 1) / 2) as u32 - }); + -((iGen as i32 + 1) / 2) + })) as u32; stateCount[0] = 0; stateCount[1] = 0; stateCount[2] = 0; diff --git a/src/qrcode/detector/detector.rs b/src/qrcode/detector/detector.rs index 6df5d54..71b6d87 100644 --- a/src/qrcode/detector/detector.rs +++ b/src/qrcode/detector/detector.rs @@ -336,27 +336,27 @@ impl Detector { // Now count other way -- don't run off image though of course let mut scale = 1.0; - let mut otherToX = fromX - (toX - fromX); + let mut otherToX = fromX as i32 - (toX as i32 - fromX as i32); if (otherToX < 0) { - scale = fromX as f32 / (fromX - otherToX) as f32; + scale = fromX as f32 / (fromX as i32- otherToX) as f32; otherToX = 0; - } else if (otherToX >= self.image.getWidth()) { - scale = (self.image.getWidth() - 1 - fromX) as f32 / (otherToX - fromX) as f32; - otherToX = self.image.getWidth() - 1; + } else if (otherToX as u32 >= self.image.getWidth() ) { + scale = (self.image.getWidth() as i32 - 1 - fromX as i32) as f32 / (otherToX - fromX as i32) as f32; + otherToX = self.image.getWidth() as i32 - 1; } - let mut otherToY = (fromY - (toY - fromY) * scale as u32) as u32; + let mut otherToY = (fromY as i32 - (toY as i32 - fromY as i32) * scale as i32); scale = 1.0; if (otherToY < 0) { - scale = fromY as f32 / (fromY - otherToY) as f32; + scale = fromY as f32 / (fromY as i32 - otherToY) as f32; otherToY = 0; - } else if (otherToY >= self.image.getHeight()) { - scale = (self.image.getHeight() - 1 - fromY) as f32 / (otherToY - fromY) as f32; - otherToY = self.image.getHeight() - 1; + } else if (otherToY as u32 >= self.image.getHeight()) { + scale = (self.image.getHeight() as i32 - 1 - fromY as i32) as f32 / (otherToY - fromY as i32) as f32; + otherToY = self.image.getHeight() as i32 - 1; } - otherToX = (fromX + (otherToX - fromX) * scale as u32) as u32; + otherToX = (fromX as i32+ (otherToX - fromX as i32) * scale as i32); - result += self.sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY); + result += self.sizeOfBlackWhiteBlackRun(fromX as u32, fromY as u32, otherToX as u32, otherToY as u32); // Middle pixel is double-counted this way; subtract 1 return result - 1.0; @@ -377,7 +377,7 @@ impl Detector { let mut toY = toY; // Mild variant of Bresenham's algorithm; // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm - let steep = (toY - fromY) > (toX - fromX); + let steep = (toY as i64 - fromY as i64).abs() > (toX as i64 - fromX as i64).abs(); if (steep) { let mut temp = fromX; fromX = fromY; @@ -387,8 +387,8 @@ impl Detector { toY = temp; } - let dx: i32 = (toX - fromX) as i32; - let dy: i32 = (toY - fromY) as i32; + let dx: i32 = (toX as i64 - fromX as i64).abs() as i32; + let dy: i32 = (toY as i64 - fromY as i64).abs() as i32; let mut error = -dx / 2; let xstep: i32 = if fromX < toX { 1 } else { -1 }; let ystep: i32 = if fromY < toY { 1 } else { -1 }; diff --git a/src/qrcode/detector/finder_pattern_finder.rs b/src/qrcode/detector/finder_pattern_finder.rs index 77bc11b..1af1ef8 100755 --- a/src/qrcode/detector/finder_pattern_finder.rs +++ b/src/qrcode/detector/finder_pattern_finder.rs @@ -161,6 +161,7 @@ impl FinderPatternFinder { stateCount[currentState] += 1; } } + j+=1; } if FinderPatternFinder::foundPatternCross(&stateCount) { let confirmed = self.handlePossibleCenter(&stateCount, i, maxJ); @@ -187,7 +188,7 @@ impl FinderPatternFinder { * figures the location of the center of this run. */ fn centerFromEnd(stateCount: &[u32], end: u32) -> f32 { - ((end - stateCount[4] - stateCount[3]) - stateCount[2]) as f32 / 2.0 + (end - stateCount[4] - stateCount[3]) as f32 - ((stateCount[2] as f32) / 2.0) } /** @@ -363,19 +364,19 @@ impl FinderPatternFinder { ) -> f32 { // let image = &self.image; - let maxI = self.image.getHeight(); + let maxI = self.image.getHeight() as i32; let _stateCount = self.getCrossCheckStateCount(); // Start counting up from center - let mut i = startI; - while i >= 0 && self.image.get(centerJ, i) { + let mut i = startI as i32; + while i >= 0 && self.image.get(centerJ, i as u32) { self.crossCheckStateCount[2] += 1; i -= 1; } if i < 0 { return f32::NAN; } - while i >= 0 && !self.image.get(centerJ, i) && self.crossCheckStateCount[1] <= maxCount { + while i >= 0 && !self.image.get(centerJ, i as u32) && self.crossCheckStateCount[1] <= maxCount { self.crossCheckStateCount[1] += 1; i -= 1; } @@ -383,7 +384,7 @@ impl FinderPatternFinder { if i < 0 || self.crossCheckStateCount[1] > maxCount { return f32::NAN; } - while i >= 0 && self.image.get(centerJ, i) && self.crossCheckStateCount[0] <= maxCount { + while i >= 0 && self.image.get(centerJ, i as u32) && self.crossCheckStateCount[0] <= maxCount { self.crossCheckStateCount[0] += 1; i -= 1; } @@ -392,22 +393,22 @@ impl FinderPatternFinder { } // Now also count down from center - i = startI + 1; - while i < maxI && self.image.get(centerJ, i) { + i = startI as i32 + 1; + while i < maxI && self.image.get(centerJ, i as u32) { self.crossCheckStateCount[2] += 1; i += 1; } if i == maxI { return f32::NAN; } - while i < maxI && !self.image.get(centerJ, i) && self.crossCheckStateCount[3] < maxCount { + while i < maxI && !self.image.get(centerJ, i as u32) && self.crossCheckStateCount[3] < maxCount { self.crossCheckStateCount[3] += 1; i += 1; } if i == maxI || self.crossCheckStateCount[3] >= maxCount { return f32::NAN; } - while i < maxI && self.image.get(centerJ, i) && self.crossCheckStateCount[4] < maxCount { + while i < maxI && self.image.get(centerJ, i as u32) && self.crossCheckStateCount[4] < maxCount { self.crossCheckStateCount[4] += 1; i += 1; } @@ -422,12 +423,12 @@ impl FinderPatternFinder { + self.crossCheckStateCount[2] + self.crossCheckStateCount[3] + self.crossCheckStateCount[4]; - if 5 * (stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal { + if 5 * (stateCountTotal as i64 - originalStateCountTotal as i64) >= 2 * originalStateCountTotal as i64 { return f32::NAN; } if Self::foundPatternCross(&self.crossCheckStateCount) { - Self::centerFromEnd(&self.crossCheckStateCount, i) + Self::centerFromEnd(&self.crossCheckStateCount, i as u32) } else { f32::NAN } @@ -450,22 +451,22 @@ impl FinderPatternFinder { let maxJ = self.image.getWidth(); let _stateCount = self.getCrossCheckStateCount(); - let mut j = startJ; - while j >= 0 && self.image.get(j, centerI) { + let mut j = startJ as i32; + while j >= 0 && self.image.get(j as u32, centerI) { self.crossCheckStateCount[2] += 1; j -= 1; } if j < 0 { return f32::NAN; } - while j >= 0 && !self.image.get(j, centerI) && self.crossCheckStateCount[1] <= maxCount { + while j >= 0 && !self.image.get(j as u32, centerI) && self.crossCheckStateCount[1] <= maxCount { self.crossCheckStateCount[1] += 1; j -= 1; } if j < 0 || self.crossCheckStateCount[1] > maxCount { return f32::NAN; } - while j >= 0 && self.image.get(j, centerI) && self.crossCheckStateCount[0] <= maxCount { + while j >= 0 && self.image.get(j as u32 as u32, centerI) && self.crossCheckStateCount[0] <= maxCount { self.crossCheckStateCount[0] += 1; j -= 1; } @@ -473,22 +474,22 @@ impl FinderPatternFinder { return f32::NAN; } - j = startJ + 1; - while j < maxJ && self.image.get(j, centerI) { + j = startJ as i32 + 1; + while j < (maxJ as i32) && self.image.get(j as u32, centerI) { self.crossCheckStateCount[2] += 1; j += 1; } - if j == maxJ { + if j == maxJ as i32 { return f32::NAN; } - while j < maxJ && !self.image.get(j, centerI) && self.crossCheckStateCount[3] < maxCount { + while j < maxJ as i32 && !self.image.get(j as u32, centerI) && self.crossCheckStateCount[3] < maxCount { self.crossCheckStateCount[3] += 1; j += 1; } - if j == maxJ || self.crossCheckStateCount[3] >= maxCount { + if j == (maxJ as i32) || self.crossCheckStateCount[3] >= maxCount { return f32::NAN; } - while j < maxJ && self.image.get(j, centerI) && self.crossCheckStateCount[4] < maxCount { + while j < (maxJ as i32) && self.image.get(j as u32, centerI) && self.crossCheckStateCount[4] < maxCount { self.crossCheckStateCount[4] += 1; j += 1; } @@ -503,12 +504,12 @@ impl FinderPatternFinder { + self.crossCheckStateCount[2] + self.crossCheckStateCount[3] + self.crossCheckStateCount[4]; - if 5 * (stateCountTotal - originalStateCountTotal) >= originalStateCountTotal { + if 5 * (stateCountTotal as i64 - originalStateCountTotal as i64) >= originalStateCountTotal as i64 { return f32::NAN; } if Self::foundPatternCross(&self.crossCheckStateCount) { - Self::centerFromEnd(&self.crossCheckStateCount, j) + Self::centerFromEnd(&self.crossCheckStateCount, j as u32) } else { f32::NAN } diff --git a/src/qrcode/detector/mod.rs b/src/qrcode/detector/mod.rs index f1cffd5..822363f 100644 --- a/src/qrcode/detector/mod.rs +++ b/src/qrcode/detector/mod.rs @@ -12,4 +12,7 @@ pub use alignment_pattern::*; pub use alignment_pattern_finder::*; pub use finder_pattern_finder::*; pub use detector::*; -pub use qrcode_detector_result::*; \ No newline at end of file +pub use qrcode_detector_result::*; + +#[cfg(test)] +mod DetectorTest; \ No newline at end of file diff --git a/src/qrcode/encoder/byte_matrix.rs b/src/qrcode/encoder/byte_matrix.rs index 4953136..2c3d43d 100644 --- a/src/qrcode/encoder/byte_matrix.rs +++ b/src/qrcode/encoder/byte_matrix.rs @@ -16,6 +16,8 @@ use std::fmt; +use crate::common::BitMatrix; + /** * JAVAPORT: The original code was a 2D array of ints, but since it only ever gets assigned * -1, 0, and 1, I'm going to use less memory and go with bytes. @@ -110,3 +112,17 @@ impl fmt::Display for ByteMatrix { write!(f, "{}", result) } } + +impl From for BitMatrix { + fn from(bm: ByteMatrix) -> Self { + let mut bit_matrix = BitMatrix::new(bm.getWidth(), bm.getHeight()).unwrap(); + for y in 0..bm.getHeight() { + for x in 0..bm.getWidth() { + if bm.get(x, y) > 0 { + bit_matrix.set(x as u32, y as u32); + } + } + } + bit_matrix + } +} diff --git a/src/qrcode/encoder/qr_code.rs b/src/qrcode/encoder/qr_code.rs index 0ecc1b2..00835c2 100644 --- a/src/qrcode/encoder/qr_code.rs +++ b/src/qrcode/encoder/qr_code.rs @@ -16,7 +16,7 @@ use std::fmt; -use crate::qrcode::decoder::{Mode, ErrorCorrectionLevel, Version}; +use crate::qrcode::decoder::{Mode, ErrorCorrectionLevel, Version, VersionRef}; use super::ByteMatrix; @@ -25,13 +25,14 @@ use super::ByteMatrix; * @author satorux@google.com (Satoru Takabayashi) - creator * @author dswitkin@google.com (Daniel Switkin) - ported from C++ */ +#[derive(Debug, Clone)] pub struct QRCode { // public static final int NUM_MASK_PATTERNS = 8; mode:Option, ecLevel:Option, - version:Option<&'static Version>, + version:Option, maskPattern:i32, matrix:Option, diff --git a/src/qrcode/mod.rs b/src/qrcode/mod.rs index 7964688..90ea2ca 100644 --- a/src/qrcode/mod.rs +++ b/src/qrcode/mod.rs @@ -1,3 +1,12 @@ pub mod detector; pub mod decoder; -pub mod encoder; \ No newline at end of file +pub mod encoder; + +mod qr_code_reader; +pub use qr_code_reader::*; + +mod qr_code_writer; +pub use qr_code_writer::*; + +#[cfg(test)] +mod QRCodeWriterTestCase; \ No newline at end of file diff --git a/src/qrcode/qr_code_reader.rs b/src/qrcode/qr_code_reader.rs new file mode 100644 index 0000000..8759290 --- /dev/null +++ b/src/qrcode/qr_code_reader.rs @@ -0,0 +1,252 @@ +/* + * Copyright 2007 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 std::collections::HashMap; + +use crate::{ + common::{BitMatrix, DecoderRXingResult, DetectorRXingResult}, + BarcodeFormat, DecodeHintType, Exceptions, RXingResult, RXingResultMetadataType, + RXingResultMetadataValue, RXingResultPoint, Reader, ResultPoint, +}; + +use super::{ + decoder::{decoder, QRCodeDecoderMetaData}, + detector::Detector, +}; + +/** + * This implementation can detect and decode QR Codes in an image. + * + * @author Sean Owen + */ +pub struct QRCodeReader; +// pub struct QRCodeReader; { + +// // private static final RXingResultPoint[] NO_POINTS = new RXingResultPoint[0]; +// } + +impl Reader for QRCodeReader { + /** + * Locates and decodes a QR code in an image. + * + * @return a String representing the content encoded by the QR code + * @throws NotFoundException if a QR code cannot be found + * @throws FormatException if a QR code cannot be decoded + * @throws ChecksumException if error correction fails + */ + fn decode(&self, image: &crate::BinaryBitmap) -> Result { + self.decode_with_hints(image, &HashMap::new()) + } + + fn decode_with_hints( + &self, + image: &crate::BinaryBitmap, + hints: &crate::DecodingHintDictionary, + ) -> Result { + let decoderRXingResult: DecoderRXingResult; + let mut points: Vec; + if hints.contains_key(&DecodeHintType::PURE_BARCODE) { + let bits = Self::extractPureBits(image.getBlackMatrix()?)?; + decoderRXingResult = decoder::decode_bitmatrix_with_hints(&bits, &hints)?; + points = Vec::new(); + } else { + let detectorRXingResult = + Detector::new(image.getBlackMatrix()?.clone()).detect_with_hints(&hints)?; + decoderRXingResult = + decoder::decode_bitmatrix_with_hints(detectorRXingResult.getBits(), &hints)?; + points = detectorRXingResult.getPoints().clone(); + } + + // If the code was mirrored: swap the bottom-left and the top-right points. + if decoderRXingResult.getOther().is::() { + // if (decoderRXingResult.getOther() instanceof QRCodeDecoderMetaData) { + decoderRXingResult + .getOther() + .downcast_ref::() + .unwrap() + .applyMirroredCorrection(&mut points); + // ((QRCodeDecoderMetaData) decoderRXingResult.getOther()).applyMirroredCorrection(points); + } + + let mut result = RXingResult::new( + decoderRXingResult.getText(), + decoderRXingResult.getRawBytes().clone(), + points, + BarcodeFormat::QR_CODE, + ); + let byteSegments = decoderRXingResult.getByteSegments(); + if !byteSegments.is_empty() { + result.putMetadata( + RXingResultMetadataType::BYTE_SEGMENTS, + RXingResultMetadataValue::ByteSegments(byteSegments.clone()), + ); + } + let ecLevel = decoderRXingResult.getECLevel(); + if !ecLevel.is_empty() { + result.putMetadata( + RXingResultMetadataType::ERROR_CORRECTION_LEVEL, + RXingResultMetadataValue::ErrorCorrectionLevel(ecLevel.to_owned()), + ); + } + if decoderRXingResult.hasStructuredAppend() { + result.putMetadata( + RXingResultMetadataType::STRUCTURED_APPEND_SEQUENCE, + RXingResultMetadataValue::StructuredAppendSequence( + decoderRXingResult.getStructuredAppendSequenceNumber(), + ), + ); + result.putMetadata( + RXingResultMetadataType::STRUCTURED_APPEND_PARITY, + RXingResultMetadataValue::StructuredAppendParity( + decoderRXingResult.getStructuredAppendParity(), + ), + ); + } + result.putMetadata( + RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, + RXingResultMetadataValue::SymbologyIdentifier(format!( + "]Q{}", + decoderRXingResult.getSymbologyModifier() + )), + ); + + Ok(result) + } + + fn reset(&self) { + // nothing + } +} + +impl QRCodeReader { + pub fn new() -> Self { + Self {} + } + + /** + * This method detects a code in a "pure" image -- that is, pure monochrome image + * which contains only an unrotated, unskewed, image of a code, with some white border + * around it. This is a specialized method that works exceptionally fast in this special + * case. + */ + fn extractPureBits(image: &BitMatrix) -> Result { + let leftTopBlack = image.getTopLeftOnBit(); + let rightBottomBlack = image.getBottomRightOnBit(); + if leftTopBlack.is_none() || rightBottomBlack.is_none() { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + let leftTopBlack = leftTopBlack.unwrap(); + let rightBottomBlack = rightBottomBlack.unwrap(); + + let moduleSize = Self::moduleSize(&leftTopBlack, image)?; + + let mut top = leftTopBlack[1] as i32; + let mut bottom = rightBottomBlack[1] as i32; + let mut left = leftTopBlack[0] as i32; + let mut right = rightBottomBlack[0] as i32; + + // Sanity check! + if left >= right || top >= bottom { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + if bottom - top != right - left { + // Special case, where bottom-right module wasn't black so we found something else in the last row + // Assume it's a square, so use height as the width + right = left + (bottom - top); + if right >= image.getWidth() as i32 { + // Abort if that would not make sense -- off image + return Err(Exceptions::NotFoundException("".to_owned())); + } + } + let matrixWidth = ((right as f32 - left as f32 + 1.0) / moduleSize).round() as u32; + let matrixHeight = ((bottom as f32 - top as f32 + 1.0) / moduleSize).round() as u32; + if matrixWidth <= 0 || matrixHeight <= 0 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + if matrixHeight != matrixWidth { + // Only possibly decode square regions + return Err(Exceptions::NotFoundException("".to_owned())); + } + + // Push in the "border" by half the module width so that we start + // sampling in the middle of the module. Just in case the image is a + // little off, this will help recover. + let nudge = (moduleSize / 2.0) as u32; + top += nudge as i32; + left += nudge as i32; + + // But careful that this does not sample off the edge + // "right" is the farthest-right valid pixel location -- right+1 is not necessarily + // This is positive by how much the inner x loop below would be too large + let nudgedTooFarRight = left as i32 + ((matrixWidth as i32 - 1) as f32 * moduleSize as f32) as i32 - right as i32; + if nudgedTooFarRight > 0 { + if nudgedTooFarRight > nudge as i32 { + // Neither way fits; abort + return Err(Exceptions::NotFoundException("".to_owned())); + } + left -= nudgedTooFarRight; + } + // See logic above + let nudgedTooFarDown = top + ((matrixHeight - 1) as f32 * moduleSize) as i32 - bottom; + if nudgedTooFarDown > 0 { + if nudgedTooFarDown > nudge as i32 { + // Neither way fits; abort + return Err(Exceptions::NotFoundException("".to_owned())); + } + top -= nudgedTooFarDown; + } + + // Now just read off the bits + let mut bits = BitMatrix::new(matrixWidth, matrixHeight)?; + for y in 0..matrixHeight { + // for (int y = 0; y < matrixHeight; y++) { + let iOffset = top + ((y as f32) * moduleSize) as i32; + for x in 0..matrixWidth { + // for (int x = 0; x < matrixWidth; x++) { + if image.get(left as u32 + (x as f32 * moduleSize) as u32, iOffset as u32) { + bits.set(x, y); + } + } + } + Ok(bits) + } + + fn moduleSize(leftTopBlack: &[u32], image: &BitMatrix) -> Result { + let height = image.getHeight(); + let width = image.getWidth(); + let mut x = leftTopBlack[0]; + let mut y = leftTopBlack[1]; + let mut inBlack = true; + let mut transitions = 0; + while x < width && y < height { + if inBlack != image.get(x, y) { + transitions += 1; + if transitions == 5 { + break; + } + inBlack = !inBlack; + } + x += 1; + y += 1; + } + if x == width || y == height { + return Err(Exceptions::NotFoundException("".to_owned())); + } + Ok((x - leftTopBlack[0]) as f32 / 7.0) + } +} diff --git a/src/qrcode/qr_code_writer.rs b/src/qrcode/qr_code_writer.rs new file mode 100644 index 0000000..5e31473 --- /dev/null +++ b/src/qrcode/qr_code_writer.rs @@ -0,0 +1,174 @@ +/* + * 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 std::collections::HashMap; + +use crate::{ + common::BitMatrix, BarcodeFormat, EncodeHintType, EncodeHintValue, Exceptions, Writer, +}; + +use super::{ + decoder::ErrorCorrectionLevel, + encoder::{encoder, QRCode}, +}; + +const QUIET_ZONE_SIZE: i32 = 4; + +/** + * This object renders a QR Code as a BitMatrix 2D array of greyscale values. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +pub struct QRCodeWriter; // { + +// private static final int QUIET_ZONE_SIZE = 4; +//} + +impl Writer for QRCodeWriter { + fn encode( + &self, + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + ) -> Result { + self.encode_with_hints(contents, format, width, height, &HashMap::new()) + } + + fn encode_with_hints( + &self, + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + hints: &crate::EncodingHintDictionary, + ) -> Result { + if (contents.is_empty()) { + return Err(Exceptions::IllegalArgumentException( + "Found empty contents".to_owned(), + )); + // throw new IllegalArgumentException("Found empty contents"); + } + + if format != &BarcodeFormat::QR_CODE { + return Err(Exceptions::IllegalArgumentException(format!( + "Can only encode QR_CODE, but got {:?}", + format + ))); + // throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format); + } + + if (width < 0 || height < 0) { + return Err(Exceptions::IllegalArgumentException(format!( + "Requested dimensions are too small: {}x{}", + width, height + ))); + // throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' + + // height); + } + + let mut errorCorrectionLevel = ErrorCorrectionLevel::L; + let mut quietZone = QUIET_ZONE_SIZE; + // if (hints != null) { + if hints.contains_key(&EncodeHintType::ERROR_CORRECTION) { + if let EncodeHintValue::ErrorCorrection(ec_level) = + hints.get(&EncodeHintType::ERROR_CORRECTION).unwrap() + { + errorCorrectionLevel = ec_level.parse()?; + } + // errorCorrectionLevel = ErrorCorrectionLevel.valueOf(hints.get(EncodeHintType.ERROR_CORRECTION).toString()); + } + if hints.contains_key(&EncodeHintType::MARGIN) { + if let EncodeHintValue::Margin(margin) = hints.get(&EncodeHintType::MARGIN).unwrap() { + quietZone = margin.parse().unwrap(); + // quietZone = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString()); + } + + // quietZone = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString()); + } + // } + + let code = encoder::encode_with_hints(contents, errorCorrectionLevel, &hints)?; + + Self::renderRXingResult(&code, width, height, quietZone) + } +} + +impl QRCodeWriter { + // Note that the input matrix uses 0 == white, 1 == black, while the output matrix uses + // 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap). + fn renderRXingResult( + code: &QRCode, + width: i32, + height: i32, + quietZone: i32, + ) -> Result { + let input = code.getMatrix(); + if input.is_none() { + return Err(Exceptions::IllegalStateException( + "matrix is empty".to_owned(), + )); + // throw new IllegalStateException(); + } + + let input = input.as_ref().unwrap(); + + let inputWidth = input.getWidth() as i32; + let inputHeight = input.getHeight() as i32; + let qrWidth = inputWidth + (quietZone * 2); + let qrHeight = inputHeight + (quietZone * 2); + let outputWidth = width.max(qrWidth); + let outputHeight = height.max(qrHeight); + + let multiple = (outputWidth / qrWidth).min(outputHeight / qrHeight); + // Padding includes both the quiet zone and the extra white pixels to accommodate the requested + // dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone. + // If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will + // handle all the padding from 100x100 (the actual QR) up to 200x160. + let leftPadding = (outputWidth - (inputWidth * multiple)) / 2; + let topPadding = (outputHeight - (inputHeight * multiple)) / 2; + + let mut output = BitMatrix::new(outputWidth as u32, outputHeight as u32)?; + + let mut inputY = 0; + let mut outputY = topPadding; + while inputY < inputHeight { + // for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) { + // Write the contents of this row of the barcode + let mut inputX = 0; + let mut outputX = leftPadding; + while inputX < inputWidth { + // for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { + if input.get(inputX as u32, inputY as u32) == 1 { + output.setRegion( + outputX as u32, + outputY as u32, + multiple as u32, + multiple as u32, + ); + } + + inputX += 1; + outputX += multiple; + } + + inputY += 1; + outputY += multiple; + } + + Ok(output) + } +} diff --git a/tests/QRCodeBlackBox1TestCase.java b/tests/QRCodeBlackBox1TestCase.rs similarity index 54% rename from tests/QRCodeBlackBox1TestCase.java rename to tests/QRCodeBlackBox1TestCase.rs index 805db74..0f28c1e 100644 --- a/tests/QRCodeBlackBox1TestCase.java +++ b/tests/QRCodeBlackBox1TestCase.rs @@ -14,23 +14,26 @@ * limitations under the License. */ -package com.google.zxing.qrcode; +use rxing::qrcode::QRCodeReader; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; +mod common; /** * @author Sean Owen */ -public final class QRCodeBlackBox1TestCase extends AbstractBlackBoxTestCase { - public QRCodeBlackBox1TestCase() { - super("src/test/resources/blackbox/qrcode-1", new MultiFormatReader(), BarcodeFormat.QR_CODE); - addTest(17, 17, 0.0f); - addTest(14, 14, 90.0f); - addTest(17, 17, 180.0f); - addTest(14, 14, 270.0f); - } +#[test] +fn QRCodeBlackBox1TestCase() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/qrcode-1", + Box::new(QRCodeReader {}), + rxing::BarcodeFormat::QR_CODE, + ); + // super("src/test/resources/blackbox/qrcode-1", new MultiFormatReader(), BarcodeFormat.QR_CODE); + tester.addTest(17, 17, 0.0); + tester.addTest(14, 14, 90.0); + tester.addTest(17, 17, 180.0); + tester.addTest(14, 14, 270.0); + tester.testBlackBox(); } diff --git a/tests/QRCodeBlackBox2TestCase.java b/tests/QRCodeBlackBox2TestCase.rs similarity index 55% rename from tests/QRCodeBlackBox2TestCase.java rename to tests/QRCodeBlackBox2TestCase.rs index 7c11dad..e674924 100644 --- a/tests/QRCodeBlackBox2TestCase.java +++ b/tests/QRCodeBlackBox2TestCase.rs @@ -14,23 +14,22 @@ * limitations under the License. */ -package com.google.zxing.qrcode; +use rxing::{qrcode::QRCodeReader, BarcodeFormat}; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; +mod common; /** * @author Sean Owen */ -public final class QRCodeBlackBox2TestCase extends AbstractBlackBoxTestCase { - public QRCodeBlackBox2TestCase() { - super("src/test/resources/blackbox/qrcode-2", new MultiFormatReader(), BarcodeFormat.QR_CODE); - addTest(31, 31, 0.0f); - addTest(30, 30, 90.0f); - addTest(30, 30, 180.0f); - addTest(30, 30, 270.0f); + #[test] + fn QRCodeBlackBox2TestCase() { + let mut tester = common::AbstractBlackBoxTestCase::new("test_resources/blackbox/qrcode-2", Box::new(QRCodeReader{}), BarcodeFormat::QR_CODE); + tester.addTest(31, 31, 0.0); + tester.addTest(30, 30, 90.0); + tester.addTest(30, 30, 180.0); + tester.addTest(30, 30, 270.0); + + tester.testBlackBox(); } -} diff --git a/tests/QRCodeBlackBox3TestCase.java b/tests/QRCodeBlackBox3TestCase.rs similarity index 56% rename from tests/QRCodeBlackBox3TestCase.java rename to tests/QRCodeBlackBox3TestCase.rs index 9968608..ddf9d9e 100644 --- a/tests/QRCodeBlackBox3TestCase.java +++ b/tests/QRCodeBlackBox3TestCase.rs @@ -14,23 +14,22 @@ * limitations under the License. */ -package com.google.zxing.qrcode; +use rxing::{qrcode::QRCodeReader, BarcodeFormat}; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; +mod common; /** * @author dswitkin@google.com (Daniel Switkin) */ -public final class QRCodeBlackBox3TestCase extends AbstractBlackBoxTestCase { - public QRCodeBlackBox3TestCase() { - super("src/test/resources/blackbox/qrcode-3", new MultiFormatReader(), BarcodeFormat.QR_CODE); - addTest(38, 38, 0.0f); - addTest(39, 39, 90.0f); - addTest(36, 36, 180.0f); - addTest(39, 39, 270.0f); +#[test] +fn QRCodeBlackBox3TestCase() { + let mut tester = common::AbstractBlackBoxTestCase::new("test_resources/blackbox/qrcode-3", Box::new(QRCodeReader{}), BarcodeFormat::QR_CODE); + tester.addTest(38, 38, 0.0); + tester.addTest(39, 39, 90.0); + tester.addTest(36, 36, 180.0); + tester.addTest(39, 39, 270.0); + + tester.testBlackBox(); } -} diff --git a/tests/QRCodeBlackBox4TestCase.java b/tests/QRCodeBlackBox4TestCase.rs similarity index 59% rename from tests/QRCodeBlackBox4TestCase.java rename to tests/QRCodeBlackBox4TestCase.rs index 8693aff..55df756 100644 --- a/tests/QRCodeBlackBox4TestCase.java +++ b/tests/QRCodeBlackBox4TestCase.rs @@ -14,25 +14,24 @@ * limitations under the License. */ -package com.google.zxing.qrcode; +use rxing::{qrcode::QRCodeReader, BarcodeFormat}; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; +mod common; /** * Tests of various QR Codes from t-shirts, which are notoriously not flat. * * @author dswitkin@google.com (Daniel Switkin) */ -public final class QRCodeBlackBox4TestCase extends AbstractBlackBoxTestCase { - public QRCodeBlackBox4TestCase() { - super("src/test/resources/blackbox/qrcode-4", new MultiFormatReader(), BarcodeFormat.QR_CODE); - addTest(36, 36, 0.0f); - addTest(35, 35, 90.0f); - addTest(35, 35, 180.0f); - addTest(35, 35, 270.0f); +#[test] +fn QRCodeBlackBox4TestCase() { + let mut tester = common::AbstractBlackBoxTestCase::new("test_resources/blackbox/qrcode-4", Box::new(QRCodeReader{}), BarcodeFormat::QR_CODE); + tester.addTest(36, 36, 0.0); + tester.addTest(35, 35, 90.0); + tester.addTest(35, 35, 180.0); + tester.addTest(35, 35, 270.0); + + tester.testBlackBox(); } -} diff --git a/tests/QRCodeBlackBox5TestCase.java b/tests/QRCodeBlackBox5TestCase.rs similarity index 64% rename from tests/QRCodeBlackBox5TestCase.java rename to tests/QRCodeBlackBox5TestCase.rs index 404bc45..01332f6 100644 --- a/tests/QRCodeBlackBox5TestCase.java +++ b/tests/QRCodeBlackBox5TestCase.rs @@ -14,11 +14,9 @@ * limitations under the License. */ -package com.google.zxing.qrcode; +use rxing::{BarcodeFormat, qrcode::QRCodeReader}; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; +mod common; /** * Some very difficult exposure conditions including self-shadowing, which happens a lot when @@ -27,14 +25,15 @@ import com.google.zxing.common.AbstractBlackBoxTestCase; * * @author dswitkin@google.com (Daniel Switkin) */ -public final class QRCodeBlackBox5TestCase extends AbstractBlackBoxTestCase { - public QRCodeBlackBox5TestCase() { - super("src/test/resources/blackbox/qrcode-5", new MultiFormatReader(), BarcodeFormat.QR_CODE); - addTest(19, 19, 0.0f); - addTest(19, 19, 90.0f); - addTest(19, 19, 180.0f); - addTest(19, 19, 270.0f); +#[test] +fn QRCodeBlackBox5TestCase() { + let mut tester = common::AbstractBlackBoxTestCase::new("test_resources/blackbox/qrcode-5", Box::new(QRCodeReader{}), BarcodeFormat::QR_CODE); + tester.addTest(19, 19, 0.0); + tester.addTest(19, 19, 90.0); + tester.addTest(19, 19, 180.0); + tester.addTest(19, 19, 270.0); + + tester.testBlackBox(); } -} diff --git a/tests/QRCodeBlackBox6TestCase.java b/tests/QRCodeBlackBox6TestCase.rs similarity index 59% rename from tests/QRCodeBlackBox6TestCase.java rename to tests/QRCodeBlackBox6TestCase.rs index 531ffa6..cbfcec4 100644 --- a/tests/QRCodeBlackBox6TestCase.java +++ b/tests/QRCodeBlackBox6TestCase.rs @@ -14,24 +14,22 @@ * limitations under the License. */ -package com.google.zxing.qrcode; +use rxing::{qrcode::QRCodeReader, BarcodeFormat}; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; +mod common; /** * These tests are supplied by Tim Gernat and test finder pattern detection at small size and under * rotation, which was a weak spot. */ -public final class QRCodeBlackBox6TestCase extends AbstractBlackBoxTestCase { - public QRCodeBlackBox6TestCase() { - super("src/test/resources/blackbox/qrcode-6", new MultiFormatReader(), BarcodeFormat.QR_CODE); - addTest(15, 15, 0.0f); - addTest(14, 14, 90.0f); - addTest(13, 13, 180.0f); - addTest(14, 14, 270.0f); +#[test] +fn QRCodeBlackBox6TestCase() { + let mut tester = common::AbstractBlackBoxTestCase::new("test_resources/blackbox/qrcode-6", Box::new(QRCodeReader{}), BarcodeFormat::QR_CODE); + tester.addTest(15, 15, 0.0); + tester.addTest(14, 14, 90.0); + tester.addTest(13, 13, 180.0); + tester.addTest(14, 14, 270.0); + + tester.testBlackBox(); } - -} diff --git a/tests/common/abstract_black_box_test_case.rs b/tests/common/abstract_black_box_test_case.rs index 157fbb3..1919556 100644 --- a/tests/common/abstract_black_box_test_case.rs +++ b/tests/common/abstract_black_box_test_case.rs @@ -189,7 +189,7 @@ impl AbstractBlackBoxTestCase { RXingResultMetadataValue::Orientation(v.parse().unwrap_or_default()) } RXingResultMetadataType::BYTE_SEGMENTS => { - RXingResultMetadataValue::ByteSegments(v.into_bytes()) + RXingResultMetadataValue::ByteSegments(vec![v.into_bytes()]) } RXingResultMetadataType::ERROR_CORRECTION_LEVEL => { RXingResultMetadataValue::ErrorCorrectionLevel(v)