xref: /aosp_15_r20/external/zxing/core/src/test/java/com/google/zxing/aztec/detector/DetectorTest.java (revision 513427e33d61bc67fc40bc261642ac0b2a686b45)
1 /*
2  * Copyright 2013 ZXing authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.zxing.aztec.detector;
18 
19 import com.google.zxing.NotFoundException;
20 import com.google.zxing.aztec.AztecDetectorResult;
21 import com.google.zxing.aztec.decoder.Decoder;
22 import com.google.zxing.aztec.detector.Detector.Point;
23 import com.google.zxing.aztec.encoder.AztecCode;
24 import com.google.zxing.aztec.encoder.Encoder;
25 import com.google.zxing.common.BitMatrix;
26 import com.google.zxing.common.DecoderResult;
27 import org.junit.Assert;
28 import org.junit.Test;
29 
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.List;
34 import java.util.Random;
35 import java.util.TreeSet;
36 
37 /**
38  * Tests for the Detector
39  *
40  * @author Frank Yellin
41  */
42 public final class DetectorTest extends Assert {
43 
44   @Test
testErrorInParameterLocatorZeroZero()45   public void testErrorInParameterLocatorZeroZero() throws Exception {
46     // Layers=1, CodeWords=1.  So the parameter info and its Reed-Solomon info
47     // will be completely zero!
48     testErrorInParameterLocator("X");
49   }
50 
51   @Test
testErrorInParameterLocatorCompact()52   public void testErrorInParameterLocatorCompact() throws Exception {
53     testErrorInParameterLocator("This is an example Aztec symbol for Wikipedia.");
54   }
55 
56   @Test
testErrorInParameterLocatorNotCompact()57   public void testErrorInParameterLocatorNotCompact() throws Exception {
58     String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz";
59     testErrorInParameterLocator(alphabet + alphabet + alphabet);
60   }
61 
62   // Test that we can tolerate errors in the parameter locator bits
testErrorInParameterLocator(String data)63   private static void testErrorInParameterLocator(String data) throws Exception {
64     AztecCode aztec = Encoder.encode(data, 25, Encoder.DEFAULT_AZTEC_LAYERS);
65     Random random = new Random(aztec.getMatrix().hashCode());   // pseudo-random, but deterministic
66     int layers = aztec.getLayers();
67     boolean compact = aztec.isCompact();
68     List<Point> orientationPoints = getOrientationPoints(aztec);
69     for (boolean isMirror : new boolean[] { false, true }) {
70       for (BitMatrix matrix : getRotations(aztec.getMatrix())) {
71         // Systematically try every possible 1- and 2-bit error.
72         for (int error1 = 0; error1 < orientationPoints.size(); error1++) {
73           for (int error2 = error1; error2 < orientationPoints.size(); error2++) {
74             BitMatrix copy = isMirror ? transpose(matrix) : clone(matrix);
75             copy.flip(orientationPoints.get(error1).getX(), orientationPoints.get(error1).getY());
76             if (error2 > error1) {
77               // if error2 == error1, we only test a single error
78               copy.flip(orientationPoints.get(error2).getX(), orientationPoints.get(error2).getY());
79             }
80             // The detector doesn't seem to work when matrix bits are only 1x1.  So magnify.
81             AztecDetectorResult r = new Detector(makeLarger(copy, 3)).detect(isMirror);
82             assertNotNull(r);
83             assertEquals(r.getNbLayers(), layers);
84             assertEquals(r.isCompact(), compact);
85             DecoderResult res = new Decoder().decode(r);
86             assertEquals(data, res.getText());
87           }
88         }
89         // Try a few random three-bit errors;
90         for (int i = 0; i < 5; i++) {
91           BitMatrix copy = clone(matrix);
92           Collection<Integer> errors = new TreeSet<>();
93           while (errors.size() < 3) {
94             // Quick and dirty way of getting three distinct integers between 1 and n.
95             errors.add(random.nextInt(orientationPoints.size()));
96           }
97           for (int error : errors) {
98             copy.flip(orientationPoints.get(error).getX(), orientationPoints.get(error).getY());
99           }
100           try {
101             new Detector(makeLarger(copy, 3)).detect(false);
102             fail("Should not reach here");
103           } catch (NotFoundException expected) {
104             // continue
105           }
106         }
107       }
108     }
109   }
110 
111   // Zooms a bit matrix so that each bit is factor x factor
makeLarger(BitMatrix input, int factor)112   private static BitMatrix makeLarger(BitMatrix input, int factor) {
113     int width = input.getWidth();
114     BitMatrix output = new BitMatrix(width * factor);
115     for (int inputY = 0; inputY < width; inputY++) {
116       for (int inputX = 0; inputX < width; inputX++) {
117         if (input.get(inputX, inputY)) {
118           output.setRegion(inputX * factor, inputY * factor, factor, factor);
119         }
120       }
121     }
122     return output;
123   }
124 
125   // Returns a list of the four rotations of the BitMatrix.
getRotations(BitMatrix matrix0)126   private static Iterable<BitMatrix> getRotations(BitMatrix matrix0) {
127     BitMatrix matrix90 = rotateRight(matrix0);
128     BitMatrix matrix180 = rotateRight(matrix90);
129     BitMatrix matrix270 = rotateRight(matrix180);
130     return Arrays.asList(matrix0, matrix90, matrix180, matrix270);
131   }
132 
133   // Rotates a square BitMatrix to the right by 90 degrees
rotateRight(BitMatrix input)134   private static BitMatrix rotateRight(BitMatrix input) {
135     int width = input.getWidth();
136     BitMatrix result = new BitMatrix(width);
137     for (int x = 0; x < width; x++) {
138       for (int y = 0; y < width; y++) {
139         if (input.get(x,y)) {
140           result.set(y, width - x - 1);
141         }
142       }
143     }
144     return result;
145   }
146 
147   // Returns the transpose of a bit matrix, which is equivalent to rotating the
148   // matrix to the right, and then flipping it left-to-right
transpose(BitMatrix input)149   private static BitMatrix transpose(BitMatrix input) {
150     int width = input.getWidth();
151     BitMatrix result = new BitMatrix(width);
152     for (int x = 0; x < width; x++) {
153       for (int y = 0; y < width; y++) {
154         if (input.get(x, y)) {
155           result.set(y, x);
156         }
157       }
158     }
159     return result;
160   }
161 
clone(BitMatrix input)162   private static BitMatrix clone(BitMatrix input)  {
163     int width = input.getWidth();
164     BitMatrix result = new BitMatrix(width);
165     for (int x = 0; x < width; x++) {
166       for (int y = 0; y < width; y++) {
167         if (input.get(x,y)) {
168           result.set(x,y);
169         }
170       }
171     }
172     return result;
173   }
174 
getOrientationPoints(AztecCode code)175   private static List<Point> getOrientationPoints(AztecCode code) {
176     int center = code.getMatrix().getWidth() / 2;
177     int offset = code.isCompact() ? 5 : 7;
178     List<Point> result = new ArrayList<>();
179     for (int xSign = -1; xSign <= 1; xSign += 2) {
180       for (int ySign = -1; ySign <= 1; ySign += 2) {
181         result.add(new Point(center + xSign * offset, center + ySign * offset));
182         result.add(new Point(center + xSign * (offset - 1), center + ySign * offset));
183         result.add(new Point(center + xSign * offset, center + ySign * (offset - 1)));
184       }
185     }
186     return result;
187   }
188 
189 }
190