From 8e69cfec54e259671d4b8a06976ea83e125a5fd1 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sat, 1 Oct 2022 17:57:53 -0500 Subject: [PATCH] mask util port and pass --- src/qrcode/encoder/MaskUtil.java | 224 ------------------ src/qrcode/encoder/MaskUtilTestCase.java | 279 ----------------------- src/qrcode/encoder/MaskUtilTestCase.rs | 279 +++++++++++++++++++++++ src/qrcode/encoder/mask_util.rs | 269 ++++++++++++++++++++++ src/qrcode/encoder/mod.rs | 5 +- 5 files changed, 552 insertions(+), 504 deletions(-) delete mode 100644 src/qrcode/encoder/MaskUtil.java delete mode 100644 src/qrcode/encoder/MaskUtilTestCase.java create mode 100644 src/qrcode/encoder/MaskUtilTestCase.rs create mode 100644 src/qrcode/encoder/mask_util.rs diff --git a/src/qrcode/encoder/MaskUtil.java b/src/qrcode/encoder/MaskUtil.java deleted file mode 100644 index 568f6ff..0000000 --- a/src/qrcode/encoder/MaskUtil.java +++ /dev/null @@ -1,224 +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.encoder; - -/** - * @author Satoru Takabayashi - * @author Daniel Switkin - * @author Sean Owen - */ -final class MaskUtil { - - // Penalty weights from section 6.8.2.1 - private static final int N1 = 3; - private static final int N2 = 3; - private static final int N3 = 40; - private static final int N4 = 10; - - private MaskUtil() { - // do nothing - } - - /** - * Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and - * give penalty to them. Example: 00000 or 11111. - */ - static int applyMaskPenaltyRule1(ByteMatrix matrix) { - return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false); - } - - /** - * Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give - * penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a - * penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block. - */ - static int applyMaskPenaltyRule2(ByteMatrix matrix) { - int penalty = 0; - byte[][] array = matrix.getArray(); - int width = matrix.getWidth(); - int height = matrix.getHeight(); - for (int y = 0; y < height - 1; y++) { - byte[] arrayY = array[y]; - for (int x = 0; x < width - 1; x++) { - int value = arrayY[x]; - if (value == arrayY[x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1]) { - penalty++; - } - } - } - return N2 * penalty; - } - - /** - * Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4 - * starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we - * find patterns like 000010111010000, we give penalty once. - */ - static int applyMaskPenaltyRule3(ByteMatrix matrix) { - int numPenalties = 0; - byte[][] array = matrix.getArray(); - int width = matrix.getWidth(); - int height = matrix.getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - byte[] arrayY = array[y]; // We can at least optimize this access - if (x + 6 < width && - arrayY[x] == 1 && - arrayY[x + 1] == 0 && - arrayY[x + 2] == 1 && - arrayY[x + 3] == 1 && - arrayY[x + 4] == 1 && - arrayY[x + 5] == 0 && - arrayY[x + 6] == 1 && - (isWhiteHorizontal(arrayY, x - 4, x) || isWhiteHorizontal(arrayY, x + 7, x + 11))) { - numPenalties++; - } - if (y + 6 < height && - array[y][x] == 1 && - array[y + 1][x] == 0 && - array[y + 2][x] == 1 && - array[y + 3][x] == 1 && - array[y + 4][x] == 1 && - array[y + 5][x] == 0 && - array[y + 6][x] == 1 && - (isWhiteVertical(array, x, y - 4, y) || isWhiteVertical(array, x, y + 7, y + 11))) { - numPenalties++; - } - } - } - return numPenalties * N3; - } - - private static boolean isWhiteHorizontal(byte[] rowArray, int from, int to) { - if (from < 0 || rowArray.length < to) { - return false; - } - for (int i = from; i < to; i++) { - if (rowArray[i] == 1) { - return false; - } - } - return true; - } - - private static boolean isWhiteVertical(byte[][] array, int col, int from, int to) { - if (from < 0 || array.length < to) { - return false; - } - for (int i = from; i < to; i++) { - if (array[i][col] == 1) { - return false; - } - } - return true; - } - - /** - * Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give - * penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance. - */ - static int applyMaskPenaltyRule4(ByteMatrix matrix) { - int numDarkCells = 0; - byte[][] array = matrix.getArray(); - int width = matrix.getWidth(); - int height = matrix.getHeight(); - for (int y = 0; y < height; y++) { - byte[] arrayY = array[y]; - for (int x = 0; x < width; x++) { - if (arrayY[x] == 1) { - numDarkCells++; - } - } - } - int numTotalCells = matrix.getHeight() * matrix.getWidth(); - int fivePercentVariances = Math.abs(numDarkCells * 2 - numTotalCells) * 10 / numTotalCells; - return fivePercentVariances * N4; - } - - /** - * Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask - * pattern conditions. - */ - static boolean getDataMaskBit(int maskPattern, int x, int y) { - int intermediate; - int temp; - switch (maskPattern) { - case 0: - intermediate = (y + x) & 0x1; - break; - case 1: - intermediate = y & 0x1; - break; - case 2: - intermediate = x % 3; - break; - case 3: - intermediate = (y + x) % 3; - break; - case 4: - intermediate = ((y / 2) + (x / 3)) & 0x1; - break; - case 5: - temp = y * x; - intermediate = (temp & 0x1) + (temp % 3); - break; - case 6: - temp = y * x; - intermediate = ((temp & 0x1) + (temp % 3)) & 0x1; - break; - case 7: - temp = y * x; - intermediate = ((temp % 3) + ((y + x) & 0x1)) & 0x1; - break; - default: - throw new IllegalArgumentException("Invalid mask pattern: " + maskPattern); - } - return intermediate == 0; - } - - /** - * Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both - * vertical and horizontal orders respectively. - */ - private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, boolean isHorizontal) { - int penalty = 0; - int iLimit = isHorizontal ? matrix.getHeight() : matrix.getWidth(); - int jLimit = isHorizontal ? matrix.getWidth() : matrix.getHeight(); - byte[][] array = matrix.getArray(); - for (int i = 0; i < iLimit; i++) { - int numSameBitCells = 0; - int prevBit = -1; - for (int j = 0; j < jLimit; j++) { - int bit = isHorizontal ? array[i][j] : array[j][i]; - if (bit == prevBit) { - numSameBitCells++; - } else { - if (numSameBitCells >= 5) { - penalty += N1 + (numSameBitCells - 5); - } - numSameBitCells = 1; // Include the cell itself. - prevBit = bit; - } - } - if (numSameBitCells >= 5) { - penalty += N1 + (numSameBitCells - 5); - } - } - return penalty; - } - -} diff --git a/src/qrcode/encoder/MaskUtilTestCase.java b/src/qrcode/encoder/MaskUtilTestCase.java deleted file mode 100644 index 2cb6a29..0000000 --- a/src/qrcode/encoder/MaskUtilTestCase.java +++ /dev/null @@ -1,279 +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.encoder; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author satorux@google.com (Satoru Takabayashi) - creator - * @author mysen@google.com (Chris Mysen) - ported from C++ - */ -public final class MaskUtilTestCase extends Assert { - - @Test - public void testApplyMaskPenaltyRule1() { - ByteMatrix matrix = new ByteMatrix(4, 1); - matrix.set(0, 0, 0); - matrix.set(1, 0, 0); - matrix.set(2, 0, 0); - matrix.set(3, 0, 0); - assertEquals(0, MaskUtil.applyMaskPenaltyRule1(matrix)); - // Horizontal. - matrix = new ByteMatrix(6, 1); - matrix.set(0, 0, 0); - matrix.set(1, 0, 0); - matrix.set(2, 0, 0); - matrix.set(3, 0, 0); - matrix.set(4, 0, 0); - matrix.set(5, 0, 1); - assertEquals(3, MaskUtil.applyMaskPenaltyRule1(matrix)); - matrix.set(5, 0, 0); - assertEquals(4, MaskUtil.applyMaskPenaltyRule1(matrix)); - // Vertical. - matrix = new ByteMatrix(1, 6); - matrix.set(0, 0, 0); - matrix.set(0, 1, 0); - matrix.set(0, 2, 0); - matrix.set(0, 3, 0); - matrix.set(0, 4, 0); - matrix.set(0, 5, 1); - assertEquals(3, MaskUtil.applyMaskPenaltyRule1(matrix)); - matrix.set(0, 5, 0); - assertEquals(4, MaskUtil.applyMaskPenaltyRule1(matrix)); - } - - @Test - public void testApplyMaskPenaltyRule2() { - ByteMatrix matrix = new ByteMatrix(1, 1); - matrix.set(0, 0, 0); - assertEquals(0, MaskUtil.applyMaskPenaltyRule2(matrix)); - matrix = new ByteMatrix(2, 2); - matrix.set(0, 0, 0); - matrix.set(1, 0, 0); - matrix.set(0, 1, 0); - matrix.set(1, 1, 1); - assertEquals(0, MaskUtil.applyMaskPenaltyRule2(matrix)); - matrix = new ByteMatrix(2, 2); - matrix.set(0, 0, 0); - matrix.set(1, 0, 0); - matrix.set(0, 1, 0); - matrix.set(1, 1, 0); - assertEquals(3, MaskUtil.applyMaskPenaltyRule2(matrix)); - matrix = new ByteMatrix(3, 3); - matrix.set(0, 0, 0); - matrix.set(1, 0, 0); - matrix.set(2, 0, 0); - matrix.set(0, 1, 0); - matrix.set(1, 1, 0); - matrix.set(2, 1, 0); - matrix.set(0, 2, 0); - matrix.set(1, 2, 0); - matrix.set(2, 2, 0); - // Four instances of 2x2 blocks. - assertEquals(3 * 4, MaskUtil.applyMaskPenaltyRule2(matrix)); - } - - @Test - public void testApplyMaskPenaltyRule3() { - // Horizontal 00001011101. - ByteMatrix matrix = new ByteMatrix(11, 1); - matrix.set(0, 0, 0); - matrix.set(1, 0, 0); - matrix.set(2, 0, 0); - matrix.set(3, 0, 0); - matrix.set(4, 0, 1); - matrix.set(5, 0, 0); - matrix.set(6, 0, 1); - matrix.set(7, 0, 1); - matrix.set(8, 0, 1); - matrix.set(9, 0, 0); - matrix.set(10, 0, 1); - assertEquals(40, MaskUtil.applyMaskPenaltyRule3(matrix)); - // Horizontal 10111010000. - matrix = new ByteMatrix(11, 1); - matrix.set(0, 0, 1); - matrix.set(1, 0, 0); - matrix.set(2, 0, 1); - matrix.set(3, 0, 1); - matrix.set(4, 0, 1); - matrix.set(5, 0, 0); - matrix.set(6, 0, 1); - matrix.set(7, 0, 0); - matrix.set(8, 0, 0); - matrix.set(9, 0, 0); - matrix.set(10, 0, 0); - assertEquals(40, MaskUtil.applyMaskPenaltyRule3(matrix)); - // Horizontal 1011101. - matrix = new ByteMatrix(7, 1); - matrix.set(0, 0, 1); - matrix.set(1, 0, 0); - matrix.set(2, 0, 1); - matrix.set(3, 0, 1); - matrix.set(4, 0, 1); - matrix.set(5, 0, 0); - matrix.set(6, 0, 1); - assertEquals(0, MaskUtil.applyMaskPenaltyRule3(matrix)); - // Vertical 00001011101. - matrix = new ByteMatrix(1, 11); - matrix.set(0, 0, 0); - matrix.set(0, 1, 0); - matrix.set(0, 2, 0); - matrix.set(0, 3, 0); - matrix.set(0, 4, 1); - matrix.set(0, 5, 0); - matrix.set(0, 6, 1); - matrix.set(0, 7, 1); - matrix.set(0, 8, 1); - matrix.set(0, 9, 0); - matrix.set(0, 10, 1); - assertEquals(40, MaskUtil.applyMaskPenaltyRule3(matrix)); - // Vertical 10111010000. - matrix = new ByteMatrix(1, 11); - matrix.set(0, 0, 1); - matrix.set(0, 1, 0); - matrix.set(0, 2, 1); - matrix.set(0, 3, 1); - matrix.set(0, 4, 1); - matrix.set(0, 5, 0); - matrix.set(0, 6, 1); - matrix.set(0, 7, 0); - matrix.set(0, 8, 0); - matrix.set(0, 9, 0); - matrix.set(0, 10, 0); - // Vertical 1011101. - matrix = new ByteMatrix(1, 7); - matrix.set(0, 0, 1); - matrix.set(0, 1, 0); - matrix.set(0, 2, 1); - matrix.set(0, 3, 1); - matrix.set(0, 4, 1); - matrix.set(0, 5, 0); - matrix.set(0, 6, 1); - assertEquals(0, MaskUtil.applyMaskPenaltyRule3(matrix)); - } - - @Test - public void testApplyMaskPenaltyRule4() { - // Dark cell ratio = 0% - ByteMatrix matrix = new ByteMatrix(1, 1); - matrix.set(0, 0, 0); - assertEquals(100, MaskUtil.applyMaskPenaltyRule4(matrix)); - // Dark cell ratio = 5% - matrix = new ByteMatrix(2, 1); - matrix.set(0, 0, 0); - matrix.set(0, 0, 1); - assertEquals(0, MaskUtil.applyMaskPenaltyRule4(matrix)); - // Dark cell ratio = 66.67% - matrix = new ByteMatrix(6, 1); - matrix.set(0, 0, 0); - matrix.set(1, 0, 1); - matrix.set(2, 0, 1); - matrix.set(3, 0, 1); - matrix.set(4, 0, 1); - matrix.set(5, 0, 0); - assertEquals(30, MaskUtil.applyMaskPenaltyRule4(matrix)); - } - - private static boolean testGetDataMaskBitInternal(int maskPattern, int[][] expected) { - for (int x = 0; x < 6; ++x) { - for (int y = 0; y < 6; ++y) { - if ((expected[y][x] == 1) != MaskUtil.getDataMaskBit(maskPattern, x, y)) { - return false; - } - } - } - return true; - } - - // See mask patterns on the page 43 of JISX0510:2004. - @Test - public void testGetDataMaskBit() { - int[][] mask0 = { - {1, 0, 1, 0, 1, 0}, - {0, 1, 0, 1, 0, 1}, - {1, 0, 1, 0, 1, 0}, - {0, 1, 0, 1, 0, 1}, - {1, 0, 1, 0, 1, 0}, - {0, 1, 0, 1, 0, 1}, - }; - assertTrue(testGetDataMaskBitInternal(0, mask0)); - int[][] mask1 = { - {1, 1, 1, 1, 1, 1}, - {0, 0, 0, 0, 0, 0}, - {1, 1, 1, 1, 1, 1}, - {0, 0, 0, 0, 0, 0}, - {1, 1, 1, 1, 1, 1}, - {0, 0, 0, 0, 0, 0}, - }; - assertTrue(testGetDataMaskBitInternal(1, mask1)); - int[][] mask2 = { - {1, 0, 0, 1, 0, 0}, - {1, 0, 0, 1, 0, 0}, - {1, 0, 0, 1, 0, 0}, - {1, 0, 0, 1, 0, 0}, - {1, 0, 0, 1, 0, 0}, - {1, 0, 0, 1, 0, 0}, - }; - assertTrue(testGetDataMaskBitInternal(2, mask2)); - int[][] mask3 = { - {1, 0, 0, 1, 0, 0}, - {0, 0, 1, 0, 0, 1}, - {0, 1, 0, 0, 1, 0}, - {1, 0, 0, 1, 0, 0}, - {0, 0, 1, 0, 0, 1}, - {0, 1, 0, 0, 1, 0}, - }; - assertTrue(testGetDataMaskBitInternal(3, mask3)); - int[][] mask4 = { - {1, 1, 1, 0, 0, 0}, - {1, 1, 1, 0, 0, 0}, - {0, 0, 0, 1, 1, 1}, - {0, 0, 0, 1, 1, 1}, - {1, 1, 1, 0, 0, 0}, - {1, 1, 1, 0, 0, 0}, - }; - assertTrue(testGetDataMaskBitInternal(4, mask4)); - int[][] mask5 = { - {1, 1, 1, 1, 1, 1}, - {1, 0, 0, 0, 0, 0}, - {1, 0, 0, 1, 0, 0}, - {1, 0, 1, 0, 1, 0}, - {1, 0, 0, 1, 0, 0}, - {1, 0, 0, 0, 0, 0}, - }; - assertTrue(testGetDataMaskBitInternal(5, mask5)); - int[][] mask6 = { - {1, 1, 1, 1, 1, 1}, - {1, 1, 1, 0, 0, 0}, - {1, 1, 0, 1, 1, 0}, - {1, 0, 1, 0, 1, 0}, - {1, 0, 1, 1, 0, 1}, - {1, 0, 0, 0, 1, 1}, - }; - assertTrue(testGetDataMaskBitInternal(6, mask6)); - int[][] mask7 = { - {1, 0, 1, 0, 1, 0}, - {0, 0, 0, 1, 1, 1}, - {1, 0, 0, 0, 1, 1}, - {0, 1, 0, 1, 0, 1}, - {1, 1, 1, 0, 0, 0}, - {0, 1, 1, 1, 0, 0}, - }; - assertTrue(testGetDataMaskBitInternal(7, mask7)); - } -} diff --git a/src/qrcode/encoder/MaskUtilTestCase.rs b/src/qrcode/encoder/MaskUtilTestCase.rs new file mode 100644 index 0000000..5186b51 --- /dev/null +++ b/src/qrcode/encoder/MaskUtilTestCase.rs @@ -0,0 +1,279 @@ +/* + * 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::qrcode::encoder::{mask_util, ByteMatrix}; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author mysen@google.com (Chris Mysen) - ported from C++ + */ + +#[test] +fn testApplyMaskPenaltyRule1() { + let mut matrix = ByteMatrix::new(4, 1); + matrix.set(0, 0, 0); + matrix.set(1, 0, 0); + matrix.set(2, 0, 0); + matrix.set(3, 0, 0); + assert_eq!(0, mask_util::applyMaskPenaltyRule1(&matrix)); + // Horizontal. + let mut matrix = ByteMatrix::new(6, 1); + matrix.set(0, 0, 0); + matrix.set(1, 0, 0); + matrix.set(2, 0, 0); + matrix.set(3, 0, 0); + matrix.set(4, 0, 0); + matrix.set(5, 0, 1); + assert_eq!(3, mask_util::applyMaskPenaltyRule1(&matrix)); + matrix.set(5, 0, 0); + assert_eq!(4, mask_util::applyMaskPenaltyRule1(&matrix)); + // Vertical. + let mut matrix = ByteMatrix::new(1, 6); + matrix.set(0, 0, 0); + matrix.set(0, 1, 0); + matrix.set(0, 2, 0); + matrix.set(0, 3, 0); + matrix.set(0, 4, 0); + matrix.set(0, 5, 1); + assert_eq!(3, mask_util::applyMaskPenaltyRule1(&matrix)); + matrix.set(0, 5, 0); + assert_eq!(4, mask_util::applyMaskPenaltyRule1(&matrix)); +} + +#[test] +fn testApplyMaskPenaltyRule2() { + let mut matrix = ByteMatrix::new(1, 1); + matrix.set(0, 0, 0); + assert_eq!(0, mask_util::applyMaskPenaltyRule2(&matrix)); + let mut matrix = ByteMatrix::new(2, 2); + matrix.set(0, 0, 0); + matrix.set(1, 0, 0); + matrix.set(0, 1, 0); + matrix.set(1, 1, 1); + assert_eq!(0, mask_util::applyMaskPenaltyRule2(&matrix)); + let mut matrix = ByteMatrix::new(2, 2); + matrix.set(0, 0, 0); + matrix.set(1, 0, 0); + matrix.set(0, 1, 0); + matrix.set(1, 1, 0); + assert_eq!(3, mask_util::applyMaskPenaltyRule2(&matrix)); + let mut matrix = ByteMatrix::new(3, 3); + matrix.set(0, 0, 0); + matrix.set(1, 0, 0); + matrix.set(2, 0, 0); + matrix.set(0, 1, 0); + matrix.set(1, 1, 0); + matrix.set(2, 1, 0); + matrix.set(0, 2, 0); + matrix.set(1, 2, 0); + matrix.set(2, 2, 0); + // Four instances of 2x2 blocks. + assert_eq!(3 * 4, mask_util::applyMaskPenaltyRule2(&matrix)); +} + +#[test] +fn testApplyMaskPenaltyRule3() { + // Horizontal 00001011101. + let mut matrix = ByteMatrix::new(11, 1); + matrix.set(0, 0, 0); + matrix.set(1, 0, 0); + matrix.set(2, 0, 0); + matrix.set(3, 0, 0); + matrix.set(4, 0, 1); + matrix.set(5, 0, 0); + matrix.set(6, 0, 1); + matrix.set(7, 0, 1); + matrix.set(8, 0, 1); + matrix.set(9, 0, 0); + matrix.set(10, 0, 1); + assert_eq!(40, mask_util::applyMaskPenaltyRule3(&matrix)); + // Horizontal 10111010000. + let mut matrix = ByteMatrix::new(11, 1); + matrix.set(0, 0, 1); + matrix.set(1, 0, 0); + matrix.set(2, 0, 1); + matrix.set(3, 0, 1); + matrix.set(4, 0, 1); + matrix.set(5, 0, 0); + matrix.set(6, 0, 1); + matrix.set(7, 0, 0); + matrix.set(8, 0, 0); + matrix.set(9, 0, 0); + matrix.set(10, 0, 0); + assert_eq!(40, mask_util::applyMaskPenaltyRule3(&matrix)); + // Horizontal 1011101. + let mut matrix = ByteMatrix::new(7, 1); + matrix.set(0, 0, 1); + matrix.set(1, 0, 0); + matrix.set(2, 0, 1); + matrix.set(3, 0, 1); + matrix.set(4, 0, 1); + matrix.set(5, 0, 0); + matrix.set(6, 0, 1); + assert_eq!(0, mask_util::applyMaskPenaltyRule3(&matrix)); + // Vertical 00001011101. + let mut matrix = ByteMatrix::new(1, 11); + matrix.set(0, 0, 0); + matrix.set(0, 1, 0); + matrix.set(0, 2, 0); + matrix.set(0, 3, 0); + matrix.set(0, 4, 1); + matrix.set(0, 5, 0); + matrix.set(0, 6, 1); + matrix.set(0, 7, 1); + matrix.set(0, 8, 1); + matrix.set(0, 9, 0); + matrix.set(0, 10, 1); + assert_eq!(40, mask_util::applyMaskPenaltyRule3(&matrix)); + // Vertical 10111010000. + let mut matrix = ByteMatrix::new(1, 11); + matrix.set(0, 0, 1); + matrix.set(0, 1, 0); + matrix.set(0, 2, 1); + matrix.set(0, 3, 1); + matrix.set(0, 4, 1); + matrix.set(0, 5, 0); + matrix.set(0, 6, 1); + matrix.set(0, 7, 0); + matrix.set(0, 8, 0); + matrix.set(0, 9, 0); + matrix.set(0, 10, 0); + // Vertical 1011101. + let mut matrix = ByteMatrix::new(1, 7); + matrix.set(0, 0, 1); + matrix.set(0, 1, 0); + matrix.set(0, 2, 1); + matrix.set(0, 3, 1); + matrix.set(0, 4, 1); + matrix.set(0, 5, 0); + matrix.set(0, 6, 1); + assert_eq!(0, mask_util::applyMaskPenaltyRule3(&matrix)); +} + +#[test] +fn testApplyMaskPenaltyRule4() { + // Dark cell ratio = 0% + let mut matrix = ByteMatrix::new(1, 1); + matrix.set(0, 0, 0); + assert_eq!(100, mask_util::applyMaskPenaltyRule4(&matrix)); + // Dark cell ratio = 5% + let mut matrix = ByteMatrix::new(2, 1); + matrix.set(0, 0, 0); + matrix.set(0, 0, 1); + assert_eq!(0, mask_util::applyMaskPenaltyRule4(&matrix)); + // Dark cell ratio = 66.67% + let mut matrix = ByteMatrix::new(6, 1); + matrix.set(0, 0, 0); + matrix.set(1, 0, 1); + matrix.set(2, 0, 1); + matrix.set(3, 0, 1); + matrix.set(4, 0, 1); + matrix.set(5, 0, 0); + assert_eq!(30, mask_util::applyMaskPenaltyRule4(&matrix)); +} + +fn testGetDataMaskBitInternal(maskPattern: u32, expected: &Vec>) -> bool { + for x in 0..6 { + // for (int x = 0; x < 6; ++x) { + for y in 0..6 { + // for (int y = 0; y < 6; ++y) { + if (expected[y][x] == 1) + != mask_util::getDataMaskBit(maskPattern, x as u32, y as u32) + .expect("should never fail") + { + return false; + } + } + } + return true; +} + +// See mask patterns on the page 43 of JISX0510:2004. +#[test] +fn testGetDataMaskBit() { + let mask0 = vec![ + vec![1, 0, 1, 0, 1, 0], + vec![0, 1, 0, 1, 0, 1], + vec![1, 0, 1, 0, 1, 0], + vec![0, 1, 0, 1, 0, 1], + vec![1, 0, 1, 0, 1, 0], + vec![0, 1, 0, 1, 0, 1], + ]; + assert!(testGetDataMaskBitInternal(0, &mask0)); + let mask1 = vec![ + vec![1, 1, 1, 1, 1, 1], + vec![0, 0, 0, 0, 0, 0], + vec![1, 1, 1, 1, 1, 1], + vec![0, 0, 0, 0, 0, 0], + vec![1, 1, 1, 1, 1, 1], + vec![0, 0, 0, 0, 0, 0], + ]; + assert!(testGetDataMaskBitInternal(1, &mask1)); + let mask2 = vec![ + vec![1, 0, 0, 1, 0, 0], + vec![1, 0, 0, 1, 0, 0], + vec![1, 0, 0, 1, 0, 0], + vec![1, 0, 0, 1, 0, 0], + vec![1, 0, 0, 1, 0, 0], + vec![1, 0, 0, 1, 0, 0], + ]; + assert!(testGetDataMaskBitInternal(2, &mask2)); + let mask3 = vec![ + vec![1, 0, 0, 1, 0, 0], + vec![0, 0, 1, 0, 0, 1], + vec![0, 1, 0, 0, 1, 0], + vec![1, 0, 0, 1, 0, 0], + vec![0, 0, 1, 0, 0, 1], + vec![0, 1, 0, 0, 1, 0], + ]; + assert!(testGetDataMaskBitInternal(3, &mask3)); + let mask4 = vec![ + vec![1, 1, 1, 0, 0, 0], + vec![1, 1, 1, 0, 0, 0], + vec![0, 0, 0, 1, 1, 1], + vec![0, 0, 0, 1, 1, 1], + vec![1, 1, 1, 0, 0, 0], + vec![1, 1, 1, 0, 0, 0], + ]; + assert!(testGetDataMaskBitInternal(4, &mask4)); + let mask5 = vec![ + vec![1, 1, 1, 1, 1, 1], + vec![1, 0, 0, 0, 0, 0], + vec![1, 0, 0, 1, 0, 0], + vec![1, 0, 1, 0, 1, 0], + vec![1, 0, 0, 1, 0, 0], + vec![1, 0, 0, 0, 0, 0], + ]; + assert!(testGetDataMaskBitInternal(5, &mask5)); + let mask6 = vec![ + vec![1, 1, 1, 1, 1, 1], + vec![1, 1, 1, 0, 0, 0], + vec![1, 1, 0, 1, 1, 0], + vec![1, 0, 1, 0, 1, 0], + vec![1, 0, 1, 1, 0, 1], + vec![1, 0, 0, 0, 1, 1], + ]; + assert!(testGetDataMaskBitInternal(6, &mask6)); + let mask7 = vec![ + vec![1, 0, 1, 0, 1, 0], + vec![0, 0, 0, 1, 1, 1], + vec![1, 0, 0, 0, 1, 1], + vec![0, 1, 0, 1, 0, 1], + vec![1, 1, 1, 0, 0, 0], + vec![0, 1, 1, 1, 0, 0], + ]; + assert!(testGetDataMaskBitInternal(7, &mask7)); +} diff --git a/src/qrcode/encoder/mask_util.rs b/src/qrcode/encoder/mask_util.rs new file mode 100644 index 0000000..febbf8f --- /dev/null +++ b/src/qrcode/encoder/mask_util.rs @@ -0,0 +1,269 @@ +/* + * 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::Exceptions; + +use super::ByteMatrix; + +/** + * @author Satoru Takabayashi + * @author Daniel Switkin + * @author Sean Owen + */ + +// Penalty weights from section 6.8.2.1 +const N1: u32 = 3; +const N2: u32 = 3; +const N3: u32 = 40; +const N4: u32 = 10; + +/** + * Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and + * give penalty to them. Example: 00000 or 11111. + */ +pub fn applyMaskPenaltyRule1(matrix: &ByteMatrix) -> u32 { + applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false) +} + +/** + * Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give + * penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a + * penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block. + */ +pub fn applyMaskPenaltyRule2(matrix: &ByteMatrix) -> u32 { + let mut penalty = 0; + let array = matrix.getArray(); + let width = matrix.getWidth(); + let height = matrix.getHeight(); + for y in 0..(height -1) as usize { + // for (int y = 0; y < height - 1; y++) { + let arrayY = &array[y]; + for x in 0..(width -1) as usize { + // for (int x = 0; x < width - 1; x++) { + let value = arrayY[x]; + if value == arrayY[x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1] { + penalty += 1; + } + } + } + N2 * penalty +} + +/** + * Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4 + * starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we + * find patterns like 000010111010000, we give penalty once. + */ +pub fn applyMaskPenaltyRule3(matrix: &ByteMatrix) -> u32 { + let mut numPenalties = 0; + let array = matrix.getArray(); + let width = matrix.getWidth(); + let height = matrix.getHeight(); + for y in 0..height as usize { + // for (int y = 0; y < height; y++) { + for x in 0..width as usize { + // for (int x = 0; x < width; x++) { + let arrayY = &array[y]; // We can at least optimize this access + if x + 6 < width as usize + && arrayY[x] == 1 + && arrayY[x + 1] == 0 + && arrayY[x + 2] == 1 + && arrayY[x + 3] == 1 + && arrayY[x + 4] == 1 + && arrayY[x + 5] == 0 + && arrayY[x + 6] == 1 + && (isWhiteHorizontal(&arrayY, x as i32 - 4, x as u32) + || isWhiteHorizontal(&arrayY, x as i32 + 7, x as u32 + 11)) + { + numPenalties += 1; + } + if y + 6 < height as usize + && array[y][x] == 1 + && array[y + 1][x] == 0 + && array[y + 2][x] == 1 + && array[y + 3][x] == 1 + && array[y + 4][x] == 1 + && array[y + 5][x] == 0 + && array[y + 6][x] == 1 + && (isWhiteVertical(array, x as u32, y as i32 - 4, y as u32) + || isWhiteVertical(array, x as u32, y as i32 + 7, y as u32 + 11)) + { + numPenalties += 1; + } + } + } + numPenalties * N3 +} + +pub fn isWhiteHorizontal(rowArray: &[u8], from: i32, to: u32) -> bool { + if from < 0 || rowArray.len() < to as usize { + return false; + } + for i in from..to as i32 { + // for (int i = from; i < to; i++) { + if rowArray[i as usize] == 1 { + return false; + } + } + return true; +} + +pub fn isWhiteVertical(array: &Vec>, col: u32, from: i32, to: u32) -> bool { + if from < 0 || array.len() < to as usize { + return false; + } + for i in from..to as i32 { + // for (int i = from; i < to; i++) { + if array[i as usize][col as usize] == 1 { + return false; + } + } + return true; +} + +/** + * Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give + * penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance. + */ +pub fn applyMaskPenaltyRule4(matrix: &ByteMatrix) -> u32 { + let mut numDarkCells = 0; + let array = matrix.getArray(); + let width = matrix.getWidth(); + let height = matrix.getHeight(); + for y in 0..height as usize { + // for (int y = 0; y < height; y++) { + let arrayY = &array[y]; + for x in 0..width as usize { + // for (int x = 0; x < width; x++) { + if arrayY[x] == 1 { + numDarkCells += 1; + } + } + } + let numTotalCells = matrix.getHeight() * matrix.getWidth(); + let fivePercentVariances = (numDarkCells as i64 * 2 - numTotalCells as i64).abs() as u32 * 10 / numTotalCells; + return fivePercentVariances * N4; +} + +/** + * Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask + * pattern conditions. + */ +pub fn getDataMaskBit(maskPattern: u32, x: u32, y: u32) -> Result { + let intermediate = match maskPattern { + 0 => (y + x) & 0x1, + 1 => y & 0x1, + 2 => x % 3, + 3 => (y + x) % 3, + 4 => ((y / 2) + (x / 3)) & 0x1, + 5 => { + let temp = y * x; + (temp & 0x1) + (temp % 3) + } + 6 => { + let temp = y * x; + ((temp & 0x1) + (temp % 3)) & 0x1 + } + 7 => { + let temp = y * x; + ((temp % 3) + ((y + x) & 0x1)) & 0x1 + } + _ => { + return Err(Exceptions::IllegalArgumentException(format!( + "Invalid mask pattern: {}", + maskPattern + ))) + } + }; + // switch (maskPattern) { + // case 0: + // intermediate = (y + x) & 0x1; + // break; + // case 1: + // intermediate = y & 0x1; + // break; + // case 2: + // intermediate = x % 3; + // break; + // case 3: + // intermediate = (y + x) % 3; + // break; + // case 4: + // intermediate = ((y / 2) + (x / 3)) & 0x1; + // break; + // case 5: + // temp = y * x; + // intermediate = (temp & 0x1) + (temp % 3); + // break; + // case 6: + // temp = y * x; + // intermediate = ((temp & 0x1) + (temp % 3)) & 0x1; + // break; + // case 7: + // temp = y * x; + // intermediate = ((temp % 3) + ((y + x) & 0x1)) & 0x1; + // break; + // default: + // throw new IllegalArgumentException("Invalid mask pattern: " + maskPattern); + // } + Ok(intermediate == 0) +} + +/** + * Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both + * vertical and horizontal orders respectively. + */ +fn applyMaskPenaltyRule1Internal(matrix: &ByteMatrix, isHorizontal: bool) -> u32 { + let mut penalty = 0; + let iLimit = if isHorizontal { + matrix.getHeight() + } else { + matrix.getWidth() + }; + let jLimit = if isHorizontal { + matrix.getWidth() + } else { + matrix.getHeight() + }; + let array = matrix.getArray(); + for i in 0..iLimit as usize { + // for (int i = 0; i < iLimit; i++) { + let mut numSameBitCells = 0; + // let prevBit = -1; + let mut prevBit = 0; + for j in 0..jLimit as usize { + // for (int j = 0; j < jLimit; j++) { + let bit = if isHorizontal { + array[i][j] + } else { + array[j][i] + }; + if bit == prevBit { + numSameBitCells += 1; + } else { + if numSameBitCells >= 5 { + penalty += N1 + (numSameBitCells - 5); + } + numSameBitCells = 1; // Include the cell itself. + prevBit = bit; + } + } + if numSameBitCells >= 5 { + penalty += N1 + (numSameBitCells - 5); + } + } + return penalty; +} diff --git a/src/qrcode/encoder/mod.rs b/src/qrcode/encoder/mod.rs index 77379e1..7e79f3e 100644 --- a/src/qrcode/encoder/mod.rs +++ b/src/qrcode/encoder/mod.rs @@ -1,6 +1,7 @@ mod qr_code; mod byte_matrix; mod block_pair; +pub mod mask_util; pub use qr_code::*; pub use byte_matrix::*; @@ -9,4 +10,6 @@ pub use block_pair::*; #[cfg(test)] mod QRCodeTestCase; #[cfg(test)] -mod BitVectorTestCase; \ No newline at end of file +mod BitVectorTestCase; +#[cfg(test)] +mod MaskUtilTestCase; \ No newline at end of file