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 }