• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.encoder;
18 
19 import com.google.zxing.common.BitArray;
20 import com.google.zxing.common.BitMatrix;
21 import com.google.zxing.common.reedsolomon.GenericGF;
22 import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
23 
24 import java.nio.charset.Charset;
25 import java.nio.charset.StandardCharsets;
26 
27 /**
28  * Generates Aztec 2D barcodes.
29  *
30  * @author Rustam Abdullaev
31  */
32 public final class Encoder {
33 
34   public static final int DEFAULT_EC_PERCENT = 33; // default minimal percentage of error check words
35   public static final int DEFAULT_AZTEC_LAYERS = 0;
36   private static final int MAX_NB_BITS = 32;
37   private static final int MAX_NB_BITS_COMPACT = 4;
38 
39   private static final int[] WORD_SIZE = {
40     4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
41     12, 12, 12, 12, 12, 12, 12, 12, 12, 12
42   };
43 
Encoder()44   private Encoder() {
45   }
46 
47   /**
48    * Encodes the given string content as an Aztec symbol (without ECI code)
49    *
50    * @param data input data string; must be encodable as ISO/IEC 8859-1 (Latin-1)
51    * @return Aztec symbol matrix with metadata
52    */
encode(String data)53   public static AztecCode encode(String data) {
54     return encode(data.getBytes(StandardCharsets.ISO_8859_1));
55   }
56 
57   /**
58    * Encodes the given string content as an Aztec symbol (without ECI code)
59    *
60    * @param data input data string; must be encodable as ISO/IEC 8859-1 (Latin-1)
61    * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
62    *                      a minimum of 23% + 3 words is recommended)
63    * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
64    * @return Aztec symbol matrix with metadata
65    */
encode(String data, int minECCPercent, int userSpecifiedLayers)66   public static AztecCode encode(String data, int minECCPercent, int userSpecifiedLayers) {
67     return encode(data.getBytes(StandardCharsets.ISO_8859_1), minECCPercent, userSpecifiedLayers, null);
68   }
69 
70   /**
71    * Encodes the given string content as an Aztec symbol
72    *
73    * @param data input data string
74    * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
75    *                      a minimum of 23% + 3 words is recommended)
76    * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
77    * @param charset character set in which to encode string using ECI; if null, no ECI code
78    *                will be inserted, and the string must be encodable as ISO/IEC 8859-1
79    *                (Latin-1), the default encoding of the symbol.
80    * @return Aztec symbol matrix with metadata
81    */
encode(String data, int minECCPercent, int userSpecifiedLayers, Charset charset)82   public static AztecCode encode(String data, int minECCPercent, int userSpecifiedLayers, Charset charset) {
83     byte[] bytes = data.getBytes(null != charset ? charset : StandardCharsets.ISO_8859_1);
84     return encode(bytes, minECCPercent, userSpecifiedLayers, charset);
85   }
86 
87   /**
88    * Encodes the given binary content as an Aztec symbol (without ECI code)
89    *
90    * @param data input data string
91    * @return Aztec symbol matrix with metadata
92    */
encode(byte[] data)93   public static AztecCode encode(byte[] data) {
94     return encode(data, DEFAULT_EC_PERCENT, DEFAULT_AZTEC_LAYERS, null);
95   }
96 
97   /**
98    * Encodes the given binary content as an Aztec symbol (without ECI code)
99    *
100    * @param data input data string
101    * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
102    *                      a minimum of 23% + 3 words is recommended)
103    * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
104    * @return Aztec symbol matrix with metadata
105    */
encode(byte[] data, int minECCPercent, int userSpecifiedLayers)106   public static AztecCode encode(byte[] data, int minECCPercent, int userSpecifiedLayers) {
107     return encode(data, minECCPercent, userSpecifiedLayers, null);
108   }
109 
110   /**
111    * Encodes the given binary content as an Aztec symbol
112    *
113    * @param data input data string
114    * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
115    *                      a minimum of 23% + 3 words is recommended)
116    * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
117    * @param charset character set to mark using ECI; if null, no ECI code will be inserted, and the
118    *                default encoding of ISO/IEC 8859-1 will be assuming by readers.
119    * @return Aztec symbol matrix with metadata
120    */
encode(byte[] data, int minECCPercent, int userSpecifiedLayers, Charset charset)121   public static AztecCode encode(byte[] data, int minECCPercent, int userSpecifiedLayers, Charset charset) {
122     // High-level encode
123     BitArray bits = new HighLevelEncoder(data, charset).encode();
124 
125     // stuff bits and choose symbol size
126     int eccBits = bits.getSize() * minECCPercent / 100 + 11;
127     int totalSizeBits = bits.getSize() + eccBits;
128     boolean compact;
129     int layers;
130     int totalBitsInLayer;
131     int wordSize;
132     BitArray stuffedBits;
133     if (userSpecifiedLayers != DEFAULT_AZTEC_LAYERS) {
134       compact = userSpecifiedLayers < 0;
135       layers = Math.abs(userSpecifiedLayers);
136       if (layers > (compact ? MAX_NB_BITS_COMPACT : MAX_NB_BITS)) {
137         throw new IllegalArgumentException(
138             String.format("Illegal value %s for layers", userSpecifiedLayers));
139       }
140       totalBitsInLayer = totalBitsInLayer(layers, compact);
141       wordSize = WORD_SIZE[layers];
142       int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
143       stuffedBits = stuffBits(bits, wordSize);
144       if (stuffedBits.getSize() + eccBits > usableBitsInLayers) {
145         throw new IllegalArgumentException("Data to large for user specified layer");
146       }
147       if (compact && stuffedBits.getSize() > wordSize * 64) {
148         // Compact format only allows 64 data words, though C4 can hold more words than that
149         throw new IllegalArgumentException("Data to large for user specified layer");
150       }
151     } else {
152       wordSize = 0;
153       stuffedBits = null;
154       // We look at the possible table sizes in the order Compact1, Compact2, Compact3,
155       // Compact4, Normal4,...  Normal(i) for i < 4 isn't typically used since Compact(i+1)
156       // is the same size, but has more data.
157       for (int i = 0; ; i++) {
158         if (i > MAX_NB_BITS) {
159           throw new IllegalArgumentException("Data too large for an Aztec code");
160         }
161         compact = i <= 3;
162         layers = compact ? i + 1 : i;
163         totalBitsInLayer = totalBitsInLayer(layers, compact);
164         if (totalSizeBits > totalBitsInLayer) {
165           continue;
166         }
167         // [Re]stuff the bits if this is the first opportunity, or if the
168         // wordSize has changed
169         if (stuffedBits == null || wordSize != WORD_SIZE[layers]) {
170           wordSize = WORD_SIZE[layers];
171           stuffedBits = stuffBits(bits, wordSize);
172         }
173         int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
174         if (compact && stuffedBits.getSize() > wordSize * 64) {
175           // Compact format only allows 64 data words, though C4 can hold more words than that
176           continue;
177         }
178         if (stuffedBits.getSize() + eccBits <= usableBitsInLayers) {
179           break;
180         }
181       }
182     }
183     BitArray messageBits = generateCheckWords(stuffedBits, totalBitsInLayer, wordSize);
184 
185     // generate mode message
186     int messageSizeInWords = stuffedBits.getSize() / wordSize;
187     BitArray modeMessage = generateModeMessage(compact, layers, messageSizeInWords);
188 
189     // allocate symbol
190     int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines
191     int[] alignmentMap = new int[baseMatrixSize];
192     int matrixSize;
193     if (compact) {
194       // no alignment marks in compact mode, alignmentMap is a no-op
195       matrixSize = baseMatrixSize;
196       for (int i = 0; i < alignmentMap.length; i++) {
197         alignmentMap[i] = i;
198       }
199     } else {
200       matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
201       int origCenter = baseMatrixSize / 2;
202       int center = matrixSize / 2;
203       for (int i = 0; i < origCenter; i++) {
204         int newOffset = i + i / 15;
205         alignmentMap[origCenter - i - 1] = center - newOffset - 1;
206         alignmentMap[origCenter + i] = center + newOffset + 1;
207       }
208     }
209     BitMatrix matrix = new BitMatrix(matrixSize);
210 
211     // draw data bits
212     for (int i = 0, rowOffset = 0; i < layers; i++) {
213       int rowSize = (layers - i) * 4 + (compact ? 9 : 12);
214       for (int j = 0; j < rowSize; j++) {
215         int columnOffset = j * 2;
216         for (int k = 0; k < 2; k++) {
217           if (messageBits.get(rowOffset + columnOffset + k)) {
218             matrix.set(alignmentMap[i * 2 + k], alignmentMap[i * 2 + j]);
219           }
220           if (messageBits.get(rowOffset + rowSize * 2 + columnOffset + k)) {
221             matrix.set(alignmentMap[i * 2 + j], alignmentMap[baseMatrixSize - 1 - i * 2 - k]);
222           }
223           if (messageBits.get(rowOffset + rowSize * 4 + columnOffset + k)) {
224             matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - k], alignmentMap[baseMatrixSize - 1 - i * 2 - j]);
225           }
226           if (messageBits.get(rowOffset + rowSize * 6 + columnOffset + k)) {
227             matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - j], alignmentMap[i * 2 + k]);
228           }
229         }
230       }
231       rowOffset += rowSize * 8;
232     }
233 
234     // draw mode message
235     drawModeMessage(matrix, compact, matrixSize, modeMessage);
236 
237     // draw alignment marks
238     if (compact) {
239       drawBullsEye(matrix, matrixSize / 2, 5);
240     } else {
241       drawBullsEye(matrix, matrixSize / 2, 7);
242       for (int i = 0, j = 0; i < baseMatrixSize / 2 - 1; i += 15, j += 16) {
243         for (int k = (matrixSize / 2) & 1; k < matrixSize; k += 2) {
244           matrix.set(matrixSize / 2 - j, k);
245           matrix.set(matrixSize / 2 + j, k);
246           matrix.set(k, matrixSize / 2 - j);
247           matrix.set(k, matrixSize / 2 + j);
248         }
249       }
250     }
251 
252     AztecCode aztec = new AztecCode();
253     aztec.setCompact(compact);
254     aztec.setSize(matrixSize);
255     aztec.setLayers(layers);
256     aztec.setCodeWords(messageSizeInWords);
257     aztec.setMatrix(matrix);
258     return aztec;
259   }
260 
drawBullsEye(BitMatrix matrix, int center, int size)261   private static void drawBullsEye(BitMatrix matrix, int center, int size) {
262     for (int i = 0; i < size; i += 2) {
263       for (int j = center - i; j <= center + i; j++) {
264         matrix.set(j, center - i);
265         matrix.set(j, center + i);
266         matrix.set(center - i, j);
267         matrix.set(center + i, j);
268       }
269     }
270     matrix.set(center - size, center - size);
271     matrix.set(center - size + 1, center - size);
272     matrix.set(center - size, center - size + 1);
273     matrix.set(center + size, center - size);
274     matrix.set(center + size, center - size + 1);
275     matrix.set(center + size, center + size - 1);
276   }
277 
generateModeMessage(boolean compact, int layers, int messageSizeInWords)278   static BitArray generateModeMessage(boolean compact, int layers, int messageSizeInWords) {
279     BitArray modeMessage = new BitArray();
280     if (compact) {
281       modeMessage.appendBits(layers - 1, 2);
282       modeMessage.appendBits(messageSizeInWords - 1, 6);
283       modeMessage = generateCheckWords(modeMessage, 28, 4);
284     } else {
285       modeMessage.appendBits(layers - 1, 5);
286       modeMessage.appendBits(messageSizeInWords - 1, 11);
287       modeMessage = generateCheckWords(modeMessage, 40, 4);
288     }
289     return modeMessage;
290   }
291 
drawModeMessage(BitMatrix matrix, boolean compact, int matrixSize, BitArray modeMessage)292   private static void drawModeMessage(BitMatrix matrix, boolean compact, int matrixSize, BitArray modeMessage) {
293     int center = matrixSize / 2;
294     if (compact) {
295       for (int i = 0; i < 7; i++) {
296         int offset = center - 3 + i;
297         if (modeMessage.get(i)) {
298           matrix.set(offset, center - 5);
299         }
300         if (modeMessage.get(i + 7)) {
301           matrix.set(center + 5, offset);
302         }
303         if (modeMessage.get(20 - i)) {
304           matrix.set(offset, center + 5);
305         }
306         if (modeMessage.get(27 - i)) {
307           matrix.set(center - 5, offset);
308         }
309       }
310     } else {
311       for (int i = 0; i < 10; i++) {
312         int offset = center - 5 + i + i / 5;
313         if (modeMessage.get(i)) {
314           matrix.set(offset, center - 7);
315         }
316         if (modeMessage.get(i + 10)) {
317           matrix.set(center + 7, offset);
318         }
319         if (modeMessage.get(29 - i)) {
320           matrix.set(offset, center + 7);
321         }
322         if (modeMessage.get(39 - i)) {
323           matrix.set(center - 7, offset);
324         }
325       }
326     }
327   }
328 
generateCheckWords(BitArray bitArray, int totalBits, int wordSize)329   private static BitArray generateCheckWords(BitArray bitArray, int totalBits, int wordSize) {
330     // bitArray is guaranteed to be a multiple of the wordSize, so no padding needed
331     int messageSizeInWords = bitArray.getSize() / wordSize;
332     ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize));
333     int totalWords = totalBits / wordSize;
334     int[] messageWords = bitsToWords(bitArray, wordSize, totalWords);
335     rs.encode(messageWords, totalWords - messageSizeInWords);
336     int startPad = totalBits % wordSize;
337     BitArray messageBits = new BitArray();
338     messageBits.appendBits(0, startPad);
339     for (int messageWord : messageWords) {
340       messageBits.appendBits(messageWord, wordSize);
341     }
342     return messageBits;
343   }
344 
bitsToWords(BitArray stuffedBits, int wordSize, int totalWords)345   private static int[] bitsToWords(BitArray stuffedBits, int wordSize, int totalWords) {
346     int[] message = new int[totalWords];
347     int i;
348     int n;
349     for (i = 0, n = stuffedBits.getSize() / wordSize; i < n; i++) {
350       int value = 0;
351       for (int j = 0; j < wordSize; j++) {
352         value |= stuffedBits.get(i * wordSize + j) ? (1 << wordSize - j - 1) : 0;
353       }
354       message[i] = value;
355     }
356     return message;
357   }
358 
getGF(int wordSize)359   private static GenericGF getGF(int wordSize) {
360     switch (wordSize) {
361       case 4:
362         return GenericGF.AZTEC_PARAM;
363       case 6:
364         return GenericGF.AZTEC_DATA_6;
365       case 8:
366         return GenericGF.AZTEC_DATA_8;
367       case 10:
368         return GenericGF.AZTEC_DATA_10;
369       case 12:
370         return GenericGF.AZTEC_DATA_12;
371       default:
372         throw new IllegalArgumentException("Unsupported word size " + wordSize);
373     }
374   }
375 
stuffBits(BitArray bits, int wordSize)376   static BitArray stuffBits(BitArray bits, int wordSize) {
377     BitArray out = new BitArray();
378 
379     int n = bits.getSize();
380     int mask = (1 << wordSize) - 2;
381     for (int i = 0; i < n; i += wordSize) {
382       int word = 0;
383       for (int j = 0; j < wordSize; j++) {
384         if (i + j >= n || bits.get(i + j)) {
385           word |= 1 << (wordSize - 1 - j);
386         }
387       }
388       if ((word & mask) == mask) {
389         out.appendBits(word & mask, wordSize);
390         i--;
391       } else if ((word & mask) == 0) {
392         out.appendBits(word | 1, wordSize);
393         i--;
394       } else {
395         out.appendBits(word, wordSize);
396       }
397     }
398     return out;
399   }
400 
totalBitsInLayer(int layers, boolean compact)401   private static int totalBitsInLayer(int layers, boolean compact) {
402     return ((compact ? 88 : 112) + 16 * layers) * layers;
403   }
404 }
405