1 module ithox.qrcode.encoder.matrixutil;
2 
3 import ithox.qrcode.common.bitarray;
4 import ithox.qrcode.common.qrcodeversion;
5 import ithox.qrcode.common.errorcorrectionlevel;
6 import ithox.qrcode.encoder.bytematrix;
7 import ithox.qrcode.encoder.maskutil;
8 import std.conv;
9 
10 /**
11 * Matrix utility.
12 */
13 class MatrixUtil
14 {
15     /**
16 	* Position detection pattern.
17 	*
18 	* @var array
19 	*/
20     protected enum positionDetectionPattern = [[1, 1, 1, 1, 1, 1, 1], [
21             1, 0, 0, 0, 0, 0, 1
22         ], [1, 0, 1, 1, 1, 0, 1], [1, 0, 1, 1, 1, 0, 1], [
23             1, 0, 1, 1, 1, 0, 1
24         ], [1, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1],];
25 
26     /**
27 	* Position adjustment pattern.
28 	*
29 	* @var array
30 	*/
31     protected enum positionAdjustmentPattern = [[1, 1, 1, 1, 1], [
32             1, 0, 0, 0, 1
33         ], [1, 0, 1, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 1],];
34 
35     /**
36 	* Coordinates for position adjustment patterns for each version.
37 	*
38 	* @var array
39 	*/
40     protected enum positionAdjustmentPatternCoordinateTable = [
41             [-1, -1, -1, -1, -1, -1, -1
42         ], // Version 1
43         [6, 18, -1, -1, -1, -1, -1], // Version 2
44         [6, 22, -1, -1, -1, -1, -1], // Version 3
45         [6, 26, -1, -1, -1, -1, -1], // Version 4
46         [6, 30, -1, -1, -1, -1, -1], // Version 5
47         [6, 34, -1, -1, -1, -1, -1], // Version 6
48         [6, 22, 38, -1, -1, -1, -1], // Version 7
49         [6, 24, 42, -1, -1, -1, -1], // Version 8
50         [6, 26, 46, -1, -1, -1, -1], // Version 9
51         [6, 28, 50, -1, -1, -1, -1], // Version 10
52         [6, 30, 54, -1, -1, -1, -1], // Version 11
53         [6, 32, 58, -1, -1, -1, -1], // Version 12
54         [6, 34, 62, -1, -1, -1, -1], // Version 13
55         [6, 26, 46, 66, -1, -1, -1], // Version 14
56         [6, 26, 48, 70, -1, -1, -1], // Version 15
57         [6, 26, 50, 74, -1, -1, -1], // Version 16
58         [6, 30, 54, 78, -1, -1, -1], // Version 17
59         [6, 30, 56, 82, -1, -1, -1], // Version 18
60         [6, 30, 58, 86, -1, -1, -1], // Version 19
61         [6, 34, 62, 90, -1, -1, -1], // Version 20
62         [6, 28, 50, 72, 94, -1, -1], // Version 21
63         [6, 26, 50, 74, 98, -1, -1], // Version 22
64         [6, 30, 54, 78, 102, -1, -1], // Version 23
65         [6, 28, 54, 80, 106, -1, -1], // Version 24
66         [6, 32, 58, 84, 110, -1, -1], // Version 25
67         [6, 30, 58, 86, 114, -1, -1], // Version 26
68         [6, 34, 62, 90, 118, -1, -1], // Version 27
69         [6, 26, 50, 74, 98, 122, -1], // Version 28
70         [6, 30, 54, 78, 102, 126, -1], // Version 29
71         [6, 26, 52, 78, 104, 130, -1], // Version 30
72         [6, 30, 56, 82, 108, 134, -1], // Version 31
73         [6, 34, 60, 86, 112, 138, -1], // Version 32
74         [6, 30, 58, 86, 114, 142, -1], // Version 33
75         [6, 34, 62, 90, 118, 146, -1], // Version 34
76         [6, 30, 54, 78, 102, 126, 150], // Version 35
77         [6, 24, 50, 76, 102, 128, 154], // Version 36
78         [6, 28, 54, 80, 106, 132, 158], // Version 37
79         [6, 32, 58, 84, 110, 136, 162], // Version 38
80         [6, 26, 54, 82, 110, 138, 166], // Version 39
81         [6, 30, 58, 86, 114, 142, 170], // Version 40
82         ];
83 
84     /**
85 	* Type information coordinates.
86 	*
87 	* @var array
88 	*/
89     protected enum typeInfoCoordinates = [[8, 0], [8, 1], [
90             8, 2
91         ], [8, 3], [8, 4], [8, 5], [8, 7], [8, 8], [7, 8], [
92             5, 8
93         ], [4, 8], [3, 8], [2, 8], [1, 8], [0, 8],];
94     /**
95 	* Version information polynomial.
96 	*
97 	* @var integer
98 	*/
99     protected enum versionInfoPoly = 0x1f25;
100     /**
101 	* Type information polynomial.
102 	*
103 	* @var integer
104 	*/
105     protected enum typeInfoPoly = 0x537;
106     /**
107 	* Type information mask pattern.
108 	*
109 	* @var integer
110 	*/
111     protected enum typeInfoMaskPattern = 0x5412;
112 
113     /**
114 	* Clears a given matrix.
115 	*
116 	* @param  ByteMatrix $matrix
117 	* @return void
118 	*/
119     public static void clearMatrix(ByteMatrix matrix)
120     {
121         matrix.clear(-1);
122     }
123 
124     /**
125 	* Builds a complete matrix.
126 	*
127 	* @param  BitArray             $dataBits
128 	* @param  ErrorCorrectionLevel $level
129 	* @param  Version              $version
130 	* @param  integer              $maskPattern
131 	* @param  ByteMatrix           $matrix
132 	* @return void
133 	*/
134     public static void buildMatrix(ref BitArray dataBits, ErrorCorrectionLevel level,
135             QrCodeVersion _version, int maskPattern, ref ByteMatrix matrix)
136     {
137         clearMatrix(matrix);
138 
139         embedBasicPatterns(_version, matrix);
140         embedTypeInfo(level, maskPattern, matrix);
141         import std.stdio;
142 
143         //writeln("clearMatrix(matrix);",maskPattern);
144         //writeln(matrix);
145         maybeEmbedVersionInfo(_version, matrix);
146         embedDataBits(dataBits, maskPattern, matrix);
147     }
148     /**
149 	* Embeds type information into a matrix.
150 	*
151 	* @param  ErrorCorrectionLevel level
152 	* @param  integer              maskPattern
153 	* @param  ByteMatrix           matrix
154 	* @return void
155 	*/
156     protected static void embedTypeInfo(ErrorCorrectionLevel level,
157             int maskPattern, ref ByteMatrix matrix)
158     {
159         auto typeInfoBits = new BitArray();
160         makeTypeInfoBits(level, maskPattern, typeInfoBits);
161         auto typeInfoBitsSize = typeInfoBits.getSize();
162         for (auto i = 0; i < typeInfoBitsSize; i++)
163         {
164             auto bit = typeInfoBits.get(typeInfoBitsSize - 1 - i);
165             auto x1 = typeInfoCoordinates[i][0];
166             auto y1 = typeInfoCoordinates[i][1];
167             matrix.set(x1, y1, bit);
168             int x2, y2;
169             if (i < 8)
170             {
171                 x2 = matrix.width() - i - 1;
172                 y2 = 8;
173             }
174             else
175             {
176                 x2 = 8;
177                 y2 = matrix.height() - 7 + (i - 8);
178             }
179             matrix.set(x2, y2, bit);
180 
181             import std.stdio;
182 
183             //writeln("----bit:", bit, " x1:", x1, " y1:", y1, " x2:", x2, " y2:", y2);
184         }
185     }
186 
187     /**
188 	* Generates type information bits and appends them to a bit array.
189 	*
190 	* @param  ErrorCorrectionLevel level
191 	* @param  integer maskPattern
192 	* @param  BitArray bits
193 	* @return void
194 	* @throws Exception\RuntimeException
195 	*/
196     protected static void makeTypeInfoBits(ErrorCorrectionLevel level,
197             int maskPattern, ref BitArray bits)
198     {
199         auto typeInfo = (cast(int) level << 3) | maskPattern;
200         bits.appendBits(typeInfo, 5);
201         auto bchCode = calculateBchCode(typeInfo, typeInfoPoly);
202         bits.appendBits(bchCode, 10);
203         auto maskBits = new BitArray();
204         maskBits.appendBits(typeInfoMaskPattern, 15);
205         bits.xorBits(maskBits);
206         if (bits.getSize() != 15)
207         {
208             throw new Exception("Bit array resulted in invalid size: " ~ to!string(bits.getSize()));
209         }
210     }
211 
212     /**
213 	* Embeds version information if required.
214 	*
215 	* @param  Version    version
216 	* @param  ByteMatrix matrix
217 	* @return void
218 	*/
219     protected static void maybeEmbedVersionInfo(QrCodeVersion _version, ByteMatrix matrix)
220     {
221         if (_version.VersionNumber < 7)
222         {
223             return;
224         }
225         auto versionInfoBits = new BitArray();
226         makeVersionInfoBits(_version, versionInfoBits);
227         auto bitIndex = 6 * 3 - 1;
228         for (auto i = 0; i < 6; i++)
229         {
230             for (auto j = 0; j < 3; j++)
231             {
232                 auto bit = versionInfoBits.get(bitIndex);
233                 bitIndex--;
234                 matrix.set(i, matrix.height() - 11 + j, bit);
235                 matrix.set(matrix.height() - 11 + j, i, bit);
236             }
237         }
238     }
239 
240     /**
241 	* Generates version information bits and appends them to a bit array.
242 	*
243 	* @param  Version  version
244 	* @param  BitArray bits
245 	* @return void
246 	* @throws Exception\RuntimeException
247 	*/
248     protected static void makeVersionInfoBits(QrCodeVersion _version, BitArray bits)
249     {
250         bits.appendBits(_version.VersionNumber(), 6);
251         auto bchCode = calculateBchCode(_version.VersionNumber(), versionInfoPoly);
252         bits.appendBits(bchCode, 12);
253         if (bits.getSize() != 18)
254         {
255             throw new Exception("Bit array resulted in invalid size: " ~ to!string(bits.getSize()));
256         }
257     }
258 
259     /**
260 	* Calculates the BHC code for a value and a polynomial.
261 	*
262 	* @param  integer value
263 	* @param  integer poly
264 	* @return integer
265 	*/
266     protected static int calculateBchCode(int value, int poly)
267     {
268         auto msbSetInPoly = findMsbSet(poly);
269         value <<= msbSetInPoly - 1;
270         while (findMsbSet(value) >= msbSetInPoly)
271         {
272             value ^= poly << (findMsbSet(value) - msbSetInPoly);
273         }
274         return value;
275     }
276 
277     /**
278 	* Finds and MSB set.
279 	*
280 	* @param  integer value
281 	* @return integer
282 	*/
283     protected static int findMsbSet(int value)
284     {
285         auto numDigits = 0;
286         while (value != 0)
287         {
288             value >>= 1;
289             numDigits++;
290         }
291         return numDigits;
292     }
293 
294     /**
295 	* Embeds basic patterns into a matrix.
296 	*
297 	* @param  Version    version
298 	* @param  ByteMatrix matrix
299 	* @return void
300 	*/
301     protected static void embedBasicPatterns(QrCodeVersion _version, ByteMatrix matrix)
302     {
303         embedPositionDetectionPatternsAndSeparators(matrix);
304         embedDarkDotAtLeftBottomCorner(matrix);
305         maybeEmbedPositionAdjustmentPatterns(_version, matrix);
306         embedTimingPatterns(matrix);
307     }
308 
309     /**
310 	* Embeds position detection patterns and separators into a byte matrix.
311 	*
312 	* @param  ByteMatrix matrix
313 	* @return void
314 	*/
315     protected static void embedPositionDetectionPatternsAndSeparators(ByteMatrix matrix)
316     {
317         int pdpWidth = positionDetectionPattern[0].length;
318         embedPositionDetectionPattern(0, 0, matrix);
319         embedPositionDetectionPattern(matrix.width() - pdpWidth, 0, matrix);
320         embedPositionDetectionPattern(0, matrix.width() - pdpWidth, matrix);
321         int hspWidth = 8;
322         embedHorizontalSeparationPattern(0, hspWidth - 1, matrix);
323         embedHorizontalSeparationPattern(matrix.width - hspWidth, hspWidth - 1, matrix);
324         embedHorizontalSeparationPattern(0, matrix.width - hspWidth, matrix);
325         int vspSize = 7;
326         embedVerticalSeparationPattern(vspSize, 0, matrix);
327         embedVerticalSeparationPattern(matrix.height - vspSize - 1, 0, matrix);
328         embedVerticalSeparationPattern(vspSize, matrix.height - vspSize, matrix);
329     }
330 
331     /**
332 	* Embeds a single position detection pattern into a byte matrix.
333 	*
334 	* @param  integer    xStart
335 	* @param  integer    yStart
336 	* @param  ByteMatrix matrix
337 	* @return void
338 	*/
339     protected static void embedPositionDetectionPattern(int xStart, int yStart, ByteMatrix matrix)
340     {
341         for (auto y = 0; y < 7; y++)
342         {
343             for (auto x = 0; x < 7; x++)
344             {
345                 matrix.set(xStart + x, yStart + y, positionDetectionPattern[y][x]);
346             }
347         }
348     }
349 
350     /**
351 	* Embeds a single horizontal separation pattern.
352 	*
353 	* @param  integer    xStart
354 	* @param  integer    yStart
355 	* @param  ByteMatrix matrix
356 	* @return void
357 	* @throws Exception\RuntimeException
358 	*/
359     protected static void embedHorizontalSeparationPattern(int xStart, int yStart, ByteMatrix matrix)
360     {
361         for (int x = 0; x < 8; x++)
362         {
363             if (matrix.get(xStart + x, yStart) != -1)
364             {
365                 throw new Exception("Byte already set");
366             }
367             matrix.set(xStart + x, yStart, 0);
368         }
369     }
370 
371     /**
372 	* Embeds a single vertical separation pattern.
373 	*
374 	* @param  integer    xStart
375 	* @param  integer    yStart
376 	* @param  ByteMatrix matrix
377 	* @return void
378 	* @throws Exception\RuntimeException
379 	*/
380     protected static void embedVerticalSeparationPattern(int xStart, int yStart, ByteMatrix matrix)
381     {
382         for (int y = 0; y < 7; y++)
383         {
384             if (matrix.get(xStart, yStart + y) != -1)
385             {
386                 throw new Exception("Byte already set");
387             }
388             matrix.set(xStart, yStart + y, 0);
389         }
390     }
391 
392     /**
393 	* Embeds a dot at the left bottom corner.
394 	*
395 	* @param  ByteMatrix matrix
396 	* @return void
397 	* @throws Exception\RuntimeException
398 	*/
399     protected static void embedDarkDotAtLeftBottomCorner(ByteMatrix matrix)
400     {
401         if (matrix.get(8, matrix.height - 8) == 0)
402         {
403             throw new Exception("Byte already set to 0");
404         }
405         matrix.set(8, matrix.height() - 8, 1);
406     }
407     /**
408 	* Embeds position adjustment patterns if required.
409 	*
410 	* @param  Version    version
411 	* @param  ByteMatrix matrix
412 	* @return void
413 	*/
414     protected static void maybeEmbedPositionAdjustmentPatterns(QrCodeVersion _version,
415             ByteMatrix matrix)
416     {
417         if (_version.VersionNumber() < 2)
418         {
419             return;
420         }
421         auto index = _version.VersionNumber() - 1;
422         auto coordinates = positionAdjustmentPatternCoordinateTable[index];
423         auto numCoordinates = coordinates.length;
424         for (auto i = 0; i < numCoordinates; i++)
425         {
426             for (auto j = 0; j < numCoordinates; j++)
427             {
428                 auto y = coordinates[i];
429                 auto x = coordinates[j];
430                 if (x == -1 || y == -1)
431                 {
432                     continue;
433                 }
434                 if (matrix.get(x, y) == -1)
435                 {
436                     embedPositionAdjustmentPattern(x - 2, y - 2, matrix);
437                 }
438             }
439         }
440     }
441     /**
442 	* Embeds a single position adjustment pattern.
443 	*
444 	* @param  integer    xStart
445 	* @param  intger     yStart
446 	* @param  ByteMatrix matrix
447 	* @return void
448 	*/
449     protected static void embedPositionAdjustmentPattern(int xStart, int yStart, ByteMatrix matrix)
450     {
451         for (int y = 0; y < 5; y++)
452         {
453             for (int x = 0; x < 5; x++)
454             {
455                 matrix.set(xStart + x, yStart + y, positionAdjustmentPattern[y][x]);
456             }
457         }
458     }
459     /**
460 	* Embeds timing patterns into a matrix.
461 	*
462 	* @param  ByteMatrix matrix
463 	* @return void
464 	*/
465     protected static void embedTimingPatterns(ByteMatrix matrix)
466     {
467         auto matrixWidth = matrix.width;
468         for (auto i = 8; i < matrixWidth - 8; i++)
469         {
470             auto bit = (i + 1) % 2;
471             if (matrix.get(i, 6) == -1)
472             {
473                 matrix.set(i, 6, bit);
474             }
475             if (matrix.get(6, i) == -1)
476             {
477                 matrix.set(6, i, bit);
478             }
479         }
480     }
481     /**
482 	* Embeds "dataBits" using "getMaskPattern".
483 	*
484 	*  For debugging purposes, it skips masking process if "getMaskPattern" is
485 	* -1. See 8.7 of JISX0510:2004 (p.38) for how to embed data bits.
486 	*
487 	* @param  BitArray   dataBits
488 	* @param  integer    maskPattern
489 	* @param  ByteMatrix matrix
490 	* @return void
491 	* @throws Exception\WriterException
492 	*/
493     protected static void embedDataBits(ref BitArray dataBits, int maskPattern, ByteMatrix matrix)
494     {
495         int bitIndex = 0;
496         int direction = -1;
497         // Start from the right bottom cell.
498         auto x = matrix.width - 1;
499         auto y = matrix.height - 1;
500         while (x > 0)
501         {
502             // Skip vertical timing pattern.
503             if (x == 6)
504             {
505                 x--;
506             }
507             while (y >= 0 && y < matrix.height)
508             {
509                 for (int i = 0; i < 2; i++)
510                 {
511                     auto xx = x - i;
512                     // Skip the cell if it's not empty.
513                     if (matrix.get(xx, y) != -1)
514                     {
515                         continue;
516                     }
517                     bool bit;
518                     if (bitIndex < dataBits.getSize())
519                     {
520                         bit = dataBits.get(bitIndex);
521                         bitIndex++;
522                     }
523                     else
524                     {
525                         // Padding bit. If there is no bit left, we'll fill the
526                         // left cells with 0, as described in 8.4.9 of
527                         // JISX0510:2004 (p. 24).
528                         bit = false;
529                     }
530                     // Skip masking if maskPattern is -1.
531                     if (maskPattern != -1 && MaskUtil.getDataMaskBit(maskPattern, xx, y))
532                     {
533                         bit = !bit;
534                     }
535                     matrix.set(xx, y, bit);
536                 }
537                 y += direction;
538             }
539             direction = -direction;
540             y += direction;
541             x -= 2;
542         }
543         // All bits should be consumed
544         if (bitIndex != dataBits.getSize())
545         {
546             throw new Exception("Not all bits consumed (" ~ to!string(
547                     bitIndex) ~ " out of " ~ to!string(dataBits.getSize()) ~ ")");
548         }
549     }
550 
551 }