1 /*
2 * Copyright 2016 Huy Cuong Nguyen
3 * Copyright 2016 ZXing authors
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #include "AZEncoder.h"
19
20 #include "AZHighLevelEncoder.h"
21 #include "BitArray.h"
22 #include "GenericGF.h"
23 #include "ReedSolomonEncoder.h"
24 #include "ZXTestSupport.h"
25
26 #include <cstdlib>
27 #include <stdexcept>
28 #include <vector>
29
30 namespace ZXing::Aztec {
31
32 static const int MAX_NB_BITS = 32;
33 static const int MAX_NB_BITS_COMPACT = 4;
34
35 static const int WORD_SIZE[] = {4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10,
36 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12};
37
DrawBullsEye(BitMatrix & matrix,int center,int size)38 static void DrawBullsEye(BitMatrix& matrix, int center, int size)
39 {
40 for (int i = 0; i < size; i += 2) {
41 for (int j = center - i; j <= center + i; j++) {
42 matrix.set(j, center - i);
43 matrix.set(j, center + i);
44 matrix.set(center - i, j);
45 matrix.set(center + i, j);
46 }
47 }
48 matrix.set(center - size, center - size);
49 matrix.set(center - size + 1, center - size);
50 matrix.set(center - size, center - size + 1);
51 matrix.set(center + size, center - size);
52 matrix.set(center + size, center - size + 1);
53 matrix.set(center + size, center + size - 1);
54 }
55
GetGFFromWordSize(int wordSize)56 static const GenericGF& GetGFFromWordSize(int wordSize)
57 {
58 switch (wordSize) {
59 case 4: return GenericGF::AztecParam();
60 case 6: return GenericGF::AztecData6();
61 case 8: return GenericGF::AztecData8();
62 case 10: return GenericGF::AztecData10();
63 case 12: return GenericGF::AztecData12();
64 default: throw std::invalid_argument("Unsupported word size " + std::to_string(wordSize));
65 }
66 }
67
GenerateCheckWords(const BitArray & bitArray,int totalBits,int wordSize,BitArray & messageBits)68 static void GenerateCheckWords(const BitArray& bitArray, int totalBits, int wordSize, BitArray& messageBits)
69 {
70 // bitArray is guaranteed to be a multiple of the wordSize, so no padding needed
71 std::vector<int> messageWords = ToInts(bitArray, wordSize, totalBits / wordSize);
72 ReedSolomonEncode(GetGFFromWordSize(wordSize), messageWords, (totalBits - bitArray.size()) / wordSize);
73 int startPad = totalBits % wordSize;
74 messageBits = BitArray();
75 messageBits.appendBits(0, startPad);
76 for (int messageWord : messageWords)
77 messageBits.appendBits(messageWord, wordSize);
78 }
79
80 ZXING_EXPORT_TEST_ONLY
GenerateModeMessage(bool compact,int layers,int messageSizeInWords,BitArray & modeMessage)81 void GenerateModeMessage(bool compact, int layers, int messageSizeInWords, BitArray& modeMessage)
82 {
83 modeMessage = BitArray();
84 if (compact) {
85 modeMessage.appendBits(layers - 1, 2);
86 modeMessage.appendBits(messageSizeInWords - 1, 6);
87 GenerateCheckWords(modeMessage, 28, 4, modeMessage);
88 }
89 else {
90 modeMessage.appendBits(layers - 1, 5);
91 modeMessage.appendBits(messageSizeInWords - 1, 11);
92 GenerateCheckWords(modeMessage, 40, 4, modeMessage);
93 }
94 }
95
DrawModeMessage(BitMatrix & matrix,bool compact,int matrixSize,const BitArray & modeMessage)96 static void DrawModeMessage(BitMatrix& matrix, bool compact, int matrixSize, const BitArray& modeMessage)
97 {
98 int center = matrixSize / 2;
99 if (compact) {
100 for (int i = 0; i < 7; i++) {
101 int offset = center - 3 + i;
102 if (modeMessage.get(i)) {
103 matrix.set(offset, center - 5);
104 }
105 if (modeMessage.get(i + 7)) {
106 matrix.set(center + 5, offset);
107 }
108 if (modeMessage.get(20 - i)) {
109 matrix.set(offset, center + 5);
110 }
111 if (modeMessage.get(27 - i)) {
112 matrix.set(center - 5, offset);
113 }
114 }
115 }
116 else {
117 for (int i = 0; i < 10; i++) {
118 int offset = center - 5 + i + i / 5;
119 if (modeMessage.get(i)) {
120 matrix.set(offset, center - 7);
121 }
122 if (modeMessage.get(i + 10)) {
123 matrix.set(center + 7, offset);
124 }
125 if (modeMessage.get(29 - i)) {
126 matrix.set(offset, center + 7);
127 }
128 if (modeMessage.get(39 - i)) {
129 matrix.set(center - 7, offset);
130 }
131 }
132 }
133 }
134
135 ZXING_EXPORT_TEST_ONLY
StuffBits(const BitArray & bits,int wordSize,BitArray & out)136 void StuffBits(const BitArray& bits, int wordSize, BitArray& out)
137 {
138 out = BitArray();
139 int n = bits.size();
140 int mask = (1 << wordSize) - 2;
141 for (int i = 0; i < n; i += wordSize) {
142 int word = 0;
143 for (int j = 0; j < wordSize; j++) {
144 if (i + j >= n || bits.get(i + j)) {
145 word |= 1 << (wordSize - 1 - j);
146 }
147 }
148 if ((word & mask) == mask) {
149 out.appendBits(word & mask, wordSize);
150 i--;
151 }
152 else if ((word & mask) == 0) {
153 out.appendBits(word | 1, wordSize);
154 i--;
155 }
156 else {
157 out.appendBits(word, wordSize);
158 }
159 }
160 }
161
TotalBitsInLayer(int layers,bool compact)162 static int TotalBitsInLayer(int layers, bool compact)
163 {
164 return ((compact ? 88 : 112) + 16 * layers) * layers;
165 }
166
167 /**
168 * Encodes the given binary content as an Aztec symbol
169 *
170 * @param data input data string
171 * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
172 * a minimum of 23% + 3 words is recommended)
173 * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
174 * @return Aztec symbol matrix with metadata
175 */
176 EncodeResult
Encode(const std::string & data,int minECCPercent,int userSpecifiedLayers)177 Encoder::Encode(const std::string& data, int minECCPercent, int userSpecifiedLayers)
178 {
179 // High-level encode
180 BitArray bits = HighLevelEncoder::Encode(data);
181
182 // stuff bits and choose symbol size
183 int eccBits = bits.size() * minECCPercent / 100 + 11;
184 int totalSizeBits = bits.size() + eccBits;
185 bool compact;
186 int layers;
187 int totalBitsInLayer;
188 int wordSize;
189 BitArray stuffedBits;
190 if (userSpecifiedLayers != DEFAULT_AZTEC_LAYERS) {
191 compact = userSpecifiedLayers < 0;
192 layers = std::abs(userSpecifiedLayers);
193 if (layers > (compact ? MAX_NB_BITS_COMPACT : MAX_NB_BITS)) {
194 throw std::invalid_argument("Illegal value for layers: " + std::to_string(userSpecifiedLayers));
195 }
196 totalBitsInLayer = TotalBitsInLayer(layers, compact);
197 wordSize = WORD_SIZE[layers];
198 int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
199 StuffBits(bits, wordSize, stuffedBits);
200 if (stuffedBits.size() + eccBits > usableBitsInLayers) {
201 throw std::invalid_argument("Data to large for user specified layer");
202 }
203 if (compact && stuffedBits.size() > wordSize * 64) {
204 // Compact format only allows 64 data words, though C4 can hold more words than that
205 throw std::invalid_argument("Data to large for user specified layer");
206 }
207 }
208 else {
209 wordSize = 0;
210 // We look at the possible table sizes in the order Compact1, Compact2, Compact3,
211 // Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1)
212 // is the same size, but has more data.
213 for (int i = 0; ; i++) {
214 if (i > MAX_NB_BITS) {
215 throw std::invalid_argument("Data too large for an Aztec code");
216 }
217 compact = i <= 3;
218 layers = compact ? i + 1 : i;
219 totalBitsInLayer = TotalBitsInLayer(layers, compact);
220 if (totalSizeBits > totalBitsInLayer) {
221 continue;
222 }
223 // [Re]stuff the bits if this is the first opportunity, or if the
224 // wordSize has changed
225 if (wordSize != WORD_SIZE[layers]) {
226 wordSize = WORD_SIZE[layers];
227 StuffBits(bits, wordSize, stuffedBits);
228 }
229 int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
230 if (compact && stuffedBits.size() > wordSize * 64) {
231 // Compact format only allows 64 data words, though C4 can hold more words than that
232 continue;
233 }
234 if (stuffedBits.size() + eccBits <= usableBitsInLayers) {
235 break;
236 }
237 }
238 }
239 BitArray messageBits;
240 GenerateCheckWords(stuffedBits, totalBitsInLayer, wordSize, messageBits);
241
242 // generate mode message
243 int messageSizeInWords = stuffedBits.size() / wordSize;
244 BitArray modeMessage;
245 GenerateModeMessage(compact, layers, messageSizeInWords, modeMessage);
246
247 // allocate symbol
248 int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines
249 std::vector<int> alignmentMap(baseMatrixSize, 0);
250 int matrixSize;
251 if (compact) {
252 // no alignment marks in compact mode, alignmentMap is a no-op
253 matrixSize = baseMatrixSize;
254 std::iota(alignmentMap.begin(), alignmentMap.end(), 0);
255 }
256 else {
257 matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
258 int origCenter = baseMatrixSize / 2;
259 int center = matrixSize / 2;
260 for (int i = 0; i < origCenter; i++) {
261 int newOffset = i + i / 15;
262 alignmentMap[origCenter - i - 1] = center - newOffset - 1;
263 alignmentMap[origCenter + i] = center + newOffset + 1;
264 }
265 }
266
267 EncodeResult output{compact, matrixSize, layers, messageSizeInWords, BitMatrix(matrixSize)};
268
269 BitMatrix& matrix = output.matrix;
270
271 // draw data bits
272 for (int i = 0, rowOffset = 0; i < layers; i++) {
273 int rowSize = (layers - i) * 4 + (compact ? 9 : 12);
274 for (int j = 0; j < rowSize; j++) {
275 int columnOffset = j * 2;
276 for (int k = 0; k < 2; k++) {
277 if (messageBits.get(rowOffset + columnOffset + k)) {
278 matrix.set(alignmentMap[i * 2 + k], alignmentMap[i * 2 + j]);
279 }
280 if (messageBits.get(rowOffset + rowSize * 2 + columnOffset + k)) {
281 matrix.set(alignmentMap[i * 2 + j], alignmentMap[baseMatrixSize - 1 - i * 2 - k]);
282 }
283 if (messageBits.get(rowOffset + rowSize * 4 + columnOffset + k)) {
284 matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - k], alignmentMap[baseMatrixSize - 1 - i * 2 - j]);
285 }
286 if (messageBits.get(rowOffset + rowSize * 6 + columnOffset + k)) {
287 matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - j], alignmentMap[i * 2 + k]);
288 }
289 }
290 }
291 rowOffset += rowSize * 8;
292 }
293
294 // draw mode message
295 DrawModeMessage(matrix, compact, matrixSize, modeMessage);
296
297 // draw alignment marks
298 if (compact) {
299 DrawBullsEye(matrix, matrixSize / 2, 5);
300 }
301 else {
302 DrawBullsEye(matrix, matrixSize / 2, 7);
303 for (int i = 0, j = 0; i < baseMatrixSize / 2 - 1; i += 15, j += 16) {
304 for (int k = (matrixSize / 2) & 1; k < matrixSize; k += 2) {
305 matrix.set(matrixSize / 2 - j, k);
306 matrix.set(matrixSize / 2 + j, k);
307 matrix.set(k, matrixSize / 2 - j);
308 matrix.set(k, matrixSize / 2 + j);
309 }
310 }
311 }
312 return output;
313 }
314
315 } // namespace ZXing::Aztec
316