From 1c87314f7f6faac9373e2129aed85338f806a08b Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 6 Apr 2025 09:44:57 -0400 Subject: [PATCH 01/22] Split QRCodeGenerator further --- QRCoder/QRCodeGenerator.Tables.cs | 189 +++++++++++++++++++++ QRCoder/QRCodeGenerator.cs | 179 ------------------- QRCoderTests/PngByteQRCodeRendererTests.cs | 1 - 3 files changed, 189 insertions(+), 180 deletions(-) create mode 100644 QRCoder/QRCodeGenerator.Tables.cs diff --git a/QRCoder/QRCodeGenerator.Tables.cs b/QRCoder/QRCodeGenerator.Tables.cs new file mode 100644 index 00000000..c1052efa --- /dev/null +++ b/QRCoder/QRCodeGenerator.Tables.cs @@ -0,0 +1,189 @@ +using System.Collections.Generic; + +namespace QRCoder; + +/// +/// Partial class containing table generation methods for QR code generation. +/// +public partial class QRCodeGenerator +{ + private static readonly int[] _capacityBaseValues = { 41, 25, 17, 10, 34, 20, 14, 8, 27, 16, 11, 7, 17, 10, 7, 4, 77, 47, 32, 20, 63, 38, 26, 16, 48, 29, 20, 12, 34, 20, 14, 8, 127, 77, 53, 32, 101, 61, 42, 26, 77, 47, 32, 20, 58, 35, 24, 15, 187, 114, 78, 48, 149, 90, 62, 38, 111, 67, 46, 28, 82, 50, 34, 21, 255, 154, 106, 65, 202, 122, 84, 52, 144, 87, 60, 37, 106, 64, 44, 27, 322, 195, 134, 82, 255, 154, 106, 65, 178, 108, 74, 45, 139, 84, 58, 36, 370, 224, 154, 95, 293, 178, 122, 75, 207, 125, 86, 53, 154, 93, 64, 39, 461, 279, 192, 118, 365, 221, 152, 93, 259, 157, 108, 66, 202, 122, 84, 52, 552, 335, 230, 141, 432, 262, 180, 111, 312, 189, 130, 80, 235, 143, 98, 60, 652, 395, 271, 167, 513, 311, 213, 131, 364, 221, 151, 93, 288, 174, 119, 74, 772, 468, 321, 198, 604, 366, 251, 155, 427, 259, 177, 109, 331, 200, 137, 85, 883, 535, 367, 226, 691, 419, 287, 177, 489, 296, 203, 125, 374, 227, 155, 96, 1022, 619, 425, 262, 796, 483, 331, 204, 580, 352, 241, 149, 427, 259, 177, 109, 1101, 667, 458, 282, 871, 528, 362, 223, 621, 376, 258, 159, 468, 283, 194, 120, 1250, 758, 520, 320, 991, 600, 412, 254, 703, 426, 292, 180, 530, 321, 220, 136, 1408, 854, 586, 361, 1082, 656, 450, 277, 775, 470, 322, 198, 602, 365, 250, 154, 1548, 938, 644, 397, 1212, 734, 504, 310, 876, 531, 364, 224, 674, 408, 280, 173, 1725, 1046, 718, 442, 1346, 816, 560, 345, 948, 574, 394, 243, 746, 452, 310, 191, 1903, 1153, 792, 488, 1500, 909, 624, 384, 1063, 644, 442, 272, 813, 493, 338, 208, 2061, 1249, 858, 528, 1600, 970, 666, 410, 1159, 702, 482, 297, 919, 557, 382, 235, 2232, 1352, 929, 572, 1708, 1035, 711, 438, 1224, 742, 509, 314, 969, 587, 403, 248, 2409, 1460, 1003, 618, 1872, 1134, 779, 480, 1358, 823, 565, 348, 1056, 640, 439, 270, 2620, 1588, 1091, 672, 2059, 1248, 857, 528, 1468, 890, 611, 376, 1108, 672, 461, 284, 2812, 1704, 1171, 721, 2188, 1326, 911, 561, 1588, 963, 661, 407, 1228, 744, 511, 315, 3057, 1853, 1273, 784, 2395, 1451, 997, 614, 1718, 1041, 715, 440, 1286, 779, 535, 330, 3283, 1990, 1367, 842, 2544, 1542, 1059, 652, 1804, 1094, 751, 462, 1425, 864, 593, 365, 3517, 2132, 1465, 902, 2701, 1637, 1125, 692, 1933, 1172, 805, 496, 1501, 910, 625, 385, 3669, 2223, 1528, 940, 2857, 1732, 1190, 732, 2085, 1263, 868, 534, 1581, 958, 658, 405, 3909, 2369, 1628, 1002, 3035, 1839, 1264, 778, 2181, 1322, 908, 559, 1677, 1016, 698, 430, 4158, 2520, 1732, 1066, 3289, 1994, 1370, 843, 2358, 1429, 982, 604, 1782, 1080, 742, 457, 4417, 2677, 1840, 1132, 3486, 2113, 1452, 894, 2473, 1499, 1030, 634, 1897, 1150, 790, 486, 4686, 2840, 1952, 1201, 3693, 2238, 1538, 947, 2670, 1618, 1112, 684, 2022, 1226, 842, 518, 4965, 3009, 2068, 1273, 3909, 2369, 1628, 1002, 2805, 1700, 1168, 719, 2157, 1307, 898, 553, 5253, 3183, 2188, 1347, 4134, 2506, 1722, 1060, 2949, 1787, 1228, 756, 2301, 1394, 958, 590, 5529, 3351, 2303, 1417, 4343, 2632, 1809, 1113, 3081, 1867, 1283, 790, 2361, 1431, 983, 605, 5836, 3537, 2431, 1496, 4588, 2780, 1911, 1176, 3244, 1966, 1351, 832, 2524, 1530, 1051, 647, 6153, 3729, 2563, 1577, 4775, 2894, 1989, 1224, 3417, 2071, 1423, 876, 2625, 1591, 1093, 673, 6479, 3927, 2699, 1661, 5039, 3054, 2099, 1292, 3599, 2181, 1499, 923, 2735, 1658, 1139, 701, 6743, 4087, 2809, 1729, 5313, 3220, 2213, 1362, 3791, 2298, 1579, 972, 2927, 1774, 1219, 750, 7089, 4296, 2953, 1817, 5596, 3391, 2331, 1435, 3993, 2420, 1663, 1024, 3057, 1852, 1273, 784 }; + private static readonly int[] _capacityECCBaseValues = { 19, 7, 1, 19, 0, 0, 16, 10, 1, 16, 0, 0, 13, 13, 1, 13, 0, 0, 9, 17, 1, 9, 0, 0, 34, 10, 1, 34, 0, 0, 28, 16, 1, 28, 0, 0, 22, 22, 1, 22, 0, 0, 16, 28, 1, 16, 0, 0, 55, 15, 1, 55, 0, 0, 44, 26, 1, 44, 0, 0, 34, 18, 2, 17, 0, 0, 26, 22, 2, 13, 0, 0, 80, 20, 1, 80, 0, 0, 64, 18, 2, 32, 0, 0, 48, 26, 2, 24, 0, 0, 36, 16, 4, 9, 0, 0, 108, 26, 1, 108, 0, 0, 86, 24, 2, 43, 0, 0, 62, 18, 2, 15, 2, 16, 46, 22, 2, 11, 2, 12, 136, 18, 2, 68, 0, 0, 108, 16, 4, 27, 0, 0, 76, 24, 4, 19, 0, 0, 60, 28, 4, 15, 0, 0, 156, 20, 2, 78, 0, 0, 124, 18, 4, 31, 0, 0, 88, 18, 2, 14, 4, 15, 66, 26, 4, 13, 1, 14, 194, 24, 2, 97, 0, 0, 154, 22, 2, 38, 2, 39, 110, 22, 4, 18, 2, 19, 86, 26, 4, 14, 2, 15, 232, 30, 2, 116, 0, 0, 182, 22, 3, 36, 2, 37, 132, 20, 4, 16, 4, 17, 100, 24, 4, 12, 4, 13, 274, 18, 2, 68, 2, 69, 216, 26, 4, 43, 1, 44, 154, 24, 6, 19, 2, 20, 122, 28, 6, 15, 2, 16, 324, 20, 4, 81, 0, 0, 254, 30, 1, 50, 4, 51, 180, 28, 4, 22, 4, 23, 140, 24, 3, 12, 8, 13, 370, 24, 2, 92, 2, 93, 290, 22, 6, 36, 2, 37, 206, 26, 4, 20, 6, 21, 158, 28, 7, 14, 4, 15, 428, 26, 4, 107, 0, 0, 334, 22, 8, 37, 1, 38, 244, 24, 8, 20, 4, 21, 180, 22, 12, 11, 4, 12, 461, 30, 3, 115, 1, 116, 365, 24, 4, 40, 5, 41, 261, 20, 11, 16, 5, 17, 197, 24, 11, 12, 5, 13, 523, 22, 5, 87, 1, 88, 415, 24, 5, 41, 5, 42, 295, 30, 5, 24, 7, 25, 223, 24, 11, 12, 7, 13, 589, 24, 5, 98, 1, 99, 453, 28, 7, 45, 3, 46, 325, 24, 15, 19, 2, 20, 253, 30, 3, 15, 13, 16, 647, 28, 1, 107, 5, 108, 507, 28, 10, 46, 1, 47, 367, 28, 1, 22, 15, 23, 283, 28, 2, 14, 17, 15, 721, 30, 5, 120, 1, 121, 563, 26, 9, 43, 4, 44, 397, 28, 17, 22, 1, 23, 313, 28, 2, 14, 19, 15, 795, 28, 3, 113, 4, 114, 627, 26, 3, 44, 11, 45, 445, 26, 17, 21, 4, 22, 341, 26, 9, 13, 16, 14, 861, 28, 3, 107, 5, 108, 669, 26, 3, 41, 13, 42, 485, 30, 15, 24, 5, 25, 385, 28, 15, 15, 10, 16, 932, 28, 4, 116, 4, 117, 714, 26, 17, 42, 0, 0, 512, 28, 17, 22, 6, 23, 406, 30, 19, 16, 6, 17, 1006, 28, 2, 111, 7, 112, 782, 28, 17, 46, 0, 0, 568, 30, 7, 24, 16, 25, 442, 24, 34, 13, 0, 0, 1094, 30, 4, 121, 5, 122, 860, 28, 4, 47, 14, 48, 614, 30, 11, 24, 14, 25, 464, 30, 16, 15, 14, 16, 1174, 30, 6, 117, 4, 118, 914, 28, 6, 45, 14, 46, 664, 30, 11, 24, 16, 25, 514, 30, 30, 16, 2, 17, 1276, 26, 8, 106, 4, 107, 1000, 28, 8, 47, 13, 48, 718, 30, 7, 24, 22, 25, 538, 30, 22, 15, 13, 16, 1370, 28, 10, 114, 2, 115, 1062, 28, 19, 46, 4, 47, 754, 28, 28, 22, 6, 23, 596, 30, 33, 16, 4, 17, 1468, 30, 8, 122, 4, 123, 1128, 28, 22, 45, 3, 46, 808, 30, 8, 23, 26, 24, 628, 30, 12, 15, 28, 16, 1531, 30, 3, 117, 10, 118, 1193, 28, 3, 45, 23, 46, 871, 30, 4, 24, 31, 25, 661, 30, 11, 15, 31, 16, 1631, 30, 7, 116, 7, 117, 1267, 28, 21, 45, 7, 46, 911, 30, 1, 23, 37, 24, 701, 30, 19, 15, 26, 16, 1735, 30, 5, 115, 10, 116, 1373, 28, 19, 47, 10, 48, 985, 30, 15, 24, 25, 25, 745, 30, 23, 15, 25, 16, 1843, 30, 13, 115, 3, 116, 1455, 28, 2, 46, 29, 47, 1033, 30, 42, 24, 1, 25, 793, 30, 23, 15, 28, 16, 1955, 30, 17, 115, 0, 0, 1541, 28, 10, 46, 23, 47, 1115, 30, 10, 24, 35, 25, 845, 30, 19, 15, 35, 16, 2071, 30, 17, 115, 1, 116, 1631, 28, 14, 46, 21, 47, 1171, 30, 29, 24, 19, 25, 901, 30, 11, 15, 46, 16, 2191, 30, 13, 115, 6, 116, 1725, 28, 14, 46, 23, 47, 1231, 30, 44, 24, 7, 25, 961, 30, 59, 16, 1, 17, 2306, 30, 12, 121, 7, 122, 1812, 28, 12, 47, 26, 48, 1286, 30, 39, 24, 14, 25, 986, 30, 22, 15, 41, 16, 2434, 30, 6, 121, 14, 122, 1914, 28, 6, 47, 34, 48, 1354, 30, 46, 24, 10, 25, 1054, 30, 2, 15, 64, 16, 2566, 30, 17, 122, 4, 123, 1992, 28, 29, 46, 14, 47, 1426, 30, 49, 24, 10, 25, 1096, 30, 24, 15, 46, 16, 2702, 30, 4, 122, 18, 123, 2102, 28, 13, 46, 32, 47, 1502, 30, 48, 24, 14, 25, 1142, 30, 42, 15, 32, 16, 2812, 30, 20, 117, 4, 118, 2216, 28, 40, 47, 7, 48, 1582, 30, 43, 24, 22, 25, 1222, 30, 10, 15, 67, 16, 2956, 30, 19, 118, 6, 119, 2334, 28, 18, 47, 31, 48, 1666, 30, 34, 24, 34, 25, 1276, 30, 20, 15, 61, 16 }; + + /// + /// Creates a dictionary mapping alphanumeric characters to their respective positions used in QR code encoding. + /// This includes digits 0-9, uppercase letters A-Z, and some special characters. + /// + /// A dictionary mapping each supported alphanumeric character to its corresponding value. + private static Dictionary CreateAlphanumEncDict() + { + var localAlphanumEncDict = new Dictionary(45); + for (int i = 0; i < 10; i++) + localAlphanumEncDict.Add($"{i}"[0], i); + // Add uppercase alphabetic characters. + for (char c = 'A'; c <= 'Z'; c++) + localAlphanumEncDict.Add(c, localAlphanumEncDict.Count); + // Add special characters from a predefined table. + for (int i = 0; i < _alphanumEncTable.Length; i++) + localAlphanumEncDict.Add(_alphanumEncTable[i], localAlphanumEncDict.Count); + return localAlphanumEncDict; + } + + /// + /// Creates a lookup table mapping QR code versions to their corresponding alignment patterns. + /// Alignment patterns are used in QR codes to help scanners accurately read the code at high speeds and when partially obscured. + /// This table provides the necessary patterns based on the QR code version which dictates the size and complexity of the QR code. + /// + /// A dictionary where keys are QR code version numbers and values are AlignmentPattern structures detailing the positions of alignment patterns for each version. + private static Dictionary CreateAlignmentPatternTable() + { + var localAlignmentPatternTable = new Dictionary(40); + + for (var i = 0; i < (7 * 40); i += 7) + { + var points = new List(50); + for (var x = 0; x < 7; x++) + { + if (_alignmentPatternBaseValues[i + x] != 0) + { + for (var y = 0; y < 7; y++) + { + if (_alignmentPatternBaseValues[i + y] != 0) + { + var p = new Point(_alignmentPatternBaseValues[i + x] - 2, _alignmentPatternBaseValues[i + y] - 2); + if (!points.Contains(p)) + points.Add(p); + } + } + } + } + + var version = (i + 7) / 7; + localAlignmentPatternTable.Add(version, new AlignmentPattern() + { + Version = version, + PatternPositions = points + } + ); + } + return localAlignmentPatternTable; + } + + /// + /// Generates a table containing the error correction capacities and data codeword information for different QR code versions and error correction levels. + /// This table is essential for determining how much data can be encoded in a QR code of a specific version and ECC level, + /// as well as how robust the QR code will be against distortions or obstructions. + /// + /// A list of ECCInfo structures, each representing the ECC data and capacities for different combinations of QR code versions and ECC levels. + private static List CreateCapacityECCTable() + { + var localCapacityECCTable = new List(160); + for (var i = 0; i < (4 * 6 * 40); i += (4 * 6)) + { + localCapacityECCTable.AddRange( + new[] + { + new ECCInfo( + (i+24) / 24, + ECCLevel.L, + _capacityECCBaseValues[i], + _capacityECCBaseValues[i+1], + _capacityECCBaseValues[i+2], + _capacityECCBaseValues[i+3], + _capacityECCBaseValues[i+4], + _capacityECCBaseValues[i+5]), + new ECCInfo + ( + version: (i + 24) / 24, + errorCorrectionLevel: ECCLevel.M, + totalDataCodewords: _capacityECCBaseValues[i+6], + eccPerBlock: _capacityECCBaseValues[i+7], + blocksInGroup1: _capacityECCBaseValues[i+8], + codewordsInGroup1: _capacityECCBaseValues[i+9], + blocksInGroup2: _capacityECCBaseValues[i+10], + codewordsInGroup2: _capacityECCBaseValues[i+11] + ), + new ECCInfo + ( + version: (i + 24) / 24, + errorCorrectionLevel: ECCLevel.Q, + totalDataCodewords: _capacityECCBaseValues[i+12], + eccPerBlock: _capacityECCBaseValues[i+13], + blocksInGroup1: _capacityECCBaseValues[i+14], + codewordsInGroup1: _capacityECCBaseValues[i+15], + blocksInGroup2: _capacityECCBaseValues[i+16], + codewordsInGroup2: _capacityECCBaseValues[i+17] + ), + new ECCInfo + ( + version: (i + 24) / 24, + errorCorrectionLevel: ECCLevel.H, + totalDataCodewords: _capacityECCBaseValues[i+18], + eccPerBlock: _capacityECCBaseValues[i+19], + blocksInGroup1: _capacityECCBaseValues[i+20], + codewordsInGroup1: _capacityECCBaseValues[i+21], + blocksInGroup2: _capacityECCBaseValues[i+22], + codewordsInGroup2: _capacityECCBaseValues[i+23] + ) + }); + } + return localCapacityECCTable; + } + + /// + /// Generates a list containing detailed capacity information for various versions of QR codes. + /// This table includes capacities for different encoding modes (numeric, alphanumeric, byte, etc.) under each error correction level. + /// The capacity table is crucial for QR code generation, as it determines how much data each QR code version can store depending on the encoding mode and error correction level used. + /// + private static List CreateCapacityTable() + { + var localCapacityTable = new List(40); + for (var i = 0; i < (16 * 40); i += 16) + { + localCapacityTable.Add(new VersionInfo( + + (i + 16) / 16, + new List(4) + { + new VersionInfoDetails( + ECCLevel.L, + new Dictionary(){ + { EncodingMode.Numeric, _capacityBaseValues[i] }, + { EncodingMode.Alphanumeric, _capacityBaseValues[i+1] }, + { EncodingMode.Byte, _capacityBaseValues[i+2] }, + { EncodingMode.Kanji, _capacityBaseValues[i+3] }, + } + ), + new VersionInfoDetails( + ECCLevel.M, + new Dictionary(){ + { EncodingMode.Numeric, _capacityBaseValues[i+4] }, + { EncodingMode.Alphanumeric, _capacityBaseValues[i+5] }, + { EncodingMode.Byte, _capacityBaseValues[i+6] }, + { EncodingMode.Kanji, _capacityBaseValues[i+7] }, + } + ), + new VersionInfoDetails( + ECCLevel.Q, + new Dictionary(){ + { EncodingMode.Numeric, _capacityBaseValues[i+8] }, + { EncodingMode.Alphanumeric, _capacityBaseValues[i+9] }, + { EncodingMode.Byte, _capacityBaseValues[i+10] }, + { EncodingMode.Kanji, _capacityBaseValues[i+11] }, + } + ), + new VersionInfoDetails( + ECCLevel.H, + new Dictionary(){ + { EncodingMode.Numeric, _capacityBaseValues[i+12] }, + { EncodingMode.Alphanumeric, _capacityBaseValues[i+13] }, + { EncodingMode.Byte, _capacityBaseValues[i+14] }, + { EncodingMode.Kanji, _capacityBaseValues[i+15] }, + } + ) + } + )); + } + return localCapacityTable; + } +} diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index f446a3f4..b15dcbd8 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -17,8 +17,6 @@ namespace QRCoder; public partial class QRCodeGenerator : IDisposable { private static readonly char[] _alphanumEncTable = { ' ', '$', '%', '*', '+', '-', '.', '/', ':' }; - private static readonly int[] _capacityBaseValues = { 41, 25, 17, 10, 34, 20, 14, 8, 27, 16, 11, 7, 17, 10, 7, 4, 77, 47, 32, 20, 63, 38, 26, 16, 48, 29, 20, 12, 34, 20, 14, 8, 127, 77, 53, 32, 101, 61, 42, 26, 77, 47, 32, 20, 58, 35, 24, 15, 187, 114, 78, 48, 149, 90, 62, 38, 111, 67, 46, 28, 82, 50, 34, 21, 255, 154, 106, 65, 202, 122, 84, 52, 144, 87, 60, 37, 106, 64, 44, 27, 322, 195, 134, 82, 255, 154, 106, 65, 178, 108, 74, 45, 139, 84, 58, 36, 370, 224, 154, 95, 293, 178, 122, 75, 207, 125, 86, 53, 154, 93, 64, 39, 461, 279, 192, 118, 365, 221, 152, 93, 259, 157, 108, 66, 202, 122, 84, 52, 552, 335, 230, 141, 432, 262, 180, 111, 312, 189, 130, 80, 235, 143, 98, 60, 652, 395, 271, 167, 513, 311, 213, 131, 364, 221, 151, 93, 288, 174, 119, 74, 772, 468, 321, 198, 604, 366, 251, 155, 427, 259, 177, 109, 331, 200, 137, 85, 883, 535, 367, 226, 691, 419, 287, 177, 489, 296, 203, 125, 374, 227, 155, 96, 1022, 619, 425, 262, 796, 483, 331, 204, 580, 352, 241, 149, 427, 259, 177, 109, 1101, 667, 458, 282, 871, 528, 362, 223, 621, 376, 258, 159, 468, 283, 194, 120, 1250, 758, 520, 320, 991, 600, 412, 254, 703, 426, 292, 180, 530, 321, 220, 136, 1408, 854, 586, 361, 1082, 656, 450, 277, 775, 470, 322, 198, 602, 365, 250, 154, 1548, 938, 644, 397, 1212, 734, 504, 310, 876, 531, 364, 224, 674, 408, 280, 173, 1725, 1046, 718, 442, 1346, 816, 560, 345, 948, 574, 394, 243, 746, 452, 310, 191, 1903, 1153, 792, 488, 1500, 909, 624, 384, 1063, 644, 442, 272, 813, 493, 338, 208, 2061, 1249, 858, 528, 1600, 970, 666, 410, 1159, 702, 482, 297, 919, 557, 382, 235, 2232, 1352, 929, 572, 1708, 1035, 711, 438, 1224, 742, 509, 314, 969, 587, 403, 248, 2409, 1460, 1003, 618, 1872, 1134, 779, 480, 1358, 823, 565, 348, 1056, 640, 439, 270, 2620, 1588, 1091, 672, 2059, 1248, 857, 528, 1468, 890, 611, 376, 1108, 672, 461, 284, 2812, 1704, 1171, 721, 2188, 1326, 911, 561, 1588, 963, 661, 407, 1228, 744, 511, 315, 3057, 1853, 1273, 784, 2395, 1451, 997, 614, 1718, 1041, 715, 440, 1286, 779, 535, 330, 3283, 1990, 1367, 842, 2544, 1542, 1059, 652, 1804, 1094, 751, 462, 1425, 864, 593, 365, 3517, 2132, 1465, 902, 2701, 1637, 1125, 692, 1933, 1172, 805, 496, 1501, 910, 625, 385, 3669, 2223, 1528, 940, 2857, 1732, 1190, 732, 2085, 1263, 868, 534, 1581, 958, 658, 405, 3909, 2369, 1628, 1002, 3035, 1839, 1264, 778, 2181, 1322, 908, 559, 1677, 1016, 698, 430, 4158, 2520, 1732, 1066, 3289, 1994, 1370, 843, 2358, 1429, 982, 604, 1782, 1080, 742, 457, 4417, 2677, 1840, 1132, 3486, 2113, 1452, 894, 2473, 1499, 1030, 634, 1897, 1150, 790, 486, 4686, 2840, 1952, 1201, 3693, 2238, 1538, 947, 2670, 1618, 1112, 684, 2022, 1226, 842, 518, 4965, 3009, 2068, 1273, 3909, 2369, 1628, 1002, 2805, 1700, 1168, 719, 2157, 1307, 898, 553, 5253, 3183, 2188, 1347, 4134, 2506, 1722, 1060, 2949, 1787, 1228, 756, 2301, 1394, 958, 590, 5529, 3351, 2303, 1417, 4343, 2632, 1809, 1113, 3081, 1867, 1283, 790, 2361, 1431, 983, 605, 5836, 3537, 2431, 1496, 4588, 2780, 1911, 1176, 3244, 1966, 1351, 832, 2524, 1530, 1051, 647, 6153, 3729, 2563, 1577, 4775, 2894, 1989, 1224, 3417, 2071, 1423, 876, 2625, 1591, 1093, 673, 6479, 3927, 2699, 1661, 5039, 3054, 2099, 1292, 3599, 2181, 1499, 923, 2735, 1658, 1139, 701, 6743, 4087, 2809, 1729, 5313, 3220, 2213, 1362, 3791, 2298, 1579, 972, 2927, 1774, 1219, 750, 7089, 4296, 2953, 1817, 5596, 3391, 2331, 1435, 3993, 2420, 1663, 1024, 3057, 1852, 1273, 784 }; - private static readonly int[] _capacityECCBaseValues = { 19, 7, 1, 19, 0, 0, 16, 10, 1, 16, 0, 0, 13, 13, 1, 13, 0, 0, 9, 17, 1, 9, 0, 0, 34, 10, 1, 34, 0, 0, 28, 16, 1, 28, 0, 0, 22, 22, 1, 22, 0, 0, 16, 28, 1, 16, 0, 0, 55, 15, 1, 55, 0, 0, 44, 26, 1, 44, 0, 0, 34, 18, 2, 17, 0, 0, 26, 22, 2, 13, 0, 0, 80, 20, 1, 80, 0, 0, 64, 18, 2, 32, 0, 0, 48, 26, 2, 24, 0, 0, 36, 16, 4, 9, 0, 0, 108, 26, 1, 108, 0, 0, 86, 24, 2, 43, 0, 0, 62, 18, 2, 15, 2, 16, 46, 22, 2, 11, 2, 12, 136, 18, 2, 68, 0, 0, 108, 16, 4, 27, 0, 0, 76, 24, 4, 19, 0, 0, 60, 28, 4, 15, 0, 0, 156, 20, 2, 78, 0, 0, 124, 18, 4, 31, 0, 0, 88, 18, 2, 14, 4, 15, 66, 26, 4, 13, 1, 14, 194, 24, 2, 97, 0, 0, 154, 22, 2, 38, 2, 39, 110, 22, 4, 18, 2, 19, 86, 26, 4, 14, 2, 15, 232, 30, 2, 116, 0, 0, 182, 22, 3, 36, 2, 37, 132, 20, 4, 16, 4, 17, 100, 24, 4, 12, 4, 13, 274, 18, 2, 68, 2, 69, 216, 26, 4, 43, 1, 44, 154, 24, 6, 19, 2, 20, 122, 28, 6, 15, 2, 16, 324, 20, 4, 81, 0, 0, 254, 30, 1, 50, 4, 51, 180, 28, 4, 22, 4, 23, 140, 24, 3, 12, 8, 13, 370, 24, 2, 92, 2, 93, 290, 22, 6, 36, 2, 37, 206, 26, 4, 20, 6, 21, 158, 28, 7, 14, 4, 15, 428, 26, 4, 107, 0, 0, 334, 22, 8, 37, 1, 38, 244, 24, 8, 20, 4, 21, 180, 22, 12, 11, 4, 12, 461, 30, 3, 115, 1, 116, 365, 24, 4, 40, 5, 41, 261, 20, 11, 16, 5, 17, 197, 24, 11, 12, 5, 13, 523, 22, 5, 87, 1, 88, 415, 24, 5, 41, 5, 42, 295, 30, 5, 24, 7, 25, 223, 24, 11, 12, 7, 13, 589, 24, 5, 98, 1, 99, 453, 28, 7, 45, 3, 46, 325, 24, 15, 19, 2, 20, 253, 30, 3, 15, 13, 16, 647, 28, 1, 107, 5, 108, 507, 28, 10, 46, 1, 47, 367, 28, 1, 22, 15, 23, 283, 28, 2, 14, 17, 15, 721, 30, 5, 120, 1, 121, 563, 26, 9, 43, 4, 44, 397, 28, 17, 22, 1, 23, 313, 28, 2, 14, 19, 15, 795, 28, 3, 113, 4, 114, 627, 26, 3, 44, 11, 45, 445, 26, 17, 21, 4, 22, 341, 26, 9, 13, 16, 14, 861, 28, 3, 107, 5, 108, 669, 26, 3, 41, 13, 42, 485, 30, 15, 24, 5, 25, 385, 28, 15, 15, 10, 16, 932, 28, 4, 116, 4, 117, 714, 26, 17, 42, 0, 0, 512, 28, 17, 22, 6, 23, 406, 30, 19, 16, 6, 17, 1006, 28, 2, 111, 7, 112, 782, 28, 17, 46, 0, 0, 568, 30, 7, 24, 16, 25, 442, 24, 34, 13, 0, 0, 1094, 30, 4, 121, 5, 122, 860, 28, 4, 47, 14, 48, 614, 30, 11, 24, 14, 25, 464, 30, 16, 15, 14, 16, 1174, 30, 6, 117, 4, 118, 914, 28, 6, 45, 14, 46, 664, 30, 11, 24, 16, 25, 514, 30, 30, 16, 2, 17, 1276, 26, 8, 106, 4, 107, 1000, 28, 8, 47, 13, 48, 718, 30, 7, 24, 22, 25, 538, 30, 22, 15, 13, 16, 1370, 28, 10, 114, 2, 115, 1062, 28, 19, 46, 4, 47, 754, 28, 28, 22, 6, 23, 596, 30, 33, 16, 4, 17, 1468, 30, 8, 122, 4, 123, 1128, 28, 22, 45, 3, 46, 808, 30, 8, 23, 26, 24, 628, 30, 12, 15, 28, 16, 1531, 30, 3, 117, 10, 118, 1193, 28, 3, 45, 23, 46, 871, 30, 4, 24, 31, 25, 661, 30, 11, 15, 31, 16, 1631, 30, 7, 116, 7, 117, 1267, 28, 21, 45, 7, 46, 911, 30, 1, 23, 37, 24, 701, 30, 19, 15, 26, 16, 1735, 30, 5, 115, 10, 116, 1373, 28, 19, 47, 10, 48, 985, 30, 15, 24, 25, 25, 745, 30, 23, 15, 25, 16, 1843, 30, 13, 115, 3, 116, 1455, 28, 2, 46, 29, 47, 1033, 30, 42, 24, 1, 25, 793, 30, 23, 15, 28, 16, 1955, 30, 17, 115, 0, 0, 1541, 28, 10, 46, 23, 47, 1115, 30, 10, 24, 35, 25, 845, 30, 19, 15, 35, 16, 2071, 30, 17, 115, 1, 116, 1631, 28, 14, 46, 21, 47, 1171, 30, 29, 24, 19, 25, 901, 30, 11, 15, 46, 16, 2191, 30, 13, 115, 6, 116, 1725, 28, 14, 46, 23, 47, 1231, 30, 44, 24, 7, 25, 961, 30, 59, 16, 1, 17, 2306, 30, 12, 121, 7, 122, 1812, 28, 12, 47, 26, 48, 1286, 30, 39, 24, 14, 25, 986, 30, 22, 15, 41, 16, 2434, 30, 6, 121, 14, 122, 1914, 28, 6, 47, 34, 48, 1354, 30, 46, 24, 10, 25, 1054, 30, 2, 15, 64, 16, 2566, 30, 17, 122, 4, 123, 1992, 28, 29, 46, 14, 47, 1426, 30, 49, 24, 10, 25, 1096, 30, 24, 15, 46, 16, 2702, 30, 4, 122, 18, 123, 2102, 28, 13, 46, 32, 47, 1502, 30, 48, 24, 14, 25, 1142, 30, 42, 15, 32, 16, 2812, 30, 20, 117, 4, 118, 2216, 28, 40, 47, 7, 48, 1582, 30, 43, 24, 22, 25, 1222, 30, 10, 15, 67, 16, 2956, 30, 19, 118, 6, 119, 2334, 28, 18, 47, 31, 48, 1666, 30, 34, 24, 34, 25, 1276, 30, 20, 15, 61, 16 }; private static readonly int[] _alignmentPatternBaseValues = { 0, 0, 0, 0, 0, 0, 0, 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 }; private static readonly int[] _remainderBits = { 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 }; @@ -1188,183 +1186,6 @@ private static int GetAlphaExpFromIntVal(int intVal) private static int ShrinkAlphaExp(int alphaExp) => (int)((alphaExp % 256) + Math.Floor((double)(alphaExp / 256))); - /// - /// Creates a dictionary mapping alphanumeric characters to their respective positions used in QR code encoding. - /// This includes digits 0-9, uppercase letters A-Z, and some special characters. - /// - /// A dictionary mapping each supported alphanumeric character to its corresponding value. - private static Dictionary CreateAlphanumEncDict() - { - var localAlphanumEncDict = new Dictionary(45); - for (int i = 0; i < 10; i++) - localAlphanumEncDict.Add($"{i}"[0], i); - // Add uppercase alphabetic characters. - for (char c = 'A'; c <= 'Z'; c++) - localAlphanumEncDict.Add(c, localAlphanumEncDict.Count); - // Add special characters from a predefined table. - for (int i = 0; i < _alphanumEncTable.Length; i++) - localAlphanumEncDict.Add(_alphanumEncTable[i], localAlphanumEncDict.Count); - return localAlphanumEncDict; - } - - /// - /// Creates a lookup table mapping QR code versions to their corresponding alignment patterns. - /// Alignment patterns are used in QR codes to help scanners accurately read the code at high speeds and when partially obscured. - /// This table provides the necessary patterns based on the QR code version which dictates the size and complexity of the QR code. - /// - /// A dictionary where keys are QR code version numbers and values are AlignmentPattern structures detailing the positions of alignment patterns for each version. - private static Dictionary CreateAlignmentPatternTable() - { - var localAlignmentPatternTable = new Dictionary(40); - - for (var i = 0; i < (7 * 40); i += 7) - { - var points = new List(50); - for (var x = 0; x < 7; x++) - { - if (_alignmentPatternBaseValues[i + x] != 0) - { - for (var y = 0; y < 7; y++) - { - if (_alignmentPatternBaseValues[i + y] != 0) - { - var p = new Point(_alignmentPatternBaseValues[i + x] - 2, _alignmentPatternBaseValues[i + y] - 2); - if (!points.Contains(p)) - points.Add(p); - } - } - } - } - - var version = (i + 7) / 7; - localAlignmentPatternTable.Add(version, new AlignmentPattern() - { - Version = version, - PatternPositions = points - } - ); - } - return localAlignmentPatternTable; - } - - /// - /// Generates a table containing the error correction capacities and data codeword information for different QR code versions and error correction levels. - /// This table is essential for determining how much data can be encoded in a QR code of a specific version and ECC level, - /// as well as how robust the QR code will be against distortions or obstructions. - /// - /// A list of ECCInfo structures, each representing the ECC data and capacities for different combinations of QR code versions and ECC levels. - private static List CreateCapacityECCTable() - { - var localCapacityECCTable = new List(160); - for (var i = 0; i < (4 * 6 * 40); i += (4 * 6)) - { - localCapacityECCTable.AddRange( - new[] - { - new ECCInfo( - (i+24) / 24, - ECCLevel.L, - _capacityECCBaseValues[i], - _capacityECCBaseValues[i+1], - _capacityECCBaseValues[i+2], - _capacityECCBaseValues[i+3], - _capacityECCBaseValues[i+4], - _capacityECCBaseValues[i+5]), - new ECCInfo - ( - version: (i + 24) / 24, - errorCorrectionLevel: ECCLevel.M, - totalDataCodewords: _capacityECCBaseValues[i+6], - eccPerBlock: _capacityECCBaseValues[i+7], - blocksInGroup1: _capacityECCBaseValues[i+8], - codewordsInGroup1: _capacityECCBaseValues[i+9], - blocksInGroup2: _capacityECCBaseValues[i+10], - codewordsInGroup2: _capacityECCBaseValues[i+11] - ), - new ECCInfo - ( - version: (i + 24) / 24, - errorCorrectionLevel: ECCLevel.Q, - totalDataCodewords: _capacityECCBaseValues[i+12], - eccPerBlock: _capacityECCBaseValues[i+13], - blocksInGroup1: _capacityECCBaseValues[i+14], - codewordsInGroup1: _capacityECCBaseValues[i+15], - blocksInGroup2: _capacityECCBaseValues[i+16], - codewordsInGroup2: _capacityECCBaseValues[i+17] - ), - new ECCInfo - ( - version: (i + 24) / 24, - errorCorrectionLevel: ECCLevel.H, - totalDataCodewords: _capacityECCBaseValues[i+18], - eccPerBlock: _capacityECCBaseValues[i+19], - blocksInGroup1: _capacityECCBaseValues[i+20], - codewordsInGroup1: _capacityECCBaseValues[i+21], - blocksInGroup2: _capacityECCBaseValues[i+22], - codewordsInGroup2: _capacityECCBaseValues[i+23] - ) - }); - } - return localCapacityECCTable; - } - - /// - /// Generates a list containing detailed capacity information for various versions of QR codes. - /// This table includes capacities for different encoding modes (numeric, alphanumeric, byte, etc.) under each error correction level. - /// The capacity table is crucial for QR code generation, as it determines how much data each QR code version can store depending on the encoding mode and error correction level used. - /// - private static List CreateCapacityTable() - { - var localCapacityTable = new List(40); - for (var i = 0; i < (16 * 40); i += 16) - { - localCapacityTable.Add(new VersionInfo( - - (i + 16) / 16, - new List(4) - { - new VersionInfoDetails( - ECCLevel.L, - new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i+1] }, - { EncodingMode.Byte, _capacityBaseValues[i+2] }, - { EncodingMode.Kanji, _capacityBaseValues[i+3] }, - } - ), - new VersionInfoDetails( - ECCLevel.M, - new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i+4] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i+5] }, - { EncodingMode.Byte, _capacityBaseValues[i+6] }, - { EncodingMode.Kanji, _capacityBaseValues[i+7] }, - } - ), - new VersionInfoDetails( - ECCLevel.Q, - new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i+8] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i+9] }, - { EncodingMode.Byte, _capacityBaseValues[i+10] }, - { EncodingMode.Kanji, _capacityBaseValues[i+11] }, - } - ), - new VersionInfoDetails( - ECCLevel.H, - new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i+12] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i+13] }, - { EncodingMode.Byte, _capacityBaseValues[i+14] }, - { EncodingMode.Kanji, _capacityBaseValues[i+15] }, - } - ) - } - )); - } - return localCapacityTable; - } - /// public void Dispose() { diff --git a/QRCoderTests/PngByteQRCodeRendererTests.cs b/QRCoderTests/PngByteQRCodeRendererTests.cs index d73b8e43..f2850c30 100644 --- a/QRCoderTests/PngByteQRCodeRendererTests.cs +++ b/QRCoderTests/PngByteQRCodeRendererTests.cs @@ -113,7 +113,6 @@ public void can_render_pngbyte_qrcode_color_without_quietzones() var result = HelperFunctions.ByteArrayToHash(pngCodeGfx); result.ShouldBe("07f760b3eb54901840b094d31e299713"); #else - File.WriteAllBytes(@"C:\Temp\pngbyte_35.png", pngCodeGfx); using var mStream = new MemoryStream(pngCodeGfx); var bmp = (Bitmap)Image.FromStream(mStream); bmp.MakeTransparent(Color.Transparent); From e10801de75141e79431a9702a4df07907c98cd7d Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 6 Apr 2025 10:57:59 -0400 Subject: [PATCH 02/22] update --- QRCoder/QRCodeGenerator.Tables.cs | 3 --- QRCoder/QRCodeGenerator.cs | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/QRCoder/QRCodeGenerator.Tables.cs b/QRCoder/QRCodeGenerator.Tables.cs index c1052efa..29ecc02c 100644 --- a/QRCoder/QRCodeGenerator.Tables.cs +++ b/QRCoder/QRCodeGenerator.Tables.cs @@ -7,9 +7,6 @@ namespace QRCoder; /// public partial class QRCodeGenerator { - private static readonly int[] _capacityBaseValues = { 41, 25, 17, 10, 34, 20, 14, 8, 27, 16, 11, 7, 17, 10, 7, 4, 77, 47, 32, 20, 63, 38, 26, 16, 48, 29, 20, 12, 34, 20, 14, 8, 127, 77, 53, 32, 101, 61, 42, 26, 77, 47, 32, 20, 58, 35, 24, 15, 187, 114, 78, 48, 149, 90, 62, 38, 111, 67, 46, 28, 82, 50, 34, 21, 255, 154, 106, 65, 202, 122, 84, 52, 144, 87, 60, 37, 106, 64, 44, 27, 322, 195, 134, 82, 255, 154, 106, 65, 178, 108, 74, 45, 139, 84, 58, 36, 370, 224, 154, 95, 293, 178, 122, 75, 207, 125, 86, 53, 154, 93, 64, 39, 461, 279, 192, 118, 365, 221, 152, 93, 259, 157, 108, 66, 202, 122, 84, 52, 552, 335, 230, 141, 432, 262, 180, 111, 312, 189, 130, 80, 235, 143, 98, 60, 652, 395, 271, 167, 513, 311, 213, 131, 364, 221, 151, 93, 288, 174, 119, 74, 772, 468, 321, 198, 604, 366, 251, 155, 427, 259, 177, 109, 331, 200, 137, 85, 883, 535, 367, 226, 691, 419, 287, 177, 489, 296, 203, 125, 374, 227, 155, 96, 1022, 619, 425, 262, 796, 483, 331, 204, 580, 352, 241, 149, 427, 259, 177, 109, 1101, 667, 458, 282, 871, 528, 362, 223, 621, 376, 258, 159, 468, 283, 194, 120, 1250, 758, 520, 320, 991, 600, 412, 254, 703, 426, 292, 180, 530, 321, 220, 136, 1408, 854, 586, 361, 1082, 656, 450, 277, 775, 470, 322, 198, 602, 365, 250, 154, 1548, 938, 644, 397, 1212, 734, 504, 310, 876, 531, 364, 224, 674, 408, 280, 173, 1725, 1046, 718, 442, 1346, 816, 560, 345, 948, 574, 394, 243, 746, 452, 310, 191, 1903, 1153, 792, 488, 1500, 909, 624, 384, 1063, 644, 442, 272, 813, 493, 338, 208, 2061, 1249, 858, 528, 1600, 970, 666, 410, 1159, 702, 482, 297, 919, 557, 382, 235, 2232, 1352, 929, 572, 1708, 1035, 711, 438, 1224, 742, 509, 314, 969, 587, 403, 248, 2409, 1460, 1003, 618, 1872, 1134, 779, 480, 1358, 823, 565, 348, 1056, 640, 439, 270, 2620, 1588, 1091, 672, 2059, 1248, 857, 528, 1468, 890, 611, 376, 1108, 672, 461, 284, 2812, 1704, 1171, 721, 2188, 1326, 911, 561, 1588, 963, 661, 407, 1228, 744, 511, 315, 3057, 1853, 1273, 784, 2395, 1451, 997, 614, 1718, 1041, 715, 440, 1286, 779, 535, 330, 3283, 1990, 1367, 842, 2544, 1542, 1059, 652, 1804, 1094, 751, 462, 1425, 864, 593, 365, 3517, 2132, 1465, 902, 2701, 1637, 1125, 692, 1933, 1172, 805, 496, 1501, 910, 625, 385, 3669, 2223, 1528, 940, 2857, 1732, 1190, 732, 2085, 1263, 868, 534, 1581, 958, 658, 405, 3909, 2369, 1628, 1002, 3035, 1839, 1264, 778, 2181, 1322, 908, 559, 1677, 1016, 698, 430, 4158, 2520, 1732, 1066, 3289, 1994, 1370, 843, 2358, 1429, 982, 604, 1782, 1080, 742, 457, 4417, 2677, 1840, 1132, 3486, 2113, 1452, 894, 2473, 1499, 1030, 634, 1897, 1150, 790, 486, 4686, 2840, 1952, 1201, 3693, 2238, 1538, 947, 2670, 1618, 1112, 684, 2022, 1226, 842, 518, 4965, 3009, 2068, 1273, 3909, 2369, 1628, 1002, 2805, 1700, 1168, 719, 2157, 1307, 898, 553, 5253, 3183, 2188, 1347, 4134, 2506, 1722, 1060, 2949, 1787, 1228, 756, 2301, 1394, 958, 590, 5529, 3351, 2303, 1417, 4343, 2632, 1809, 1113, 3081, 1867, 1283, 790, 2361, 1431, 983, 605, 5836, 3537, 2431, 1496, 4588, 2780, 1911, 1176, 3244, 1966, 1351, 832, 2524, 1530, 1051, 647, 6153, 3729, 2563, 1577, 4775, 2894, 1989, 1224, 3417, 2071, 1423, 876, 2625, 1591, 1093, 673, 6479, 3927, 2699, 1661, 5039, 3054, 2099, 1292, 3599, 2181, 1499, 923, 2735, 1658, 1139, 701, 6743, 4087, 2809, 1729, 5313, 3220, 2213, 1362, 3791, 2298, 1579, 972, 2927, 1774, 1219, 750, 7089, 4296, 2953, 1817, 5596, 3391, 2331, 1435, 3993, 2420, 1663, 1024, 3057, 1852, 1273, 784 }; - private static readonly int[] _capacityECCBaseValues = { 19, 7, 1, 19, 0, 0, 16, 10, 1, 16, 0, 0, 13, 13, 1, 13, 0, 0, 9, 17, 1, 9, 0, 0, 34, 10, 1, 34, 0, 0, 28, 16, 1, 28, 0, 0, 22, 22, 1, 22, 0, 0, 16, 28, 1, 16, 0, 0, 55, 15, 1, 55, 0, 0, 44, 26, 1, 44, 0, 0, 34, 18, 2, 17, 0, 0, 26, 22, 2, 13, 0, 0, 80, 20, 1, 80, 0, 0, 64, 18, 2, 32, 0, 0, 48, 26, 2, 24, 0, 0, 36, 16, 4, 9, 0, 0, 108, 26, 1, 108, 0, 0, 86, 24, 2, 43, 0, 0, 62, 18, 2, 15, 2, 16, 46, 22, 2, 11, 2, 12, 136, 18, 2, 68, 0, 0, 108, 16, 4, 27, 0, 0, 76, 24, 4, 19, 0, 0, 60, 28, 4, 15, 0, 0, 156, 20, 2, 78, 0, 0, 124, 18, 4, 31, 0, 0, 88, 18, 2, 14, 4, 15, 66, 26, 4, 13, 1, 14, 194, 24, 2, 97, 0, 0, 154, 22, 2, 38, 2, 39, 110, 22, 4, 18, 2, 19, 86, 26, 4, 14, 2, 15, 232, 30, 2, 116, 0, 0, 182, 22, 3, 36, 2, 37, 132, 20, 4, 16, 4, 17, 100, 24, 4, 12, 4, 13, 274, 18, 2, 68, 2, 69, 216, 26, 4, 43, 1, 44, 154, 24, 6, 19, 2, 20, 122, 28, 6, 15, 2, 16, 324, 20, 4, 81, 0, 0, 254, 30, 1, 50, 4, 51, 180, 28, 4, 22, 4, 23, 140, 24, 3, 12, 8, 13, 370, 24, 2, 92, 2, 93, 290, 22, 6, 36, 2, 37, 206, 26, 4, 20, 6, 21, 158, 28, 7, 14, 4, 15, 428, 26, 4, 107, 0, 0, 334, 22, 8, 37, 1, 38, 244, 24, 8, 20, 4, 21, 180, 22, 12, 11, 4, 12, 461, 30, 3, 115, 1, 116, 365, 24, 4, 40, 5, 41, 261, 20, 11, 16, 5, 17, 197, 24, 11, 12, 5, 13, 523, 22, 5, 87, 1, 88, 415, 24, 5, 41, 5, 42, 295, 30, 5, 24, 7, 25, 223, 24, 11, 12, 7, 13, 589, 24, 5, 98, 1, 99, 453, 28, 7, 45, 3, 46, 325, 24, 15, 19, 2, 20, 253, 30, 3, 15, 13, 16, 647, 28, 1, 107, 5, 108, 507, 28, 10, 46, 1, 47, 367, 28, 1, 22, 15, 23, 283, 28, 2, 14, 17, 15, 721, 30, 5, 120, 1, 121, 563, 26, 9, 43, 4, 44, 397, 28, 17, 22, 1, 23, 313, 28, 2, 14, 19, 15, 795, 28, 3, 113, 4, 114, 627, 26, 3, 44, 11, 45, 445, 26, 17, 21, 4, 22, 341, 26, 9, 13, 16, 14, 861, 28, 3, 107, 5, 108, 669, 26, 3, 41, 13, 42, 485, 30, 15, 24, 5, 25, 385, 28, 15, 15, 10, 16, 932, 28, 4, 116, 4, 117, 714, 26, 17, 42, 0, 0, 512, 28, 17, 22, 6, 23, 406, 30, 19, 16, 6, 17, 1006, 28, 2, 111, 7, 112, 782, 28, 17, 46, 0, 0, 568, 30, 7, 24, 16, 25, 442, 24, 34, 13, 0, 0, 1094, 30, 4, 121, 5, 122, 860, 28, 4, 47, 14, 48, 614, 30, 11, 24, 14, 25, 464, 30, 16, 15, 14, 16, 1174, 30, 6, 117, 4, 118, 914, 28, 6, 45, 14, 46, 664, 30, 11, 24, 16, 25, 514, 30, 30, 16, 2, 17, 1276, 26, 8, 106, 4, 107, 1000, 28, 8, 47, 13, 48, 718, 30, 7, 24, 22, 25, 538, 30, 22, 15, 13, 16, 1370, 28, 10, 114, 2, 115, 1062, 28, 19, 46, 4, 47, 754, 28, 28, 22, 6, 23, 596, 30, 33, 16, 4, 17, 1468, 30, 8, 122, 4, 123, 1128, 28, 22, 45, 3, 46, 808, 30, 8, 23, 26, 24, 628, 30, 12, 15, 28, 16, 1531, 30, 3, 117, 10, 118, 1193, 28, 3, 45, 23, 46, 871, 30, 4, 24, 31, 25, 661, 30, 11, 15, 31, 16, 1631, 30, 7, 116, 7, 117, 1267, 28, 21, 45, 7, 46, 911, 30, 1, 23, 37, 24, 701, 30, 19, 15, 26, 16, 1735, 30, 5, 115, 10, 116, 1373, 28, 19, 47, 10, 48, 985, 30, 15, 24, 25, 25, 745, 30, 23, 15, 25, 16, 1843, 30, 13, 115, 3, 116, 1455, 28, 2, 46, 29, 47, 1033, 30, 42, 24, 1, 25, 793, 30, 23, 15, 28, 16, 1955, 30, 17, 115, 0, 0, 1541, 28, 10, 46, 23, 47, 1115, 30, 10, 24, 35, 25, 845, 30, 19, 15, 35, 16, 2071, 30, 17, 115, 1, 116, 1631, 28, 14, 46, 21, 47, 1171, 30, 29, 24, 19, 25, 901, 30, 11, 15, 46, 16, 2191, 30, 13, 115, 6, 116, 1725, 28, 14, 46, 23, 47, 1231, 30, 44, 24, 7, 25, 961, 30, 59, 16, 1, 17, 2306, 30, 12, 121, 7, 122, 1812, 28, 12, 47, 26, 48, 1286, 30, 39, 24, 14, 25, 986, 30, 22, 15, 41, 16, 2434, 30, 6, 121, 14, 122, 1914, 28, 6, 47, 34, 48, 1354, 30, 46, 24, 10, 25, 1054, 30, 2, 15, 64, 16, 2566, 30, 17, 122, 4, 123, 1992, 28, 29, 46, 14, 47, 1426, 30, 49, 24, 10, 25, 1096, 30, 24, 15, 46, 16, 2702, 30, 4, 122, 18, 123, 2102, 28, 13, 46, 32, 47, 1502, 30, 48, 24, 14, 25, 1142, 30, 42, 15, 32, 16, 2812, 30, 20, 117, 4, 118, 2216, 28, 40, 47, 7, 48, 1582, 30, 43, 24, 22, 25, 1222, 30, 10, 15, 67, 16, 2956, 30, 19, 118, 6, 119, 2334, 28, 18, 47, 31, 48, 1666, 30, 34, 24, 34, 25, 1276, 30, 20, 15, 61, 16 }; - /// /// Creates a dictionary mapping alphanumeric characters to their respective positions used in QR code encoding. /// This includes digits 0-9, uppercase letters A-Z, and some special characters. diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index b15dcbd8..ffc50cff 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -17,6 +17,8 @@ namespace QRCoder; public partial class QRCodeGenerator : IDisposable { private static readonly char[] _alphanumEncTable = { ' ', '$', '%', '*', '+', '-', '.', '/', ':' }; + private static readonly int[] _capacityBaseValues = { 41, 25, 17, 10, 34, 20, 14, 8, 27, 16, 11, 7, 17, 10, 7, 4, 77, 47, 32, 20, 63, 38, 26, 16, 48, 29, 20, 12, 34, 20, 14, 8, 127, 77, 53, 32, 101, 61, 42, 26, 77, 47, 32, 20, 58, 35, 24, 15, 187, 114, 78, 48, 149, 90, 62, 38, 111, 67, 46, 28, 82, 50, 34, 21, 255, 154, 106, 65, 202, 122, 84, 52, 144, 87, 60, 37, 106, 64, 44, 27, 322, 195, 134, 82, 255, 154, 106, 65, 178, 108, 74, 45, 139, 84, 58, 36, 370, 224, 154, 95, 293, 178, 122, 75, 207, 125, 86, 53, 154, 93, 64, 39, 461, 279, 192, 118, 365, 221, 152, 93, 259, 157, 108, 66, 202, 122, 84, 52, 552, 335, 230, 141, 432, 262, 180, 111, 312, 189, 130, 80, 235, 143, 98, 60, 652, 395, 271, 167, 513, 311, 213, 131, 364, 221, 151, 93, 288, 174, 119, 74, 772, 468, 321, 198, 604, 366, 251, 155, 427, 259, 177, 109, 331, 200, 137, 85, 883, 535, 367, 226, 691, 419, 287, 177, 489, 296, 203, 125, 374, 227, 155, 96, 1022, 619, 425, 262, 796, 483, 331, 204, 580, 352, 241, 149, 427, 259, 177, 109, 1101, 667, 458, 282, 871, 528, 362, 223, 621, 376, 258, 159, 468, 283, 194, 120, 1250, 758, 520, 320, 991, 600, 412, 254, 703, 426, 292, 180, 530, 321, 220, 136, 1408, 854, 586, 361, 1082, 656, 450, 277, 775, 470, 322, 198, 602, 365, 250, 154, 1548, 938, 644, 397, 1212, 734, 504, 310, 876, 531, 364, 224, 674, 408, 280, 173, 1725, 1046, 718, 442, 1346, 816, 560, 345, 948, 574, 394, 243, 746, 452, 310, 191, 1903, 1153, 792, 488, 1500, 909, 624, 384, 1063, 644, 442, 272, 813, 493, 338, 208, 2061, 1249, 858, 528, 1600, 970, 666, 410, 1159, 702, 482, 297, 919, 557, 382, 235, 2232, 1352, 929, 572, 1708, 1035, 711, 438, 1224, 742, 509, 314, 969, 587, 403, 248, 2409, 1460, 1003, 618, 1872, 1134, 779, 480, 1358, 823, 565, 348, 1056, 640, 439, 270, 2620, 1588, 1091, 672, 2059, 1248, 857, 528, 1468, 890, 611, 376, 1108, 672, 461, 284, 2812, 1704, 1171, 721, 2188, 1326, 911, 561, 1588, 963, 661, 407, 1228, 744, 511, 315, 3057, 1853, 1273, 784, 2395, 1451, 997, 614, 1718, 1041, 715, 440, 1286, 779, 535, 330, 3283, 1990, 1367, 842, 2544, 1542, 1059, 652, 1804, 1094, 751, 462, 1425, 864, 593, 365, 3517, 2132, 1465, 902, 2701, 1637, 1125, 692, 1933, 1172, 805, 496, 1501, 910, 625, 385, 3669, 2223, 1528, 940, 2857, 1732, 1190, 732, 2085, 1263, 868, 534, 1581, 958, 658, 405, 3909, 2369, 1628, 1002, 3035, 1839, 1264, 778, 2181, 1322, 908, 559, 1677, 1016, 698, 430, 4158, 2520, 1732, 1066, 3289, 1994, 1370, 843, 2358, 1429, 982, 604, 1782, 1080, 742, 457, 4417, 2677, 1840, 1132, 3486, 2113, 1452, 894, 2473, 1499, 1030, 634, 1897, 1150, 790, 486, 4686, 2840, 1952, 1201, 3693, 2238, 1538, 947, 2670, 1618, 1112, 684, 2022, 1226, 842, 518, 4965, 3009, 2068, 1273, 3909, 2369, 1628, 1002, 2805, 1700, 1168, 719, 2157, 1307, 898, 553, 5253, 3183, 2188, 1347, 4134, 2506, 1722, 1060, 2949, 1787, 1228, 756, 2301, 1394, 958, 590, 5529, 3351, 2303, 1417, 4343, 2632, 1809, 1113, 3081, 1867, 1283, 790, 2361, 1431, 983, 605, 5836, 3537, 2431, 1496, 4588, 2780, 1911, 1176, 3244, 1966, 1351, 832, 2524, 1530, 1051, 647, 6153, 3729, 2563, 1577, 4775, 2894, 1989, 1224, 3417, 2071, 1423, 876, 2625, 1591, 1093, 673, 6479, 3927, 2699, 1661, 5039, 3054, 2099, 1292, 3599, 2181, 1499, 923, 2735, 1658, 1139, 701, 6743, 4087, 2809, 1729, 5313, 3220, 2213, 1362, 3791, 2298, 1579, 972, 2927, 1774, 1219, 750, 7089, 4296, 2953, 1817, 5596, 3391, 2331, 1435, 3993, 2420, 1663, 1024, 3057, 1852, 1273, 784 }; + private static readonly int[] _capacityECCBaseValues = { 19, 7, 1, 19, 0, 0, 16, 10, 1, 16, 0, 0, 13, 13, 1, 13, 0, 0, 9, 17, 1, 9, 0, 0, 34, 10, 1, 34, 0, 0, 28, 16, 1, 28, 0, 0, 22, 22, 1, 22, 0, 0, 16, 28, 1, 16, 0, 0, 55, 15, 1, 55, 0, 0, 44, 26, 1, 44, 0, 0, 34, 18, 2, 17, 0, 0, 26, 22, 2, 13, 0, 0, 80, 20, 1, 80, 0, 0, 64, 18, 2, 32, 0, 0, 48, 26, 2, 24, 0, 0, 36, 16, 4, 9, 0, 0, 108, 26, 1, 108, 0, 0, 86, 24, 2, 43, 0, 0, 62, 18, 2, 15, 2, 16, 46, 22, 2, 11, 2, 12, 136, 18, 2, 68, 0, 0, 108, 16, 4, 27, 0, 0, 76, 24, 4, 19, 0, 0, 60, 28, 4, 15, 0, 0, 156, 20, 2, 78, 0, 0, 124, 18, 4, 31, 0, 0, 88, 18, 2, 14, 4, 15, 66, 26, 4, 13, 1, 14, 194, 24, 2, 97, 0, 0, 154, 22, 2, 38, 2, 39, 110, 22, 4, 18, 2, 19, 86, 26, 4, 14, 2, 15, 232, 30, 2, 116, 0, 0, 182, 22, 3, 36, 2, 37, 132, 20, 4, 16, 4, 17, 100, 24, 4, 12, 4, 13, 274, 18, 2, 68, 2, 69, 216, 26, 4, 43, 1, 44, 154, 24, 6, 19, 2, 20, 122, 28, 6, 15, 2, 16, 324, 20, 4, 81, 0, 0, 254, 30, 1, 50, 4, 51, 180, 28, 4, 22, 4, 23, 140, 24, 3, 12, 8, 13, 370, 24, 2, 92, 2, 93, 290, 22, 6, 36, 2, 37, 206, 26, 4, 20, 6, 21, 158, 28, 7, 14, 4, 15, 428, 26, 4, 107, 0, 0, 334, 22, 8, 37, 1, 38, 244, 24, 8, 20, 4, 21, 180, 22, 12, 11, 4, 12, 461, 30, 3, 115, 1, 116, 365, 24, 4, 40, 5, 41, 261, 20, 11, 16, 5, 17, 197, 24, 11, 12, 5, 13, 523, 22, 5, 87, 1, 88, 415, 24, 5, 41, 5, 42, 295, 30, 5, 24, 7, 25, 223, 24, 11, 12, 7, 13, 589, 24, 5, 98, 1, 99, 453, 28, 7, 45, 3, 46, 325, 24, 15, 19, 2, 20, 253, 30, 3, 15, 13, 16, 647, 28, 1, 107, 5, 108, 507, 28, 10, 46, 1, 47, 367, 28, 1, 22, 15, 23, 283, 28, 2, 14, 17, 15, 721, 30, 5, 120, 1, 121, 563, 26, 9, 43, 4, 44, 397, 28, 17, 22, 1, 23, 313, 28, 2, 14, 19, 15, 795, 28, 3, 113, 4, 114, 627, 26, 3, 44, 11, 45, 445, 26, 17, 21, 4, 22, 341, 26, 9, 13, 16, 14, 861, 28, 3, 107, 5, 108, 669, 26, 3, 41, 13, 42, 485, 30, 15, 24, 5, 25, 385, 28, 15, 15, 10, 16, 932, 28, 4, 116, 4, 117, 714, 26, 17, 42, 0, 0, 512, 28, 17, 22, 6, 23, 406, 30, 19, 16, 6, 17, 1006, 28, 2, 111, 7, 112, 782, 28, 17, 46, 0, 0, 568, 30, 7, 24, 16, 25, 442, 24, 34, 13, 0, 0, 1094, 30, 4, 121, 5, 122, 860, 28, 4, 47, 14, 48, 614, 30, 11, 24, 14, 25, 464, 30, 16, 15, 14, 16, 1174, 30, 6, 117, 4, 118, 914, 28, 6, 45, 14, 46, 664, 30, 11, 24, 16, 25, 514, 30, 30, 16, 2, 17, 1276, 26, 8, 106, 4, 107, 1000, 28, 8, 47, 13, 48, 718, 30, 7, 24, 22, 25, 538, 30, 22, 15, 13, 16, 1370, 28, 10, 114, 2, 115, 1062, 28, 19, 46, 4, 47, 754, 28, 28, 22, 6, 23, 596, 30, 33, 16, 4, 17, 1468, 30, 8, 122, 4, 123, 1128, 28, 22, 45, 3, 46, 808, 30, 8, 23, 26, 24, 628, 30, 12, 15, 28, 16, 1531, 30, 3, 117, 10, 118, 1193, 28, 3, 45, 23, 46, 871, 30, 4, 24, 31, 25, 661, 30, 11, 15, 31, 16, 1631, 30, 7, 116, 7, 117, 1267, 28, 21, 45, 7, 46, 911, 30, 1, 23, 37, 24, 701, 30, 19, 15, 26, 16, 1735, 30, 5, 115, 10, 116, 1373, 28, 19, 47, 10, 48, 985, 30, 15, 24, 25, 25, 745, 30, 23, 15, 25, 16, 1843, 30, 13, 115, 3, 116, 1455, 28, 2, 46, 29, 47, 1033, 30, 42, 24, 1, 25, 793, 30, 23, 15, 28, 16, 1955, 30, 17, 115, 0, 0, 1541, 28, 10, 46, 23, 47, 1115, 30, 10, 24, 35, 25, 845, 30, 19, 15, 35, 16, 2071, 30, 17, 115, 1, 116, 1631, 28, 14, 46, 21, 47, 1171, 30, 29, 24, 19, 25, 901, 30, 11, 15, 46, 16, 2191, 30, 13, 115, 6, 116, 1725, 28, 14, 46, 23, 47, 1231, 30, 44, 24, 7, 25, 961, 30, 59, 16, 1, 17, 2306, 30, 12, 121, 7, 122, 1812, 28, 12, 47, 26, 48, 1286, 30, 39, 24, 14, 25, 986, 30, 22, 15, 41, 16, 2434, 30, 6, 121, 14, 122, 1914, 28, 6, 47, 34, 48, 1354, 30, 46, 24, 10, 25, 1054, 30, 2, 15, 64, 16, 2566, 30, 17, 122, 4, 123, 1992, 28, 29, 46, 14, 47, 1426, 30, 49, 24, 10, 25, 1096, 30, 24, 15, 46, 16, 2702, 30, 4, 122, 18, 123, 2102, 28, 13, 46, 32, 47, 1502, 30, 48, 24, 14, 25, 1142, 30, 42, 15, 32, 16, 2812, 30, 20, 117, 4, 118, 2216, 28, 40, 47, 7, 48, 1582, 30, 43, 24, 22, 25, 1222, 30, 10, 15, 67, 16, 2956, 30, 19, 118, 6, 119, 2334, 28, 18, 47, 31, 48, 1666, 30, 34, 24, 34, 25, 1276, 30, 20, 15, 61, 16 }; private static readonly int[] _alignmentPatternBaseValues = { 0, 0, 0, 0, 0, 0, 0, 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 }; private static readonly int[] _remainderBits = { 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 }; From 5d16dca689e84be322a66f135de21756cb380f80 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 6 Apr 2025 11:09:39 -0400 Subject: [PATCH 03/22] update --- QRCoderDemoUWP/MainPage.xaml.cs | 2 +- QRCoderDemoUWP/Properties/AssemblyInfo.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/QRCoderDemoUWP/MainPage.xaml.cs b/QRCoderDemoUWP/MainPage.xaml.cs index 7b2fca02..6c58f3eb 100644 --- a/QRCoderDemoUWP/MainPage.xaml.cs +++ b/QRCoderDemoUWP/MainPage.xaml.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using QRCoder; using Windows.Storage.Streams; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media.Imaging; -using QRCoder; // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 diff --git a/QRCoderDemoUWP/Properties/AssemblyInfo.cs b/QRCoderDemoUWP/Properties/AssemblyInfo.cs index dc05967c..3a4387d4 100644 --- a/QRCoderDemoUWP/Properties/AssemblyInfo.cs +++ b/QRCoderDemoUWP/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -26,4 +26,4 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: ComVisible(false)] \ No newline at end of file +[assembly: ComVisible(false)] From f3d17c756542b0bab54272cb11624c6dc731c670 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 6 Apr 2025 11:26:15 -0400 Subject: [PATCH 04/22] update --- QRCoder.sln | 1 + global.json | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 global.json diff --git a/QRCoder.sln b/QRCoder.sln index 49102939..3afc8b4f 100644 --- a/QRCoder.sln +++ b/QRCoder.sln @@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig Directory.Build.props = Directory.Build.props + global.json = global.json EndProjectSection EndProject Global diff --git a/global.json b/global.json new file mode 100644 index 00000000..b7e33571 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.100", + "allowPrerelease": false, + "rollForward": "latestFeature" + } +} From 5941456685f69f4ae1f769a38be40e1953ec3334 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 6 Apr 2025 12:07:33 -0400 Subject: [PATCH 05/22] update --- QRCoder/QRCodeGenerator.Tables.cs | 186 --------------------------- QRCoder/QRCodeGenerator.cs | 41 +++--- QRCoder/QRCodeGenerator/Tables.cs | 202 ++++++++++++++++++++++++++++++ 3 files changed, 227 insertions(+), 202 deletions(-) delete mode 100644 QRCoder/QRCodeGenerator.Tables.cs create mode 100644 QRCoder/QRCodeGenerator/Tables.cs diff --git a/QRCoder/QRCodeGenerator.Tables.cs b/QRCoder/QRCodeGenerator.Tables.cs deleted file mode 100644 index 29ecc02c..00000000 --- a/QRCoder/QRCodeGenerator.Tables.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System.Collections.Generic; - -namespace QRCoder; - -/// -/// Partial class containing table generation methods for QR code generation. -/// -public partial class QRCodeGenerator -{ - /// - /// Creates a dictionary mapping alphanumeric characters to their respective positions used in QR code encoding. - /// This includes digits 0-9, uppercase letters A-Z, and some special characters. - /// - /// A dictionary mapping each supported alphanumeric character to its corresponding value. - private static Dictionary CreateAlphanumEncDict() - { - var localAlphanumEncDict = new Dictionary(45); - for (int i = 0; i < 10; i++) - localAlphanumEncDict.Add($"{i}"[0], i); - // Add uppercase alphabetic characters. - for (char c = 'A'; c <= 'Z'; c++) - localAlphanumEncDict.Add(c, localAlphanumEncDict.Count); - // Add special characters from a predefined table. - for (int i = 0; i < _alphanumEncTable.Length; i++) - localAlphanumEncDict.Add(_alphanumEncTable[i], localAlphanumEncDict.Count); - return localAlphanumEncDict; - } - - /// - /// Creates a lookup table mapping QR code versions to their corresponding alignment patterns. - /// Alignment patterns are used in QR codes to help scanners accurately read the code at high speeds and when partially obscured. - /// This table provides the necessary patterns based on the QR code version which dictates the size and complexity of the QR code. - /// - /// A dictionary where keys are QR code version numbers and values are AlignmentPattern structures detailing the positions of alignment patterns for each version. - private static Dictionary CreateAlignmentPatternTable() - { - var localAlignmentPatternTable = new Dictionary(40); - - for (var i = 0; i < (7 * 40); i += 7) - { - var points = new List(50); - for (var x = 0; x < 7; x++) - { - if (_alignmentPatternBaseValues[i + x] != 0) - { - for (var y = 0; y < 7; y++) - { - if (_alignmentPatternBaseValues[i + y] != 0) - { - var p = new Point(_alignmentPatternBaseValues[i + x] - 2, _alignmentPatternBaseValues[i + y] - 2); - if (!points.Contains(p)) - points.Add(p); - } - } - } - } - - var version = (i + 7) / 7; - localAlignmentPatternTable.Add(version, new AlignmentPattern() - { - Version = version, - PatternPositions = points - } - ); - } - return localAlignmentPatternTable; - } - - /// - /// Generates a table containing the error correction capacities and data codeword information for different QR code versions and error correction levels. - /// This table is essential for determining how much data can be encoded in a QR code of a specific version and ECC level, - /// as well as how robust the QR code will be against distortions or obstructions. - /// - /// A list of ECCInfo structures, each representing the ECC data and capacities for different combinations of QR code versions and ECC levels. - private static List CreateCapacityECCTable() - { - var localCapacityECCTable = new List(160); - for (var i = 0; i < (4 * 6 * 40); i += (4 * 6)) - { - localCapacityECCTable.AddRange( - new[] - { - new ECCInfo( - (i+24) / 24, - ECCLevel.L, - _capacityECCBaseValues[i], - _capacityECCBaseValues[i+1], - _capacityECCBaseValues[i+2], - _capacityECCBaseValues[i+3], - _capacityECCBaseValues[i+4], - _capacityECCBaseValues[i+5]), - new ECCInfo - ( - version: (i + 24) / 24, - errorCorrectionLevel: ECCLevel.M, - totalDataCodewords: _capacityECCBaseValues[i+6], - eccPerBlock: _capacityECCBaseValues[i+7], - blocksInGroup1: _capacityECCBaseValues[i+8], - codewordsInGroup1: _capacityECCBaseValues[i+9], - blocksInGroup2: _capacityECCBaseValues[i+10], - codewordsInGroup2: _capacityECCBaseValues[i+11] - ), - new ECCInfo - ( - version: (i + 24) / 24, - errorCorrectionLevel: ECCLevel.Q, - totalDataCodewords: _capacityECCBaseValues[i+12], - eccPerBlock: _capacityECCBaseValues[i+13], - blocksInGroup1: _capacityECCBaseValues[i+14], - codewordsInGroup1: _capacityECCBaseValues[i+15], - blocksInGroup2: _capacityECCBaseValues[i+16], - codewordsInGroup2: _capacityECCBaseValues[i+17] - ), - new ECCInfo - ( - version: (i + 24) / 24, - errorCorrectionLevel: ECCLevel.H, - totalDataCodewords: _capacityECCBaseValues[i+18], - eccPerBlock: _capacityECCBaseValues[i+19], - blocksInGroup1: _capacityECCBaseValues[i+20], - codewordsInGroup1: _capacityECCBaseValues[i+21], - blocksInGroup2: _capacityECCBaseValues[i+22], - codewordsInGroup2: _capacityECCBaseValues[i+23] - ) - }); - } - return localCapacityECCTable; - } - - /// - /// Generates a list containing detailed capacity information for various versions of QR codes. - /// This table includes capacities for different encoding modes (numeric, alphanumeric, byte, etc.) under each error correction level. - /// The capacity table is crucial for QR code generation, as it determines how much data each QR code version can store depending on the encoding mode and error correction level used. - /// - private static List CreateCapacityTable() - { - var localCapacityTable = new List(40); - for (var i = 0; i < (16 * 40); i += 16) - { - localCapacityTable.Add(new VersionInfo( - - (i + 16) / 16, - new List(4) - { - new VersionInfoDetails( - ECCLevel.L, - new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i+1] }, - { EncodingMode.Byte, _capacityBaseValues[i+2] }, - { EncodingMode.Kanji, _capacityBaseValues[i+3] }, - } - ), - new VersionInfoDetails( - ECCLevel.M, - new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i+4] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i+5] }, - { EncodingMode.Byte, _capacityBaseValues[i+6] }, - { EncodingMode.Kanji, _capacityBaseValues[i+7] }, - } - ), - new VersionInfoDetails( - ECCLevel.Q, - new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i+8] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i+9] }, - { EncodingMode.Byte, _capacityBaseValues[i+10] }, - { EncodingMode.Kanji, _capacityBaseValues[i+11] }, - } - ), - new VersionInfoDetails( - ECCLevel.H, - new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i+12] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i+13] }, - { EncodingMode.Byte, _capacityBaseValues[i+14] }, - { EncodingMode.Kanji, _capacityBaseValues[i+15] }, - } - ) - } - )); - } - return localCapacityTable; - } -} diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index ffc50cff..ec3db1b7 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -16,18 +16,27 @@ namespace QRCoder; /// public partial class QRCodeGenerator : IDisposable { - private static readonly char[] _alphanumEncTable = { ' ', '$', '%', '*', '+', '-', '.', '/', ':' }; - private static readonly int[] _capacityBaseValues = { 41, 25, 17, 10, 34, 20, 14, 8, 27, 16, 11, 7, 17, 10, 7, 4, 77, 47, 32, 20, 63, 38, 26, 16, 48, 29, 20, 12, 34, 20, 14, 8, 127, 77, 53, 32, 101, 61, 42, 26, 77, 47, 32, 20, 58, 35, 24, 15, 187, 114, 78, 48, 149, 90, 62, 38, 111, 67, 46, 28, 82, 50, 34, 21, 255, 154, 106, 65, 202, 122, 84, 52, 144, 87, 60, 37, 106, 64, 44, 27, 322, 195, 134, 82, 255, 154, 106, 65, 178, 108, 74, 45, 139, 84, 58, 36, 370, 224, 154, 95, 293, 178, 122, 75, 207, 125, 86, 53, 154, 93, 64, 39, 461, 279, 192, 118, 365, 221, 152, 93, 259, 157, 108, 66, 202, 122, 84, 52, 552, 335, 230, 141, 432, 262, 180, 111, 312, 189, 130, 80, 235, 143, 98, 60, 652, 395, 271, 167, 513, 311, 213, 131, 364, 221, 151, 93, 288, 174, 119, 74, 772, 468, 321, 198, 604, 366, 251, 155, 427, 259, 177, 109, 331, 200, 137, 85, 883, 535, 367, 226, 691, 419, 287, 177, 489, 296, 203, 125, 374, 227, 155, 96, 1022, 619, 425, 262, 796, 483, 331, 204, 580, 352, 241, 149, 427, 259, 177, 109, 1101, 667, 458, 282, 871, 528, 362, 223, 621, 376, 258, 159, 468, 283, 194, 120, 1250, 758, 520, 320, 991, 600, 412, 254, 703, 426, 292, 180, 530, 321, 220, 136, 1408, 854, 586, 361, 1082, 656, 450, 277, 775, 470, 322, 198, 602, 365, 250, 154, 1548, 938, 644, 397, 1212, 734, 504, 310, 876, 531, 364, 224, 674, 408, 280, 173, 1725, 1046, 718, 442, 1346, 816, 560, 345, 948, 574, 394, 243, 746, 452, 310, 191, 1903, 1153, 792, 488, 1500, 909, 624, 384, 1063, 644, 442, 272, 813, 493, 338, 208, 2061, 1249, 858, 528, 1600, 970, 666, 410, 1159, 702, 482, 297, 919, 557, 382, 235, 2232, 1352, 929, 572, 1708, 1035, 711, 438, 1224, 742, 509, 314, 969, 587, 403, 248, 2409, 1460, 1003, 618, 1872, 1134, 779, 480, 1358, 823, 565, 348, 1056, 640, 439, 270, 2620, 1588, 1091, 672, 2059, 1248, 857, 528, 1468, 890, 611, 376, 1108, 672, 461, 284, 2812, 1704, 1171, 721, 2188, 1326, 911, 561, 1588, 963, 661, 407, 1228, 744, 511, 315, 3057, 1853, 1273, 784, 2395, 1451, 997, 614, 1718, 1041, 715, 440, 1286, 779, 535, 330, 3283, 1990, 1367, 842, 2544, 1542, 1059, 652, 1804, 1094, 751, 462, 1425, 864, 593, 365, 3517, 2132, 1465, 902, 2701, 1637, 1125, 692, 1933, 1172, 805, 496, 1501, 910, 625, 385, 3669, 2223, 1528, 940, 2857, 1732, 1190, 732, 2085, 1263, 868, 534, 1581, 958, 658, 405, 3909, 2369, 1628, 1002, 3035, 1839, 1264, 778, 2181, 1322, 908, 559, 1677, 1016, 698, 430, 4158, 2520, 1732, 1066, 3289, 1994, 1370, 843, 2358, 1429, 982, 604, 1782, 1080, 742, 457, 4417, 2677, 1840, 1132, 3486, 2113, 1452, 894, 2473, 1499, 1030, 634, 1897, 1150, 790, 486, 4686, 2840, 1952, 1201, 3693, 2238, 1538, 947, 2670, 1618, 1112, 684, 2022, 1226, 842, 518, 4965, 3009, 2068, 1273, 3909, 2369, 1628, 1002, 2805, 1700, 1168, 719, 2157, 1307, 898, 553, 5253, 3183, 2188, 1347, 4134, 2506, 1722, 1060, 2949, 1787, 1228, 756, 2301, 1394, 958, 590, 5529, 3351, 2303, 1417, 4343, 2632, 1809, 1113, 3081, 1867, 1283, 790, 2361, 1431, 983, 605, 5836, 3537, 2431, 1496, 4588, 2780, 1911, 1176, 3244, 1966, 1351, 832, 2524, 1530, 1051, 647, 6153, 3729, 2563, 1577, 4775, 2894, 1989, 1224, 3417, 2071, 1423, 876, 2625, 1591, 1093, 673, 6479, 3927, 2699, 1661, 5039, 3054, 2099, 1292, 3599, 2181, 1499, 923, 2735, 1658, 1139, 701, 6743, 4087, 2809, 1729, 5313, 3220, 2213, 1362, 3791, 2298, 1579, 972, 2927, 1774, 1219, 750, 7089, 4296, 2953, 1817, 5596, 3391, 2331, 1435, 3993, 2420, 1663, 1024, 3057, 1852, 1273, 784 }; - private static readonly int[] _capacityECCBaseValues = { 19, 7, 1, 19, 0, 0, 16, 10, 1, 16, 0, 0, 13, 13, 1, 13, 0, 0, 9, 17, 1, 9, 0, 0, 34, 10, 1, 34, 0, 0, 28, 16, 1, 28, 0, 0, 22, 22, 1, 22, 0, 0, 16, 28, 1, 16, 0, 0, 55, 15, 1, 55, 0, 0, 44, 26, 1, 44, 0, 0, 34, 18, 2, 17, 0, 0, 26, 22, 2, 13, 0, 0, 80, 20, 1, 80, 0, 0, 64, 18, 2, 32, 0, 0, 48, 26, 2, 24, 0, 0, 36, 16, 4, 9, 0, 0, 108, 26, 1, 108, 0, 0, 86, 24, 2, 43, 0, 0, 62, 18, 2, 15, 2, 16, 46, 22, 2, 11, 2, 12, 136, 18, 2, 68, 0, 0, 108, 16, 4, 27, 0, 0, 76, 24, 4, 19, 0, 0, 60, 28, 4, 15, 0, 0, 156, 20, 2, 78, 0, 0, 124, 18, 4, 31, 0, 0, 88, 18, 2, 14, 4, 15, 66, 26, 4, 13, 1, 14, 194, 24, 2, 97, 0, 0, 154, 22, 2, 38, 2, 39, 110, 22, 4, 18, 2, 19, 86, 26, 4, 14, 2, 15, 232, 30, 2, 116, 0, 0, 182, 22, 3, 36, 2, 37, 132, 20, 4, 16, 4, 17, 100, 24, 4, 12, 4, 13, 274, 18, 2, 68, 2, 69, 216, 26, 4, 43, 1, 44, 154, 24, 6, 19, 2, 20, 122, 28, 6, 15, 2, 16, 324, 20, 4, 81, 0, 0, 254, 30, 1, 50, 4, 51, 180, 28, 4, 22, 4, 23, 140, 24, 3, 12, 8, 13, 370, 24, 2, 92, 2, 93, 290, 22, 6, 36, 2, 37, 206, 26, 4, 20, 6, 21, 158, 28, 7, 14, 4, 15, 428, 26, 4, 107, 0, 0, 334, 22, 8, 37, 1, 38, 244, 24, 8, 20, 4, 21, 180, 22, 12, 11, 4, 12, 461, 30, 3, 115, 1, 116, 365, 24, 4, 40, 5, 41, 261, 20, 11, 16, 5, 17, 197, 24, 11, 12, 5, 13, 523, 22, 5, 87, 1, 88, 415, 24, 5, 41, 5, 42, 295, 30, 5, 24, 7, 25, 223, 24, 11, 12, 7, 13, 589, 24, 5, 98, 1, 99, 453, 28, 7, 45, 3, 46, 325, 24, 15, 19, 2, 20, 253, 30, 3, 15, 13, 16, 647, 28, 1, 107, 5, 108, 507, 28, 10, 46, 1, 47, 367, 28, 1, 22, 15, 23, 283, 28, 2, 14, 17, 15, 721, 30, 5, 120, 1, 121, 563, 26, 9, 43, 4, 44, 397, 28, 17, 22, 1, 23, 313, 28, 2, 14, 19, 15, 795, 28, 3, 113, 4, 114, 627, 26, 3, 44, 11, 45, 445, 26, 17, 21, 4, 22, 341, 26, 9, 13, 16, 14, 861, 28, 3, 107, 5, 108, 669, 26, 3, 41, 13, 42, 485, 30, 15, 24, 5, 25, 385, 28, 15, 15, 10, 16, 932, 28, 4, 116, 4, 117, 714, 26, 17, 42, 0, 0, 512, 28, 17, 22, 6, 23, 406, 30, 19, 16, 6, 17, 1006, 28, 2, 111, 7, 112, 782, 28, 17, 46, 0, 0, 568, 30, 7, 24, 16, 25, 442, 24, 34, 13, 0, 0, 1094, 30, 4, 121, 5, 122, 860, 28, 4, 47, 14, 48, 614, 30, 11, 24, 14, 25, 464, 30, 16, 15, 14, 16, 1174, 30, 6, 117, 4, 118, 914, 28, 6, 45, 14, 46, 664, 30, 11, 24, 16, 25, 514, 30, 30, 16, 2, 17, 1276, 26, 8, 106, 4, 107, 1000, 28, 8, 47, 13, 48, 718, 30, 7, 24, 22, 25, 538, 30, 22, 15, 13, 16, 1370, 28, 10, 114, 2, 115, 1062, 28, 19, 46, 4, 47, 754, 28, 28, 22, 6, 23, 596, 30, 33, 16, 4, 17, 1468, 30, 8, 122, 4, 123, 1128, 28, 22, 45, 3, 46, 808, 30, 8, 23, 26, 24, 628, 30, 12, 15, 28, 16, 1531, 30, 3, 117, 10, 118, 1193, 28, 3, 45, 23, 46, 871, 30, 4, 24, 31, 25, 661, 30, 11, 15, 31, 16, 1631, 30, 7, 116, 7, 117, 1267, 28, 21, 45, 7, 46, 911, 30, 1, 23, 37, 24, 701, 30, 19, 15, 26, 16, 1735, 30, 5, 115, 10, 116, 1373, 28, 19, 47, 10, 48, 985, 30, 15, 24, 25, 25, 745, 30, 23, 15, 25, 16, 1843, 30, 13, 115, 3, 116, 1455, 28, 2, 46, 29, 47, 1033, 30, 42, 24, 1, 25, 793, 30, 23, 15, 28, 16, 1955, 30, 17, 115, 0, 0, 1541, 28, 10, 46, 23, 47, 1115, 30, 10, 24, 35, 25, 845, 30, 19, 15, 35, 16, 2071, 30, 17, 115, 1, 116, 1631, 28, 14, 46, 21, 47, 1171, 30, 29, 24, 19, 25, 901, 30, 11, 15, 46, 16, 2191, 30, 13, 115, 6, 116, 1725, 28, 14, 46, 23, 47, 1231, 30, 44, 24, 7, 25, 961, 30, 59, 16, 1, 17, 2306, 30, 12, 121, 7, 122, 1812, 28, 12, 47, 26, 48, 1286, 30, 39, 24, 14, 25, 986, 30, 22, 15, 41, 16, 2434, 30, 6, 121, 14, 122, 1914, 28, 6, 47, 34, 48, 1354, 30, 46, 24, 10, 25, 1054, 30, 2, 15, 64, 16, 2566, 30, 17, 122, 4, 123, 1992, 28, 29, 46, 14, 47, 1426, 30, 49, 24, 10, 25, 1096, 30, 24, 15, 46, 16, 2702, 30, 4, 122, 18, 123, 2102, 28, 13, 46, 32, 47, 1502, 30, 48, 24, 14, 25, 1142, 30, 42, 15, 32, 16, 2812, 30, 20, 117, 4, 118, 2216, 28, 40, 47, 7, 48, 1582, 30, 43, 24, 22, 25, 1222, 30, 10, 15, 67, 16, 2956, 30, 19, 118, 6, 119, 2334, 28, 18, 47, 31, 48, 1666, 30, 34, 24, 34, 25, 1276, 30, 20, 15, 61, 16 }; - private static readonly int[] _alignmentPatternBaseValues = { 0, 0, 0, 0, 0, 0, 0, 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 }; - private static readonly int[] _remainderBits = { 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 }; - - private static readonly Dictionary _alignmentPatternTable = CreateAlignmentPatternTable(); - private static readonly List _capacityECCTable = CreateCapacityECCTable(); - private static readonly List _capacityTable = CreateCapacityTable(); - private static readonly int[] _galoisFieldByExponentAlpha = { 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1 }; - private static readonly int[] _galoisFieldByIntegerValue = { 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 }; - private static readonly Dictionary _alphanumEncDict = CreateAlphanumEncDict(); + /// + /// A lookup table mapping QR code versions to their corresponding alignment patterns. + /// + private static readonly Dictionary _alignmentPatternTable = Tables.CreateAlignmentPatternTable(); + + /// + /// A table containing the error correction capacities and data codeword information for different combinations of QR code versions and error correction levels. + /// + private static readonly List _capacityECCTable = Tables.CreateCapacityECCTable(); + + /// + /// A list containing detailed capacity information for each version of QR codes. + /// The index in the capacity table corresponds to one less than the version number. + /// + private static readonly List _capacityTable = Tables.CreateCapacityTable(); + + /// + /// A dictionary mapping alphanumeric characters to their respective positions used in QR code encoding. + /// This includes digits 0-9, uppercase letters A-Z, and some special characters. + /// + private static readonly Dictionary _alphanumEncDict = Tables.CreateAlphanumEncDict(); /// /// Initializes the QR code generator @@ -302,7 +311,7 @@ int CalculateInterleavedLength() if (codeBlock.ECCWords.Length > i) length += 8; } - length += _remainderBits[version - 1]; + length += Tables.RemainderBits[version - 1]; return length; } @@ -614,7 +623,7 @@ private static EncodingMode GetEncodingFromPlaintext(string plainText, bool forc if (IsInRange(c, '0', '9')) continue; // numeric - char.IsDigit() for Latin1 result = EncodingMode.Alphanumeric; // not numeric, assume alphanumeric - if (IsInRange(c, 'A', 'Z') || _alphanumEncTable.Contains(c)) + if (IsInRange(c, 'A', 'Z') || Tables.AlphanumEncTableContains(c)) continue; // alphanumeric return EncodingMode.Byte; // not numeric or alphanumeric, assume byte } @@ -1166,7 +1175,7 @@ int[] GetNotUniqueExponents(Polynom list) /// This is used in Reed-Solomon and other error correction calculations involving Galois fields. /// private static int GetIntValFromAlphaExp(int exp) - => _galoisFieldByExponentAlpha[exp]; + => Tables.GaloisFieldByExponentAlpha[exp]; /// /// Retrieves the exponent from the Galois field that corresponds to a given integer value. @@ -1176,7 +1185,7 @@ private static int GetAlphaExpFromIntVal(int intVal) { if (intVal == 0) ThrowIntValOutOfRangeException(); // Zero is not valid as it does not have an exponent representation. - return _galoisFieldByIntegerValue[intVal]; + return Tables.GaloisFieldByIntegerValue[intVal]; void ThrowIntValOutOfRangeException() => throw new ArgumentOutOfRangeException(nameof(intVal), "The provided integer value is out of range, as zero is not representable."); } diff --git a/QRCoder/QRCodeGenerator/Tables.cs b/QRCoder/QRCodeGenerator/Tables.cs new file mode 100644 index 00000000..bcfdfe5b --- /dev/null +++ b/QRCoder/QRCodeGenerator/Tables.cs @@ -0,0 +1,202 @@ +using System.Collections.Generic; +using System.Linq; + +namespace QRCoder; + +/// +/// Partial class containing table generation methods for QR code generation. +/// +public partial class QRCodeGenerator +{ + /// + /// Contains static tables used for QR code generation, including Galois field values, alignment patterns, and capacity values. + /// + private static class Tables + { + private static readonly char[] _alphanumEncTable = { ' ', '$', '%', '*', '+', '-', '.', '/', ':' }; + private static readonly int[] _alignmentPatternBaseValues = { 0, 0, 0, 0, 0, 0, 0, 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 }; + private static readonly int[] _capacityBaseValues = { 41, 25, 17, 10, 34, 20, 14, 8, 27, 16, 11, 7, 17, 10, 7, 4, 77, 47, 32, 20, 63, 38, 26, 16, 48, 29, 20, 12, 34, 20, 14, 8, 127, 77, 53, 32, 101, 61, 42, 26, 77, 47, 32, 20, 58, 35, 24, 15, 187, 114, 78, 48, 149, 90, 62, 38, 111, 67, 46, 28, 82, 50, 34, 21, 255, 154, 106, 65, 202, 122, 84, 52, 144, 87, 60, 37, 106, 64, 44, 27, 322, 195, 134, 82, 255, 154, 106, 65, 178, 108, 74, 45, 139, 84, 58, 36, 370, 224, 154, 95, 293, 178, 122, 75, 207, 125, 86, 53, 154, 93, 64, 39, 461, 279, 192, 118, 365, 221, 152, 93, 259, 157, 108, 66, 202, 122, 84, 52, 552, 335, 230, 141, 432, 262, 180, 111, 312, 189, 130, 80, 235, 143, 98, 60, 652, 395, 271, 167, 513, 311, 213, 131, 364, 221, 151, 93, 288, 174, 119, 74, 772, 468, 321, 198, 604, 366, 251, 155, 427, 259, 177, 109, 331, 200, 137, 85, 883, 535, 367, 226, 691, 419, 287, 177, 489, 296, 203, 125, 374, 227, 155, 96, 1022, 619, 425, 262, 796, 483, 331, 204, 580, 352, 241, 149, 427, 259, 177, 109, 1101, 667, 458, 282, 871, 528, 362, 223, 621, 376, 258, 159, 468, 283, 194, 120, 1250, 758, 520, 320, 991, 600, 412, 254, 703, 426, 292, 180, 530, 321, 220, 136, 1408, 854, 586, 361, 1082, 656, 450, 277, 775, 470, 322, 198, 602, 365, 250, 154, 1548, 938, 644, 397, 1212, 734, 504, 310, 876, 531, 364, 224, 674, 408, 280, 173, 1725, 1046, 718, 442, 1346, 816, 560, 345, 948, 574, 394, 243, 746, 452, 310, 191, 1903, 1153, 792, 488, 1500, 909, 624, 384, 1063, 644, 442, 272, 813, 493, 338, 208, 2061, 1249, 858, 528, 1600, 970, 666, 410, 1159, 702, 482, 297, 919, 557, 382, 235, 2232, 1352, 929, 572, 1708, 1035, 711, 438, 1224, 742, 509, 314, 969, 587, 403, 248, 2409, 1460, 1003, 618, 1872, 1134, 779, 480, 1358, 823, 565, 348, 1056, 640, 439, 270, 2620, 1588, 1091, 672, 2059, 1248, 857, 528, 1468, 890, 611, 376, 1108, 672, 461, 284, 2812, 1704, 1171, 721, 2188, 1326, 911, 561, 1588, 963, 661, 407, 1228, 744, 511, 315, 3057, 1853, 1273, 784, 2395, 1451, 997, 614, 1718, 1041, 715, 440, 1286, 779, 535, 330, 3283, 1990, 1367, 842, 2544, 1542, 1059, 652, 1804, 1094, 751, 462, 1425, 864, 593, 365, 3517, 2132, 1465, 902, 2701, 1637, 1125, 692, 1933, 1172, 805, 496, 1501, 910, 625, 385, 3669, 2223, 1528, 940, 2857, 1732, 1190, 732, 2085, 1263, 868, 534, 1581, 958, 658, 405, 3909, 2369, 1628, 1002, 3035, 1839, 1264, 778, 2181, 1322, 908, 559, 1677, 1016, 698, 430, 4158, 2520, 1732, 1066, 3289, 1994, 1370, 843, 2358, 1429, 982, 604, 1782, 1080, 742, 457, 4417, 2677, 1840, 1132, 3486, 2113, 1452, 894, 2473, 1499, 1030, 634, 1897, 1150, 790, 486, 4686, 2840, 1952, 1201, 3693, 2238, 1538, 947, 2670, 1618, 1112, 684, 2022, 1226, 842, 518, 4965, 3009, 2068, 1273, 3909, 2369, 1628, 1002, 2805, 1700, 1168, 719, 2157, 1307, 898, 553, 5253, 3183, 2188, 1347, 4134, 2506, 1722, 1060, 2949, 1787, 1228, 756, 2301, 1394, 958, 590, 5529, 3351, 2303, 1417, 4343, 2632, 1809, 1113, 3081, 1867, 1283, 790, 2361, 1431, 983, 605, 5836, 3537, 2431, 1496, 4588, 2780, 1911, 1176, 3244, 1966, 1351, 832, 2524, 1530, 1051, 647, 6153, 3729, 2563, 1577, 4775, 2894, 1989, 1224, 3417, 2071, 1423, 876, 2625, 1591, 1093, 673, 6479, 3927, 2699, 1661, 5039, 3054, 2099, 1292, 3599, 2181, 1499, 923, 2735, 1658, 1139, 701, 6743, 4087, 2809, 1729, 5313, 3220, 2213, 1362, 3791, 2298, 1579, 972, 2927, 1774, 1219, 750, 7089, 4296, 2953, 1817, 5596, 3391, 2331, 1435, 3993, 2420, 1663, 1024, 3057, 1852, 1273, 784 }; + private static readonly int[] _capacityECCBaseValues = { 19, 7, 1, 19, 0, 0, 16, 10, 1, 16, 0, 0, 13, 13, 1, 13, 0, 0, 9, 17, 1, 9, 0, 0, 34, 10, 1, 34, 0, 0, 28, 16, 1, 28, 0, 0, 22, 22, 1, 22, 0, 0, 16, 28, 1, 16, 0, 0, 55, 15, 1, 55, 0, 0, 44, 26, 1, 44, 0, 0, 34, 18, 2, 17, 0, 0, 26, 22, 2, 13, 0, 0, 80, 20, 1, 80, 0, 0, 64, 18, 2, 32, 0, 0, 48, 26, 2, 24, 0, 0, 36, 16, 4, 9, 0, 0, 108, 26, 1, 108, 0, 0, 86, 24, 2, 43, 0, 0, 62, 18, 2, 15, 2, 16, 46, 22, 2, 11, 2, 12, 136, 18, 2, 68, 0, 0, 108, 16, 4, 27, 0, 0, 76, 24, 4, 19, 0, 0, 60, 28, 4, 15, 0, 0, 156, 20, 2, 78, 0, 0, 124, 18, 4, 31, 0, 0, 88, 18, 2, 14, 4, 15, 66, 26, 4, 13, 1, 14, 194, 24, 2, 97, 0, 0, 154, 22, 2, 38, 2, 39, 110, 22, 4, 18, 2, 19, 86, 26, 4, 14, 2, 15, 232, 30, 2, 116, 0, 0, 182, 22, 3, 36, 2, 37, 132, 20, 4, 16, 4, 17, 100, 24, 4, 12, 4, 13, 274, 18, 2, 68, 2, 69, 216, 26, 4, 43, 1, 44, 154, 24, 6, 19, 2, 20, 122, 28, 6, 15, 2, 16, 324, 20, 4, 81, 0, 0, 254, 30, 1, 50, 4, 51, 180, 28, 4, 22, 4, 23, 140, 24, 3, 12, 8, 13, 370, 24, 2, 92, 2, 93, 290, 22, 6, 36, 2, 37, 206, 26, 4, 20, 6, 21, 158, 28, 7, 14, 4, 15, 428, 26, 4, 107, 0, 0, 334, 22, 8, 37, 1, 38, 244, 24, 8, 20, 4, 21, 180, 22, 12, 11, 4, 12, 461, 30, 3, 115, 1, 116, 365, 24, 4, 40, 5, 41, 261, 20, 11, 16, 5, 17, 197, 24, 11, 12, 5, 13, 523, 22, 5, 87, 1, 88, 415, 24, 5, 41, 5, 42, 295, 30, 5, 24, 7, 25, 223, 24, 11, 12, 7, 13, 589, 24, 5, 98, 1, 99, 453, 28, 7, 45, 3, 46, 325, 24, 15, 19, 2, 20, 253, 30, 3, 15, 13, 16, 647, 28, 1, 107, 5, 108, 507, 28, 10, 46, 1, 47, 367, 28, 1, 22, 15, 23, 283, 28, 2, 14, 17, 15, 721, 30, 5, 120, 1, 121, 563, 26, 9, 43, 4, 44, 397, 28, 17, 22, 1, 23, 313, 28, 2, 14, 19, 15, 795, 28, 3, 113, 4, 114, 627, 26, 3, 44, 11, 45, 445, 26, 17, 21, 4, 22, 341, 26, 9, 13, 16, 14, 861, 28, 3, 107, 5, 108, 669, 26, 3, 41, 13, 42, 485, 30, 15, 24, 5, 25, 385, 28, 15, 15, 10, 16, 932, 28, 4, 116, 4, 117, 714, 26, 17, 42, 0, 0, 512, 28, 17, 22, 6, 23, 406, 30, 19, 16, 6, 17, 1006, 28, 2, 111, 7, 112, 782, 28, 17, 46, 0, 0, 568, 30, 7, 24, 16, 25, 442, 24, 34, 13, 0, 0, 1094, 30, 4, 121, 5, 122, 860, 28, 4, 47, 14, 48, 614, 30, 11, 24, 14, 25, 464, 30, 16, 15, 14, 16, 1174, 30, 6, 117, 4, 118, 914, 28, 6, 45, 14, 46, 664, 30, 11, 24, 16, 25, 514, 30, 30, 16, 2, 17, 1276, 26, 8, 106, 4, 107, 1000, 28, 8, 47, 13, 48, 718, 30, 7, 24, 22, 25, 538, 30, 22, 15, 13, 16, 1370, 28, 10, 114, 2, 115, 1062, 28, 19, 46, 4, 47, 754, 28, 28, 22, 6, 23, 596, 30, 33, 16, 4, 17, 1468, 30, 8, 122, 4, 123, 1128, 28, 22, 45, 3, 46, 808, 30, 8, 23, 26, 24, 628, 30, 12, 15, 28, 16, 1531, 30, 3, 117, 10, 118, 1193, 28, 3, 45, 23, 46, 871, 30, 4, 24, 31, 25, 661, 30, 11, 15, 31, 16, 1631, 30, 7, 116, 7, 117, 1267, 28, 21, 45, 7, 46, 911, 30, 1, 23, 37, 24, 701, 30, 19, 15, 26, 16, 1735, 30, 5, 115, 10, 116, 1373, 28, 19, 47, 10, 48, 985, 30, 15, 24, 25, 25, 745, 30, 23, 15, 25, 16, 1843, 30, 13, 115, 3, 116, 1455, 28, 2, 46, 29, 47, 1033, 30, 42, 24, 1, 25, 793, 30, 23, 15, 28, 16, 1955, 30, 17, 115, 0, 0, 1541, 28, 10, 46, 23, 47, 1115, 30, 10, 24, 35, 25, 845, 30, 19, 15, 35, 16, 2071, 30, 17, 115, 1, 116, 1631, 28, 14, 46, 21, 47, 1171, 30, 29, 24, 19, 25, 901, 30, 11, 15, 46, 16, 2191, 30, 13, 115, 6, 116, 1725, 28, 14, 46, 23, 47, 1231, 30, 44, 24, 7, 25, 961, 30, 59, 16, 1, 17, 2306, 30, 12, 121, 7, 122, 1812, 28, 12, 47, 26, 48, 1286, 30, 39, 24, 14, 25, 986, 30, 22, 15, 41, 16, 2434, 30, 6, 121, 14, 122, 1914, 28, 6, 47, 34, 48, 1354, 30, 46, 24, 10, 25, 1054, 30, 2, 15, 64, 16, 2566, 30, 17, 122, 4, 123, 1992, 28, 29, 46, 14, 47, 1426, 30, 49, 24, 10, 25, 1096, 30, 24, 15, 46, 16, 2702, 30, 4, 122, 18, 123, 2102, 28, 13, 46, 32, 47, 1502, 30, 48, 24, 14, 25, 1142, 30, 42, 15, 32, 16, 2812, 30, 20, 117, 4, 118, 2216, 28, 40, 47, 7, 48, 1582, 30, 43, 24, 22, 25, 1222, 30, 10, 15, 67, 16, 2956, 30, 19, 118, 6, 119, 2334, 28, 18, 47, 31, 48, 1666, 30, 34, 24, 34, 25, 1276, 30, 20, 15, 61, 16 }; + + public static readonly int[] GaloisFieldByExponentAlpha = { 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1 }; + public static readonly int[] GaloisFieldByIntegerValue = { 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 }; + public static readonly int[] RemainderBits = { 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 }; + + /// + /// Creates a dictionary mapping alphanumeric characters to their respective positions used in QR code encoding. + /// This includes digits 0-9, uppercase letters A-Z, and some special characters. + /// + /// A dictionary mapping each supported alphanumeric character to its corresponding value. + public static Dictionary CreateAlphanumEncDict() + { + var localAlphanumEncDict = new Dictionary(45); + for (int i = 0; i < 10; i++) + localAlphanumEncDict.Add($"{i}"[0], i); + // Add uppercase alphabetic characters. + for (char c = 'A'; c <= 'Z'; c++) + localAlphanumEncDict.Add(c, localAlphanumEncDict.Count); + // Add special characters from a predefined table. + for (int i = 0; i < _alphanumEncTable.Length; i++) + localAlphanumEncDict.Add(_alphanumEncTable[i], localAlphanumEncDict.Count); + return localAlphanumEncDict; + } + + /// + /// Checks if a character is present in the alphanumeric encoding table. + /// + public static bool AlphanumEncTableContains(char c) => _alphanumEncTable.Contains(c); + + /// + /// Creates a lookup table mapping QR code versions to their corresponding alignment patterns. + /// Alignment patterns are used in QR codes to help scanners accurately read the code at high speeds and when partially obscured. + /// This table provides the necessary patterns based on the QR code version which dictates the size and complexity of the QR code. + /// + /// A dictionary where keys are QR code version numbers and values are AlignmentPattern structures detailing the positions of alignment patterns for each version. + public static Dictionary CreateAlignmentPatternTable() + { + var localAlignmentPatternTable = new Dictionary(40); + + for (var i = 0; i < (7 * 40); i += 7) + { + var points = new List(50); + for (var x = 0; x < 7; x++) + { + if (_alignmentPatternBaseValues[i + x] != 0) + { + for (var y = 0; y < 7; y++) + { + if (_alignmentPatternBaseValues[i + y] != 0) + { + var p = new Point(_alignmentPatternBaseValues[i + x] - 2, _alignmentPatternBaseValues[i + y] - 2); + if (!points.Contains(p)) + points.Add(p); + } + } + } + } + + var version = (i + 7) / 7; + localAlignmentPatternTable.Add(version, new AlignmentPattern() + { + Version = version, + PatternPositions = points + }); + } + return localAlignmentPatternTable; + } + + /// + /// Generates a table containing the error correction capacities and data codeword information for different QR code versions and error correction levels. + /// This table is essential for determining how much data can be encoded in a QR code of a specific version and ECC level, + /// as well as how robust the QR code will be against distortions or obstructions. + /// + /// A list of ECCInfo structures, each representing the ECC data and capacities for different combinations of QR code versions and ECC levels. + public static List CreateCapacityECCTable() + { + var localCapacityECCTable = new List(160); + for (var i = 0; i < (4 * 6 * 40); i += (4 * 6)) + { + localCapacityECCTable.AddRange(new[] + { + new ECCInfo( + version: (i + 24) / 24, + errorCorrectionLevel: ECCLevel.L, + totalDataCodewords: _capacityECCBaseValues[i], + eccPerBlock: _capacityECCBaseValues[i + 1], + blocksInGroup1: _capacityECCBaseValues[i + 2], + codewordsInGroup1: _capacityECCBaseValues[i + 3], + blocksInGroup2: _capacityECCBaseValues[i + 4], + codewordsInGroup2: _capacityECCBaseValues[i + 5]), + new ECCInfo( + version: (i + 24) / 24, + errorCorrectionLevel: ECCLevel.M, + totalDataCodewords: _capacityECCBaseValues[i + 6], + eccPerBlock: _capacityECCBaseValues[i + 7], + blocksInGroup1: _capacityECCBaseValues[i + 8], + codewordsInGroup1: _capacityECCBaseValues[i + 9], + blocksInGroup2: _capacityECCBaseValues[i + 10], + codewordsInGroup2: _capacityECCBaseValues[i + 11] + ), + new ECCInfo( + version: (i + 24) / 24, + errorCorrectionLevel: ECCLevel.Q, + totalDataCodewords: _capacityECCBaseValues[i + 12], + eccPerBlock: _capacityECCBaseValues[i + 13], + blocksInGroup1: _capacityECCBaseValues[i + 14], + codewordsInGroup1: _capacityECCBaseValues[i + 15], + blocksInGroup2: _capacityECCBaseValues[i + 16], + codewordsInGroup2: _capacityECCBaseValues[i + 17] + ), + new ECCInfo( + version: (i + 24) / 24, + errorCorrectionLevel: ECCLevel.H, + totalDataCodewords: _capacityECCBaseValues[i + 18], + eccPerBlock: _capacityECCBaseValues[i + 19], + blocksInGroup1: _capacityECCBaseValues[i + 20], + codewordsInGroup1: _capacityECCBaseValues[i + 21], + blocksInGroup2: _capacityECCBaseValues[i + 22], + codewordsInGroup2: _capacityECCBaseValues[i + 23] + ) + }); + } + return localCapacityECCTable; + } + + /// + /// Generates a list containing detailed capacity information for various versions of QR codes. + /// This table includes capacities for different encoding modes (numeric, alphanumeric, byte, etc.) under each error correction level. + /// The capacity table is crucial for QR code generation, as it determines how much data each QR code version can store depending on the encoding mode and error correction level used. + /// The index in the capacity table corresponds to one less than the version number. + /// + public static List CreateCapacityTable() + { + var localCapacityTable = new List(40); + for (var i = 0; i < (16 * 40); i += 16) + { + localCapacityTable.Add(new VersionInfo( + version: (i + 16) / 16, + versionInfoDetails: new List(4) + { + new VersionInfoDetails( + ECCLevel.L, + new Dictionary(){ + { EncodingMode.Numeric, _capacityBaseValues[i] }, + { EncodingMode.Alphanumeric, _capacityBaseValues[i + 1] }, + { EncodingMode.Byte, _capacityBaseValues[i + 2] }, + { EncodingMode.Kanji, _capacityBaseValues[i + 3] }, + } + ), + new VersionInfoDetails( + ECCLevel.M, + new Dictionary(){ + { EncodingMode.Numeric, _capacityBaseValues[i + 4] }, + { EncodingMode.Alphanumeric, _capacityBaseValues[i + 5] }, + { EncodingMode.Byte, _capacityBaseValues[i + 6] }, + { EncodingMode.Kanji, _capacityBaseValues[i + 7] }, + } + ), + new VersionInfoDetails( + ECCLevel.Q, + new Dictionary(){ + { EncodingMode.Numeric, _capacityBaseValues[i + 8] }, + { EncodingMode.Alphanumeric, _capacityBaseValues[i + 9] }, + { EncodingMode.Byte, _capacityBaseValues[i + 10] }, + { EncodingMode.Kanji, _capacityBaseValues[i + 11] }, + } + ), + new VersionInfoDetails( + ECCLevel.H, + new Dictionary(){ + { EncodingMode.Numeric, _capacityBaseValues[i + 12] }, + { EncodingMode.Alphanumeric, _capacityBaseValues[i + 13] }, + { EncodingMode.Byte, _capacityBaseValues[i + 14] }, + { EncodingMode.Kanji, _capacityBaseValues[i + 15] }, + } + ) + } + )); + } + return localCapacityTable; + } + } +} From 6b57a3f3e943bdf90ffcc084d193eceb239fea67 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 6 Apr 2025 14:03:39 -0400 Subject: [PATCH 06/22] update test --- QRCoderTests/QRGeneratorTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 56cf7ca7..109589b3 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -21,7 +21,9 @@ public void validate_antilogtable() var gen = new QRCodeGenerator(); var checkString = string.Empty; - var gField = gen.GetType().GetField("_galoisFieldByExponentAlpha", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null).ShouldBeOfType(); + // Access the Tables class directly through reflection + var tablesType = Type.GetType("QRCoder.QRCodeGenerator+Tables, QRCoder"); + var gField = tablesType.GetField("GaloisFieldByExponentAlpha", BindingFlags.Public | BindingFlags.Static).GetValue(null).ShouldBeOfType(); gField.Length.ShouldBe(256); for (int i = 0; i < gField.Length; i++) { @@ -29,7 +31,7 @@ public void validate_antilogtable() } checkString.ShouldBe("0,1,:1,2,:2,4,:3,8,:4,16,:5,32,:6,64,:7,128,:8,29,:9,58,:10,116,:11,232,:12,205,:13,135,:14,19,:15,38,:16,76,:17,152,:18,45,:19,90,:20,180,:21,117,:22,234,:23,201,:24,143,:25,3,:26,6,:27,12,:28,24,:29,48,:30,96,:31,192,:32,157,:33,39,:34,78,:35,156,:36,37,:37,74,:38,148,:39,53,:40,106,:41,212,:42,181,:43,119,:44,238,:45,193,:46,159,:47,35,:48,70,:49,140,:50,5,:51,10,:52,20,:53,40,:54,80,:55,160,:56,93,:57,186,:58,105,:59,210,:60,185,:61,111,:62,222,:63,161,:64,95,:65,190,:66,97,:67,194,:68,153,:69,47,:70,94,:71,188,:72,101,:73,202,:74,137,:75,15,:76,30,:77,60,:78,120,:79,240,:80,253,:81,231,:82,211,:83,187,:84,107,:85,214,:86,177,:87,127,:88,254,:89,225,:90,223,:91,163,:92,91,:93,182,:94,113,:95,226,:96,217,:97,175,:98,67,:99,134,:100,17,:101,34,:102,68,:103,136,:104,13,:105,26,:106,52,:107,104,:108,208,:109,189,:110,103,:111,206,:112,129,:113,31,:114,62,:115,124,:116,248,:117,237,:118,199,:119,147,:120,59,:121,118,:122,236,:123,197,:124,151,:125,51,:126,102,:127,204,:128,133,:129,23,:130,46,:131,92,:132,184,:133,109,:134,218,:135,169,:136,79,:137,158,:138,33,:139,66,:140,132,:141,21,:142,42,:143,84,:144,168,:145,77,:146,154,:147,41,:148,82,:149,164,:150,85,:151,170,:152,73,:153,146,:154,57,:155,114,:156,228,:157,213,:158,183,:159,115,:160,230,:161,209,:162,191,:163,99,:164,198,:165,145,:166,63,:167,126,:168,252,:169,229,:170,215,:171,179,:172,123,:173,246,:174,241,:175,255,:176,227,:177,219,:178,171,:179,75,:180,150,:181,49,:182,98,:183,196,:184,149,:185,55,:186,110,:187,220,:188,165,:189,87,:190,174,:191,65,:192,130,:193,25,:194,50,:195,100,:196,200,:197,141,:198,7,:199,14,:200,28,:201,56,:202,112,:203,224,:204,221,:205,167,:206,83,:207,166,:208,81,:209,162,:210,89,:211,178,:212,121,:213,242,:214,249,:215,239,:216,195,:217,155,:218,43,:219,86,:220,172,:221,69,:222,138,:223,9,:224,18,:225,36,:226,72,:227,144,:228,61,:229,122,:230,244,:231,245,:232,247,:233,243,:234,251,:235,235,:236,203,:237,139,:238,11,:239,22,:240,44,:241,88,:242,176,:243,125,:244,250,:245,233,:246,207,:247,131,:248,27,:249,54,:250,108,:251,216,:252,173,:253,71,:254,142,:255,1,:"); - var gField2 = gen.GetType().GetField("_galoisFieldByIntegerValue", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null).ShouldBeOfType(); + var gField2 = tablesType.GetField("GaloisFieldByIntegerValue", BindingFlags.Public | BindingFlags.Static).GetValue(null).ShouldBeOfType(); gField2.Length.ShouldBe(256); var checkString2 = string.Empty; for (int i = 0; i < gField2.Length; i++) From 7546d16235c6703b95bee7bcdaa2b999a9727a18 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 6 Apr 2025 14:04:27 -0400 Subject: [PATCH 07/22] update --- QRCoderTests/QRGeneratorTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 109589b3..21c1973c 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -21,7 +21,6 @@ public void validate_antilogtable() var gen = new QRCodeGenerator(); var checkString = string.Empty; - // Access the Tables class directly through reflection var tablesType = Type.GetType("QRCoder.QRCodeGenerator+Tables, QRCoder"); var gField = tablesType.GetField("GaloisFieldByExponentAlpha", BindingFlags.Public | BindingFlags.Static).GetValue(null).ShouldBeOfType(); gField.Length.ShouldBe(256); From 5a97eb2ba27aa065405b935840d7a6636355554f Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 6 Apr 2025 22:38:50 -0400 Subject: [PATCH 08/22] update --- QRCoder/QRCodeGenerator.cs | 51 +----------- QRCoder/QRCodeGenerator/AlignmentPatterns.cs | 62 ++++++++++++++ .../QRCodeGenerator/AlphanumericEncoder.cs | 81 +++++++++++++++++++ QRCoder/QRCodeGenerator/Tables.cs | 69 ---------------- QRCoderTests/QRGeneratorTests.cs | 3 +- 5 files changed, 148 insertions(+), 118 deletions(-) create mode 100644 QRCoder/QRCodeGenerator/AlignmentPatterns.cs create mode 100644 QRCoder/QRCodeGenerator/AlphanumericEncoder.cs diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index ec3db1b7..30de39ae 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -16,11 +16,6 @@ namespace QRCoder; /// public partial class QRCodeGenerator : IDisposable { - /// - /// A lookup table mapping QR code versions to their corresponding alignment patterns. - /// - private static readonly Dictionary _alignmentPatternTable = Tables.CreateAlignmentPatternTable(); - /// /// A table containing the error correction capacities and data codeword information for different combinations of QR code versions and error correction levels. /// @@ -32,12 +27,6 @@ public partial class QRCodeGenerator : IDisposable /// private static readonly List _capacityTable = Tables.CreateCapacityTable(); - /// - /// A dictionary mapping alphanumeric characters to their respective positions used in QR code encoding. - /// This includes digits 0-9, uppercase letters A-Z, and some special characters. - /// - private static readonly Dictionary _alphanumEncDict = Tables.CreateAlphanumEncDict(); - /// /// Initializes the QR code generator /// @@ -348,7 +337,7 @@ QRCodeData PlaceModules() { ModulePlacer.PlaceFinderPatterns(qr, blockedModules); ModulePlacer.ReserveSeperatorAreas(size, blockedModules); - ModulePlacer.PlaceAlignmentPatterns(qr, _alignmentPatternTable[version].PatternPositions, blockedModules); + ModulePlacer.PlaceAlignmentPatterns(qr, AlignmentPatterns.FromVersion(version).PatternPositions, blockedModules); ModulePlacer.PlaceTimingPatterns(qr, blockedModules); ModulePlacer.PlaceDarkModule(qr, version, blockedModules); ModulePlacer.ReserveVersionAreas(size, version, blockedModules); @@ -623,7 +612,7 @@ private static EncodingMode GetEncodingFromPlaintext(string plainText, bool forc if (IsInRange(c, '0', '9')) continue; // numeric - char.IsDigit() for Latin1 result = EncodingMode.Alphanumeric; // not numeric, assume alphanumeric - if (IsInRange(c, 'A', 'Z') || Tables.AlphanumEncTableContains(c)) + if (AlphanumericEncoder.CanEncode(c)) continue; // alphanumeric return EncodingMode.Byte; // not numeric or alphanumeric, assume byte } @@ -804,7 +793,7 @@ private static BitArray PlainTextToBinary(string plainText, EncodingMode encMode { return encMode switch { - EncodingMode.Alphanumeric => PlainTextToBinaryAlphanumeric(plainText), + EncodingMode.Alphanumeric => AlphanumericEncoder.GetBitArray(plainText), EncodingMode.Numeric => PlainTextToBinaryNumeric(plainText), EncodingMode.Byte => PlainTextToBinaryByte(plainText, eciMode, utf8BOM, forceUtf8), _ => _emptyBitArray, @@ -862,40 +851,6 @@ private static BitArray PlainTextToBinaryNumeric(string plainText) return bitArray; } - /// - /// Converts alphanumeric plain text into a binary format optimized for QR codes. - /// Alphanumeric encoding packs characters into 11-bit groups for each pair of characters, - /// and 6 bits for a single remaining character if the total count is odd. - /// - /// The alphanumeric text to be encoded, which should only contain characters valid in QR alphanumeric mode. - /// A BitArray representing the binary data of the encoded alphanumeric text. - private static BitArray PlainTextToBinaryAlphanumeric(string plainText) - { - // Calculate the length of the BitArray needed based on the number of character pairs. - var codeText = new BitArray((plainText.Length / 2) * 11 + (plainText.Length & 1) * 6); - var codeIndex = 0; - var index = 0; - var count = plainText.Length; - - // Process each pair of characters. - while (count >= 2) - { - // Convert each pair of characters to a number by looking them up in the alphanumeric dictionary and calculating. - var dec = _alphanumEncDict[plainText[index++]] * 45 + _alphanumEncDict[plainText[index++]]; - // Convert the number to binary and store it in the BitArray. - codeIndex = DecToBin(dec, 11, codeText, codeIndex); - count -= 2; - } - - // Handle the last character if the length is odd. - if (count > 0) - { - DecToBin(_alphanumEncDict[plainText[index]], 6, codeText, codeIndex); - } - - return codeText; - } - private static readonly Encoding _iso8859_1 = #if NET5_0_OR_GREATER Encoding.Latin1; diff --git a/QRCoder/QRCodeGenerator/AlignmentPatterns.cs b/QRCoder/QRCodeGenerator/AlignmentPatterns.cs new file mode 100644 index 00000000..fe879623 --- /dev/null +++ b/QRCoder/QRCodeGenerator/AlignmentPatterns.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; + +namespace QRCoder; + +public partial class QRCodeGenerator +{ + /// + /// This class contains the alignment patterns used in QR codes. + /// + private static class AlignmentPatterns + { + /// + /// A lookup table mapping QR code versions to their corresponding alignment patterns. + /// + private static readonly Dictionary _alignmentPatternTable = CreateAlignmentPatternTable(); + + /// + /// Retrieves the alignment pattern for a specific QR code version. + /// + public static AlignmentPattern FromVersion(int version) => _alignmentPatternTable[version]; + + /// + /// Creates a lookup table mapping QR code versions to their corresponding alignment patterns. + /// Alignment patterns are used in QR codes to help scanners accurately read the code at high speeds and when partially obscured. + /// This table provides the necessary patterns based on the QR code version which dictates the size and complexity of the QR code. + /// + /// A dictionary where keys are QR code version numbers and values are AlignmentPattern structures detailing the positions of alignment patterns for each version. + private static Dictionary CreateAlignmentPatternTable() + { + var alignmentPatternBaseValues = new int[] { 0, 0, 0, 0, 0, 0, 0, 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 }; + var localAlignmentPatternTable = new Dictionary(40); + + for (var i = 0; i < (7 * 40); i += 7) + { + var points = new List(50); + for (var x = 0; x < 7; x++) + { + if (alignmentPatternBaseValues[i + x] != 0) + { + for (var y = 0; y < 7; y++) + { + if (alignmentPatternBaseValues[i + y] != 0) + { + var p = new Point(alignmentPatternBaseValues[i + x] - 2, alignmentPatternBaseValues[i + y] - 2); + if (!points.Contains(p)) + points.Add(p); + } + } + } + } + + var version = (i + 7) / 7; + localAlignmentPatternTable.Add(version, new AlignmentPattern() + { + Version = version, + PatternPositions = points + }); + } + return localAlignmentPatternTable; + } + } +} diff --git a/QRCoder/QRCodeGenerator/AlphanumericEncoder.cs b/QRCoder/QRCodeGenerator/AlphanumericEncoder.cs new file mode 100644 index 00000000..b0e46aca --- /dev/null +++ b/QRCoder/QRCodeGenerator/AlphanumericEncoder.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace QRCoder; + +public partial class QRCodeGenerator +{ + /// + /// Encodes alphanumeric characters (0–9, A–Z (uppercase), space, $, %, *, +, -, period, /, colon) into a binary format suitable for QR codes. + /// + private static class AlphanumericEncoder + { + private static readonly char[] _alphanumEncTable = { ' ', '$', '%', '*', '+', '-', '.', '/', ':' }; + + /// + /// A dictionary mapping alphanumeric characters to their respective positions used in QR code encoding. + /// This includes digits 0-9, uppercase letters A-Z, and some special characters. + /// + private static readonly Dictionary _alphanumEncDict = CreateAlphanumEncDict(_alphanumEncTable); + + /// + /// Creates a dictionary mapping alphanumeric characters to their respective positions used in QR code encoding. + /// This includes digits 0-9, uppercase letters A-Z, and some special characters. + /// + /// A dictionary mapping each supported alphanumeric character to its corresponding value. + private static Dictionary CreateAlphanumEncDict(char[] alphanumEncTable) + { + var localAlphanumEncDict = new Dictionary(45); + // Add 0-9 + for (char c = '0'; c <= '9'; c++) + localAlphanumEncDict.Add(c, c - '0'); + // Add uppercase alphabetic characters. + for (char c = 'A'; c <= 'Z'; c++) + localAlphanumEncDict.Add(c, localAlphanumEncDict.Count); + // Add special characters from a predefined table. + for (int i = 0; i < _alphanumEncTable.Length; i++) + localAlphanumEncDict.Add(alphanumEncTable[i], localAlphanumEncDict.Count); + return localAlphanumEncDict; + } + + /// + /// Checks if a character is present in the alphanumeric encoding table. + /// + public static bool CanEncode(char c) => IsInRange(c, 'A', 'Z') || Array.IndexOf(_alphanumEncTable, c) >= 0; + + /// + /// Converts alphanumeric plain text into a binary format optimized for QR codes. + /// Alphanumeric encoding packs characters into 11-bit groups for each pair of characters, + /// and 6 bits for a single remaining character if the total count is odd. + /// + /// The alphanumeric text to be encoded, which should only contain characters valid in QR alphanumeric mode. + /// A BitArray representing the binary data of the encoded alphanumeric text. + public static BitArray GetBitArray(string plainText) + { + // Calculate the length of the BitArray needed based on the number of character pairs. + var codeText = new BitArray((plainText.Length / 2) * 11 + (plainText.Length & 1) * 6); + var codeIndex = 0; + var index = 0; + var count = plainText.Length; + + // Process each pair of characters. + while (count >= 2) + { + // Convert each pair of characters to a number by looking them up in the alphanumeric dictionary and calculating. + var dec = _alphanumEncDict[plainText[index++]] * 45 + _alphanumEncDict[plainText[index++]]; + // Convert the number to binary and store it in the BitArray. + codeIndex = DecToBin(dec, 11, codeText, codeIndex); + count -= 2; + } + + // Handle the last character if the length is odd. + if (count > 0) + { + DecToBin(_alphanumEncDict[plainText[index]], 6, codeText, codeIndex); + } + + return codeText; + } + } +} diff --git a/QRCoder/QRCodeGenerator/Tables.cs b/QRCoder/QRCodeGenerator/Tables.cs index bcfdfe5b..2cc528f7 100644 --- a/QRCoder/QRCodeGenerator/Tables.cs +++ b/QRCoder/QRCodeGenerator/Tables.cs @@ -1,11 +1,7 @@ using System.Collections.Generic; -using System.Linq; namespace QRCoder; -/// -/// Partial class containing table generation methods for QR code generation. -/// public partial class QRCodeGenerator { /// @@ -13,8 +9,6 @@ public partial class QRCodeGenerator /// private static class Tables { - private static readonly char[] _alphanumEncTable = { ' ', '$', '%', '*', '+', '-', '.', '/', ':' }; - private static readonly int[] _alignmentPatternBaseValues = { 0, 0, 0, 0, 0, 0, 0, 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 }; private static readonly int[] _capacityBaseValues = { 41, 25, 17, 10, 34, 20, 14, 8, 27, 16, 11, 7, 17, 10, 7, 4, 77, 47, 32, 20, 63, 38, 26, 16, 48, 29, 20, 12, 34, 20, 14, 8, 127, 77, 53, 32, 101, 61, 42, 26, 77, 47, 32, 20, 58, 35, 24, 15, 187, 114, 78, 48, 149, 90, 62, 38, 111, 67, 46, 28, 82, 50, 34, 21, 255, 154, 106, 65, 202, 122, 84, 52, 144, 87, 60, 37, 106, 64, 44, 27, 322, 195, 134, 82, 255, 154, 106, 65, 178, 108, 74, 45, 139, 84, 58, 36, 370, 224, 154, 95, 293, 178, 122, 75, 207, 125, 86, 53, 154, 93, 64, 39, 461, 279, 192, 118, 365, 221, 152, 93, 259, 157, 108, 66, 202, 122, 84, 52, 552, 335, 230, 141, 432, 262, 180, 111, 312, 189, 130, 80, 235, 143, 98, 60, 652, 395, 271, 167, 513, 311, 213, 131, 364, 221, 151, 93, 288, 174, 119, 74, 772, 468, 321, 198, 604, 366, 251, 155, 427, 259, 177, 109, 331, 200, 137, 85, 883, 535, 367, 226, 691, 419, 287, 177, 489, 296, 203, 125, 374, 227, 155, 96, 1022, 619, 425, 262, 796, 483, 331, 204, 580, 352, 241, 149, 427, 259, 177, 109, 1101, 667, 458, 282, 871, 528, 362, 223, 621, 376, 258, 159, 468, 283, 194, 120, 1250, 758, 520, 320, 991, 600, 412, 254, 703, 426, 292, 180, 530, 321, 220, 136, 1408, 854, 586, 361, 1082, 656, 450, 277, 775, 470, 322, 198, 602, 365, 250, 154, 1548, 938, 644, 397, 1212, 734, 504, 310, 876, 531, 364, 224, 674, 408, 280, 173, 1725, 1046, 718, 442, 1346, 816, 560, 345, 948, 574, 394, 243, 746, 452, 310, 191, 1903, 1153, 792, 488, 1500, 909, 624, 384, 1063, 644, 442, 272, 813, 493, 338, 208, 2061, 1249, 858, 528, 1600, 970, 666, 410, 1159, 702, 482, 297, 919, 557, 382, 235, 2232, 1352, 929, 572, 1708, 1035, 711, 438, 1224, 742, 509, 314, 969, 587, 403, 248, 2409, 1460, 1003, 618, 1872, 1134, 779, 480, 1358, 823, 565, 348, 1056, 640, 439, 270, 2620, 1588, 1091, 672, 2059, 1248, 857, 528, 1468, 890, 611, 376, 1108, 672, 461, 284, 2812, 1704, 1171, 721, 2188, 1326, 911, 561, 1588, 963, 661, 407, 1228, 744, 511, 315, 3057, 1853, 1273, 784, 2395, 1451, 997, 614, 1718, 1041, 715, 440, 1286, 779, 535, 330, 3283, 1990, 1367, 842, 2544, 1542, 1059, 652, 1804, 1094, 751, 462, 1425, 864, 593, 365, 3517, 2132, 1465, 902, 2701, 1637, 1125, 692, 1933, 1172, 805, 496, 1501, 910, 625, 385, 3669, 2223, 1528, 940, 2857, 1732, 1190, 732, 2085, 1263, 868, 534, 1581, 958, 658, 405, 3909, 2369, 1628, 1002, 3035, 1839, 1264, 778, 2181, 1322, 908, 559, 1677, 1016, 698, 430, 4158, 2520, 1732, 1066, 3289, 1994, 1370, 843, 2358, 1429, 982, 604, 1782, 1080, 742, 457, 4417, 2677, 1840, 1132, 3486, 2113, 1452, 894, 2473, 1499, 1030, 634, 1897, 1150, 790, 486, 4686, 2840, 1952, 1201, 3693, 2238, 1538, 947, 2670, 1618, 1112, 684, 2022, 1226, 842, 518, 4965, 3009, 2068, 1273, 3909, 2369, 1628, 1002, 2805, 1700, 1168, 719, 2157, 1307, 898, 553, 5253, 3183, 2188, 1347, 4134, 2506, 1722, 1060, 2949, 1787, 1228, 756, 2301, 1394, 958, 590, 5529, 3351, 2303, 1417, 4343, 2632, 1809, 1113, 3081, 1867, 1283, 790, 2361, 1431, 983, 605, 5836, 3537, 2431, 1496, 4588, 2780, 1911, 1176, 3244, 1966, 1351, 832, 2524, 1530, 1051, 647, 6153, 3729, 2563, 1577, 4775, 2894, 1989, 1224, 3417, 2071, 1423, 876, 2625, 1591, 1093, 673, 6479, 3927, 2699, 1661, 5039, 3054, 2099, 1292, 3599, 2181, 1499, 923, 2735, 1658, 1139, 701, 6743, 4087, 2809, 1729, 5313, 3220, 2213, 1362, 3791, 2298, 1579, 972, 2927, 1774, 1219, 750, 7089, 4296, 2953, 1817, 5596, 3391, 2331, 1435, 3993, 2420, 1663, 1024, 3057, 1852, 1273, 784 }; private static readonly int[] _capacityECCBaseValues = { 19, 7, 1, 19, 0, 0, 16, 10, 1, 16, 0, 0, 13, 13, 1, 13, 0, 0, 9, 17, 1, 9, 0, 0, 34, 10, 1, 34, 0, 0, 28, 16, 1, 28, 0, 0, 22, 22, 1, 22, 0, 0, 16, 28, 1, 16, 0, 0, 55, 15, 1, 55, 0, 0, 44, 26, 1, 44, 0, 0, 34, 18, 2, 17, 0, 0, 26, 22, 2, 13, 0, 0, 80, 20, 1, 80, 0, 0, 64, 18, 2, 32, 0, 0, 48, 26, 2, 24, 0, 0, 36, 16, 4, 9, 0, 0, 108, 26, 1, 108, 0, 0, 86, 24, 2, 43, 0, 0, 62, 18, 2, 15, 2, 16, 46, 22, 2, 11, 2, 12, 136, 18, 2, 68, 0, 0, 108, 16, 4, 27, 0, 0, 76, 24, 4, 19, 0, 0, 60, 28, 4, 15, 0, 0, 156, 20, 2, 78, 0, 0, 124, 18, 4, 31, 0, 0, 88, 18, 2, 14, 4, 15, 66, 26, 4, 13, 1, 14, 194, 24, 2, 97, 0, 0, 154, 22, 2, 38, 2, 39, 110, 22, 4, 18, 2, 19, 86, 26, 4, 14, 2, 15, 232, 30, 2, 116, 0, 0, 182, 22, 3, 36, 2, 37, 132, 20, 4, 16, 4, 17, 100, 24, 4, 12, 4, 13, 274, 18, 2, 68, 2, 69, 216, 26, 4, 43, 1, 44, 154, 24, 6, 19, 2, 20, 122, 28, 6, 15, 2, 16, 324, 20, 4, 81, 0, 0, 254, 30, 1, 50, 4, 51, 180, 28, 4, 22, 4, 23, 140, 24, 3, 12, 8, 13, 370, 24, 2, 92, 2, 93, 290, 22, 6, 36, 2, 37, 206, 26, 4, 20, 6, 21, 158, 28, 7, 14, 4, 15, 428, 26, 4, 107, 0, 0, 334, 22, 8, 37, 1, 38, 244, 24, 8, 20, 4, 21, 180, 22, 12, 11, 4, 12, 461, 30, 3, 115, 1, 116, 365, 24, 4, 40, 5, 41, 261, 20, 11, 16, 5, 17, 197, 24, 11, 12, 5, 13, 523, 22, 5, 87, 1, 88, 415, 24, 5, 41, 5, 42, 295, 30, 5, 24, 7, 25, 223, 24, 11, 12, 7, 13, 589, 24, 5, 98, 1, 99, 453, 28, 7, 45, 3, 46, 325, 24, 15, 19, 2, 20, 253, 30, 3, 15, 13, 16, 647, 28, 1, 107, 5, 108, 507, 28, 10, 46, 1, 47, 367, 28, 1, 22, 15, 23, 283, 28, 2, 14, 17, 15, 721, 30, 5, 120, 1, 121, 563, 26, 9, 43, 4, 44, 397, 28, 17, 22, 1, 23, 313, 28, 2, 14, 19, 15, 795, 28, 3, 113, 4, 114, 627, 26, 3, 44, 11, 45, 445, 26, 17, 21, 4, 22, 341, 26, 9, 13, 16, 14, 861, 28, 3, 107, 5, 108, 669, 26, 3, 41, 13, 42, 485, 30, 15, 24, 5, 25, 385, 28, 15, 15, 10, 16, 932, 28, 4, 116, 4, 117, 714, 26, 17, 42, 0, 0, 512, 28, 17, 22, 6, 23, 406, 30, 19, 16, 6, 17, 1006, 28, 2, 111, 7, 112, 782, 28, 17, 46, 0, 0, 568, 30, 7, 24, 16, 25, 442, 24, 34, 13, 0, 0, 1094, 30, 4, 121, 5, 122, 860, 28, 4, 47, 14, 48, 614, 30, 11, 24, 14, 25, 464, 30, 16, 15, 14, 16, 1174, 30, 6, 117, 4, 118, 914, 28, 6, 45, 14, 46, 664, 30, 11, 24, 16, 25, 514, 30, 30, 16, 2, 17, 1276, 26, 8, 106, 4, 107, 1000, 28, 8, 47, 13, 48, 718, 30, 7, 24, 22, 25, 538, 30, 22, 15, 13, 16, 1370, 28, 10, 114, 2, 115, 1062, 28, 19, 46, 4, 47, 754, 28, 28, 22, 6, 23, 596, 30, 33, 16, 4, 17, 1468, 30, 8, 122, 4, 123, 1128, 28, 22, 45, 3, 46, 808, 30, 8, 23, 26, 24, 628, 30, 12, 15, 28, 16, 1531, 30, 3, 117, 10, 118, 1193, 28, 3, 45, 23, 46, 871, 30, 4, 24, 31, 25, 661, 30, 11, 15, 31, 16, 1631, 30, 7, 116, 7, 117, 1267, 28, 21, 45, 7, 46, 911, 30, 1, 23, 37, 24, 701, 30, 19, 15, 26, 16, 1735, 30, 5, 115, 10, 116, 1373, 28, 19, 47, 10, 48, 985, 30, 15, 24, 25, 25, 745, 30, 23, 15, 25, 16, 1843, 30, 13, 115, 3, 116, 1455, 28, 2, 46, 29, 47, 1033, 30, 42, 24, 1, 25, 793, 30, 23, 15, 28, 16, 1955, 30, 17, 115, 0, 0, 1541, 28, 10, 46, 23, 47, 1115, 30, 10, 24, 35, 25, 845, 30, 19, 15, 35, 16, 2071, 30, 17, 115, 1, 116, 1631, 28, 14, 46, 21, 47, 1171, 30, 29, 24, 19, 25, 901, 30, 11, 15, 46, 16, 2191, 30, 13, 115, 6, 116, 1725, 28, 14, 46, 23, 47, 1231, 30, 44, 24, 7, 25, 961, 30, 59, 16, 1, 17, 2306, 30, 12, 121, 7, 122, 1812, 28, 12, 47, 26, 48, 1286, 30, 39, 24, 14, 25, 986, 30, 22, 15, 41, 16, 2434, 30, 6, 121, 14, 122, 1914, 28, 6, 47, 34, 48, 1354, 30, 46, 24, 10, 25, 1054, 30, 2, 15, 64, 16, 2566, 30, 17, 122, 4, 123, 1992, 28, 29, 46, 14, 47, 1426, 30, 49, 24, 10, 25, 1096, 30, 24, 15, 46, 16, 2702, 30, 4, 122, 18, 123, 2102, 28, 13, 46, 32, 47, 1502, 30, 48, 24, 14, 25, 1142, 30, 42, 15, 32, 16, 2812, 30, 20, 117, 4, 118, 2216, 28, 40, 47, 7, 48, 1582, 30, 43, 24, 22, 25, 1222, 30, 10, 15, 67, 16, 2956, 30, 19, 118, 6, 119, 2334, 28, 18, 47, 31, 48, 1666, 30, 34, 24, 34, 25, 1276, 30, 20, 15, 61, 16 }; @@ -22,69 +16,6 @@ private static class Tables public static readonly int[] GaloisFieldByIntegerValue = { 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 }; public static readonly int[] RemainderBits = { 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 }; - /// - /// Creates a dictionary mapping alphanumeric characters to their respective positions used in QR code encoding. - /// This includes digits 0-9, uppercase letters A-Z, and some special characters. - /// - /// A dictionary mapping each supported alphanumeric character to its corresponding value. - public static Dictionary CreateAlphanumEncDict() - { - var localAlphanumEncDict = new Dictionary(45); - for (int i = 0; i < 10; i++) - localAlphanumEncDict.Add($"{i}"[0], i); - // Add uppercase alphabetic characters. - for (char c = 'A'; c <= 'Z'; c++) - localAlphanumEncDict.Add(c, localAlphanumEncDict.Count); - // Add special characters from a predefined table. - for (int i = 0; i < _alphanumEncTable.Length; i++) - localAlphanumEncDict.Add(_alphanumEncTable[i], localAlphanumEncDict.Count); - return localAlphanumEncDict; - } - - /// - /// Checks if a character is present in the alphanumeric encoding table. - /// - public static bool AlphanumEncTableContains(char c) => _alphanumEncTable.Contains(c); - - /// - /// Creates a lookup table mapping QR code versions to their corresponding alignment patterns. - /// Alignment patterns are used in QR codes to help scanners accurately read the code at high speeds and when partially obscured. - /// This table provides the necessary patterns based on the QR code version which dictates the size and complexity of the QR code. - /// - /// A dictionary where keys are QR code version numbers and values are AlignmentPattern structures detailing the positions of alignment patterns for each version. - public static Dictionary CreateAlignmentPatternTable() - { - var localAlignmentPatternTable = new Dictionary(40); - - for (var i = 0; i < (7 * 40); i += 7) - { - var points = new List(50); - for (var x = 0; x < 7; x++) - { - if (_alignmentPatternBaseValues[i + x] != 0) - { - for (var y = 0; y < 7; y++) - { - if (_alignmentPatternBaseValues[i + y] != 0) - { - var p = new Point(_alignmentPatternBaseValues[i + x] - 2, _alignmentPatternBaseValues[i + y] - 2); - if (!points.Contains(p)) - points.Add(p); - } - } - } - } - - var version = (i + 7) / 7; - localAlignmentPatternTable.Add(version, new AlignmentPattern() - { - Version = version, - PatternPositions = points - }); - } - return localAlignmentPatternTable; - } - /// /// Generates a table containing the error correction capacities and data codeword information for different QR code versions and error correction levels. /// This table is essential for determining how much data can be encoded in a QR code of a specific version and ECC level, diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 21c1973c..fce7544a 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -370,7 +370,8 @@ public void validate_alphanumencdict() var gen = new QRCodeGenerator(); var checkString = string.Empty; - var gField = gen.GetType().GetField("_alphanumEncDict", BindingFlags.NonPublic | BindingFlags.Static); + var encoderType = Type.GetType("QRCoder.QRCodeGenerator+AlphanumericEncoder, QRCoder"); + var gField = encoderType.GetField("_alphanumEncDict", BindingFlags.NonPublic | BindingFlags.Static); foreach (var listitem in (Dictionary)gField.GetValue(gen)) { checkString += $"{listitem.Key},{listitem.Value}:"; From ae3d1c2588a172a614cf295444b6e456a8338698 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Fri, 11 Apr 2025 23:06:02 -0400 Subject: [PATCH 09/22] update --- QRCoder.sln | 1 - global.json | 7 ------- 2 files changed, 8 deletions(-) delete mode 100644 global.json diff --git a/QRCoder.sln b/QRCoder.sln index 3afc8b4f..49102939 100644 --- a/QRCoder.sln +++ b/QRCoder.sln @@ -25,7 +25,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig Directory.Build.props = Directory.Build.props - global.json = global.json EndProjectSection EndProject Global diff --git a/global.json b/global.json deleted file mode 100644 index b7e33571..00000000 --- a/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "8.0.100", - "allowPrerelease": false, - "rollForward": "latestFeature" - } -} From ddb443ccb354195a3f87112feec4007a3df068df Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 12 Apr 2025 00:14:00 -0400 Subject: [PATCH 10/22] Separate GaloisField from CapacityTables --- QRCoder/QRCodeGenerator.cs | 90 +-------- .../{Tables.cs => CapacityTables.cs} | 178 +++++++++++++----- QRCoder/QRCodeGenerator/GaloisField.cs | 48 +++++ QRCoderTests/QRGeneratorTests.cs | 6 +- 4 files changed, 192 insertions(+), 130 deletions(-) rename QRCoder/QRCodeGenerator/{Tables.cs => CapacityTables.cs} (55%) create mode 100644 QRCoder/QRCodeGenerator/GaloisField.cs diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index 30de39ae..24a6a5d3 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -16,17 +16,6 @@ namespace QRCoder; /// public partial class QRCodeGenerator : IDisposable { - /// - /// A table containing the error correction capacities and data codeword information for different combinations of QR code versions and error correction levels. - /// - private static readonly List _capacityECCTable = Tables.CreateCapacityECCTable(); - - /// - /// A list containing detailed capacity information for each version of QR codes. - /// The index in the capacity table corresponds to one less than the version number. - /// - private static readonly List _capacityTable = Tables.CreateCapacityTable(); - /// /// Initializes the QR code generator /// @@ -121,7 +110,7 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo var codedText = PlainTextToBinary(plainText, encoding, eciMode, utf8BOM, forceUtf8); var dataInputLength = GetDataLength(encoding, plainText, codedText, forceUtf8); int version = requestedVersion; - int minVersion = GetVersion(dataInputLength + (eciMode != EciMode.Default ? 2 : 0), encoding, eccLevel); + int minVersion = CapacityTables.GetVersion(dataInputLength + (eciMode != EciMode.Default ? 2 : 0), encoding, eccLevel); if (version == -1) { version = minVersion; @@ -131,7 +120,7 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo //Version was passed as fixed version via parameter. Thus let's check if chosen version is valid. if (minVersion > version) { - var maxSizeByte = _capacityTable[version - 1].Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding]; + var maxSizeByte = CapacityTables.GetCapacityInfo(version).Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding]; throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte); } } @@ -171,7 +160,7 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel) { eccLevel = ValidateECCLevel(eccLevel); - int version = GetVersion(binaryData.Length, EncodingMode.Byte, eccLevel); + int version = CapacityTables.GetVersion(binaryData.Length, EncodingMode.Byte, eccLevel); int countIndicatorLen = GetCountIndicatorLength(version, EncodingMode.Byte); // Convert byte array to bit array, with prefix padding for mode indicator and count indicator @@ -211,7 +200,7 @@ private static ECCLevel ValidateECCLevel(ECCLevel eccLevel) /// A QRCodeData structure containing the full QR code matrix, which can be used for rendering or analysis. private static QRCodeData GenerateQrCode(BitArray bitArray, ECCLevel eccLevel, int version) { - var eccInfo = _capacityECCTable.Single(x => x.Version == version && x.ErrorCorrectionLevel == eccLevel); + var eccInfo = CapacityTables.GetCapacityEccInfo(version, eccLevel); // Fill up data code word PadData(); @@ -300,7 +289,7 @@ int CalculateInterleavedLength() if (codeBlock.ECCWords.Length > i) length += 8; } - length += Tables.RemainderBits[version - 1]; + length += CapacityTables.GetRemainderBits(version); return length; } @@ -526,7 +515,7 @@ private static byte[] CalculateECCWords(BitArray bitArray, int offset, int count // Convert the first coefficient to its corresponding alpha exponent unless it's zero. // Coefficients that are zero remain zero because log(0) is undefined. var index0Coefficient = leadTermSource[0].Coefficient; - index0Coefficient = index0Coefficient == 0 ? 0 : GetAlphaExpFromIntVal(index0Coefficient); + index0Coefficient = index0Coefficient == 0 ? 0 : GaloisField.GetAlphaExpFromIntVal(index0Coefficient); var alphaNotation = new PolynomItem(index0Coefficient, leadTermSource[0].Exponent); var resPoly = MultiplyGeneratorPolynomByLeadterm(generatorPolynom, alphaNotation, i); ConvertToDecNotationInPlace(resPoly); @@ -562,40 +551,8 @@ private static void ConvertToDecNotationInPlace(Polynom poly) for (var i = 0; i < poly.Count; i++) { // Convert the alpha exponent of the coefficient to its decimal value and create a new polynomial item with the updated coefficient. - poly[i] = new PolynomItem(GetIntValFromAlphaExp(poly[i].Coefficient), poly[i].Exponent); - } - } - - /// - /// Determines the minimum QR code version required to encode a given amount of data with a specific encoding mode and error correction level. - /// If no suitable version is found, it throws an exception indicating that the data length exceeds the maximum capacity for the given settings. - /// - /// The length of the data to be encoded. - /// The encoding mode (e.g., Numeric, Alphanumeric, Byte). - /// The error correction level (e.g., Low, Medium, Quartile, High). - /// The minimum version of the QR code that can accommodate the given data and settings. - private static int GetVersion(int length, EncodingMode encMode, ECCLevel eccLevel) - { - // capacity table is already sorted by version number ascending, so the smallest version that can hold the data is the first one found - foreach (var x in _capacityTable) - { - // find the requested ECC level and encoding mode in the capacity table - foreach (var y in x.Details) - { - if (y.ErrorCorrectionLevel == eccLevel && y.CapacityDict[encMode] >= length) - { - // if the capacity of the current version is enough, return the version number - return x.Version; - } - } + poly[i] = new PolynomItem(GaloisField.GetIntValFromAlphaExp(poly[i].Coefficient), poly[i].Exponent); } - - // if no version was found, throw an exception - var maxSizeByte = _capacityTable.Where( - x => x.Details.Any( - y => (y.ErrorCorrectionLevel == eccLevel)) - ).Max(x => x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel).CapacityDict[encMode]); - throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte); } /// @@ -1052,7 +1009,7 @@ private static Polynom MultiplyAlphaPolynoms(Polynom polynomBase, Polynom polyno // Create a new polynomial term with the coefficients added (as exponents) and exponents summed. var polItemRes = new PolynomItem ( - ShrinkAlphaExp(polItemBase.Coefficient + polItemMulti.Coefficient), + GaloisField.ShrinkAlphaExp(polItemBase.Coefficient + polItemMulti.Coefficient), (polItemBase.Exponent + polItemMulti.Exponent) ); resultPolynom.Add(polItemRes); @@ -1069,11 +1026,11 @@ private static Polynom MultiplyAlphaPolynoms(Polynom polynomBase, Polynom polyno foreach (var polynomOld in resultPolynom) { if (polynomOld.Exponent == exponent) - coefficient ^= GetIntValFromAlphaExp(polynomOld.Coefficient); + coefficient ^= GaloisField.GetIntValFromAlphaExp(polynomOld.Coefficient); } // Fix the polynomial terms by recalculating the coefficients based on XORed results. - var polynomFixed = new PolynomItem(GetAlphaExpFromIntVal(coefficient), exponent); + var polynomFixed = new PolynomItem(GaloisField.GetAlphaExpFromIntVal(coefficient), exponent); gluedPolynoms[gluedPolynomsIndex++] = polynomFixed; } @@ -1125,33 +1082,6 @@ int[] GetNotUniqueExponents(Polynom list) } } - /// - /// Retrieves the integer value from the Galois field that corresponds to a given exponent. - /// This is used in Reed-Solomon and other error correction calculations involving Galois fields. - /// - private static int GetIntValFromAlphaExp(int exp) - => Tables.GaloisFieldByExponentAlpha[exp]; - - /// - /// Retrieves the exponent from the Galois field that corresponds to a given integer value. - /// Throws an exception if the integer value is zero, as zero does not have a logarithmic representation in the field. - /// - private static int GetAlphaExpFromIntVal(int intVal) - { - if (intVal == 0) - ThrowIntValOutOfRangeException(); // Zero is not valid as it does not have an exponent representation. - return Tables.GaloisFieldByIntegerValue[intVal]; - - void ThrowIntValOutOfRangeException() => throw new ArgumentOutOfRangeException(nameof(intVal), "The provided integer value is out of range, as zero is not representable."); - } - - /// - /// Normalizes a Galois field exponent to ensure it remains within the bounds of the field's size. - /// This is particularly necessary when performing multiplications in the field which can result in exponents exceeding the field's maximum. - /// - private static int ShrinkAlphaExp(int alphaExp) - => (int)((alphaExp % 256) + Math.Floor((double)(alphaExp / 256))); - /// public void Dispose() { diff --git a/QRCoder/QRCodeGenerator/Tables.cs b/QRCoder/QRCodeGenerator/CapacityTables.cs similarity index 55% rename from QRCoder/QRCodeGenerator/Tables.cs rename to QRCoder/QRCodeGenerator/CapacityTables.cs index 2cc528f7..393aa8f1 100644 --- a/QRCoder/QRCodeGenerator/Tables.cs +++ b/QRCoder/QRCodeGenerator/CapacityTables.cs @@ -1,20 +1,104 @@ +using System; using System.Collections.Generic; +using System.Linq; namespace QRCoder; public partial class QRCodeGenerator { /// - /// Contains static tables used for QR code generation, including Galois field values, alignment patterns, and capacity values. + /// Provides QR code capacity and error correction data for each version and encoding mode. + /// Used to determine how much data can be stored in a QR code and which version is required. /// - private static class Tables + private static class CapacityTables { private static readonly int[] _capacityBaseValues = { 41, 25, 17, 10, 34, 20, 14, 8, 27, 16, 11, 7, 17, 10, 7, 4, 77, 47, 32, 20, 63, 38, 26, 16, 48, 29, 20, 12, 34, 20, 14, 8, 127, 77, 53, 32, 101, 61, 42, 26, 77, 47, 32, 20, 58, 35, 24, 15, 187, 114, 78, 48, 149, 90, 62, 38, 111, 67, 46, 28, 82, 50, 34, 21, 255, 154, 106, 65, 202, 122, 84, 52, 144, 87, 60, 37, 106, 64, 44, 27, 322, 195, 134, 82, 255, 154, 106, 65, 178, 108, 74, 45, 139, 84, 58, 36, 370, 224, 154, 95, 293, 178, 122, 75, 207, 125, 86, 53, 154, 93, 64, 39, 461, 279, 192, 118, 365, 221, 152, 93, 259, 157, 108, 66, 202, 122, 84, 52, 552, 335, 230, 141, 432, 262, 180, 111, 312, 189, 130, 80, 235, 143, 98, 60, 652, 395, 271, 167, 513, 311, 213, 131, 364, 221, 151, 93, 288, 174, 119, 74, 772, 468, 321, 198, 604, 366, 251, 155, 427, 259, 177, 109, 331, 200, 137, 85, 883, 535, 367, 226, 691, 419, 287, 177, 489, 296, 203, 125, 374, 227, 155, 96, 1022, 619, 425, 262, 796, 483, 331, 204, 580, 352, 241, 149, 427, 259, 177, 109, 1101, 667, 458, 282, 871, 528, 362, 223, 621, 376, 258, 159, 468, 283, 194, 120, 1250, 758, 520, 320, 991, 600, 412, 254, 703, 426, 292, 180, 530, 321, 220, 136, 1408, 854, 586, 361, 1082, 656, 450, 277, 775, 470, 322, 198, 602, 365, 250, 154, 1548, 938, 644, 397, 1212, 734, 504, 310, 876, 531, 364, 224, 674, 408, 280, 173, 1725, 1046, 718, 442, 1346, 816, 560, 345, 948, 574, 394, 243, 746, 452, 310, 191, 1903, 1153, 792, 488, 1500, 909, 624, 384, 1063, 644, 442, 272, 813, 493, 338, 208, 2061, 1249, 858, 528, 1600, 970, 666, 410, 1159, 702, 482, 297, 919, 557, 382, 235, 2232, 1352, 929, 572, 1708, 1035, 711, 438, 1224, 742, 509, 314, 969, 587, 403, 248, 2409, 1460, 1003, 618, 1872, 1134, 779, 480, 1358, 823, 565, 348, 1056, 640, 439, 270, 2620, 1588, 1091, 672, 2059, 1248, 857, 528, 1468, 890, 611, 376, 1108, 672, 461, 284, 2812, 1704, 1171, 721, 2188, 1326, 911, 561, 1588, 963, 661, 407, 1228, 744, 511, 315, 3057, 1853, 1273, 784, 2395, 1451, 997, 614, 1718, 1041, 715, 440, 1286, 779, 535, 330, 3283, 1990, 1367, 842, 2544, 1542, 1059, 652, 1804, 1094, 751, 462, 1425, 864, 593, 365, 3517, 2132, 1465, 902, 2701, 1637, 1125, 692, 1933, 1172, 805, 496, 1501, 910, 625, 385, 3669, 2223, 1528, 940, 2857, 1732, 1190, 732, 2085, 1263, 868, 534, 1581, 958, 658, 405, 3909, 2369, 1628, 1002, 3035, 1839, 1264, 778, 2181, 1322, 908, 559, 1677, 1016, 698, 430, 4158, 2520, 1732, 1066, 3289, 1994, 1370, 843, 2358, 1429, 982, 604, 1782, 1080, 742, 457, 4417, 2677, 1840, 1132, 3486, 2113, 1452, 894, 2473, 1499, 1030, 634, 1897, 1150, 790, 486, 4686, 2840, 1952, 1201, 3693, 2238, 1538, 947, 2670, 1618, 1112, 684, 2022, 1226, 842, 518, 4965, 3009, 2068, 1273, 3909, 2369, 1628, 1002, 2805, 1700, 1168, 719, 2157, 1307, 898, 553, 5253, 3183, 2188, 1347, 4134, 2506, 1722, 1060, 2949, 1787, 1228, 756, 2301, 1394, 958, 590, 5529, 3351, 2303, 1417, 4343, 2632, 1809, 1113, 3081, 1867, 1283, 790, 2361, 1431, 983, 605, 5836, 3537, 2431, 1496, 4588, 2780, 1911, 1176, 3244, 1966, 1351, 832, 2524, 1530, 1051, 647, 6153, 3729, 2563, 1577, 4775, 2894, 1989, 1224, 3417, 2071, 1423, 876, 2625, 1591, 1093, 673, 6479, 3927, 2699, 1661, 5039, 3054, 2099, 1292, 3599, 2181, 1499, 923, 2735, 1658, 1139, 701, 6743, 4087, 2809, 1729, 5313, 3220, 2213, 1362, 3791, 2298, 1579, 972, 2927, 1774, 1219, 750, 7089, 4296, 2953, 1817, 5596, 3391, 2331, 1435, 3993, 2420, 1663, 1024, 3057, 1852, 1273, 784 }; private static readonly int[] _capacityECCBaseValues = { 19, 7, 1, 19, 0, 0, 16, 10, 1, 16, 0, 0, 13, 13, 1, 13, 0, 0, 9, 17, 1, 9, 0, 0, 34, 10, 1, 34, 0, 0, 28, 16, 1, 28, 0, 0, 22, 22, 1, 22, 0, 0, 16, 28, 1, 16, 0, 0, 55, 15, 1, 55, 0, 0, 44, 26, 1, 44, 0, 0, 34, 18, 2, 17, 0, 0, 26, 22, 2, 13, 0, 0, 80, 20, 1, 80, 0, 0, 64, 18, 2, 32, 0, 0, 48, 26, 2, 24, 0, 0, 36, 16, 4, 9, 0, 0, 108, 26, 1, 108, 0, 0, 86, 24, 2, 43, 0, 0, 62, 18, 2, 15, 2, 16, 46, 22, 2, 11, 2, 12, 136, 18, 2, 68, 0, 0, 108, 16, 4, 27, 0, 0, 76, 24, 4, 19, 0, 0, 60, 28, 4, 15, 0, 0, 156, 20, 2, 78, 0, 0, 124, 18, 4, 31, 0, 0, 88, 18, 2, 14, 4, 15, 66, 26, 4, 13, 1, 14, 194, 24, 2, 97, 0, 0, 154, 22, 2, 38, 2, 39, 110, 22, 4, 18, 2, 19, 86, 26, 4, 14, 2, 15, 232, 30, 2, 116, 0, 0, 182, 22, 3, 36, 2, 37, 132, 20, 4, 16, 4, 17, 100, 24, 4, 12, 4, 13, 274, 18, 2, 68, 2, 69, 216, 26, 4, 43, 1, 44, 154, 24, 6, 19, 2, 20, 122, 28, 6, 15, 2, 16, 324, 20, 4, 81, 0, 0, 254, 30, 1, 50, 4, 51, 180, 28, 4, 22, 4, 23, 140, 24, 3, 12, 8, 13, 370, 24, 2, 92, 2, 93, 290, 22, 6, 36, 2, 37, 206, 26, 4, 20, 6, 21, 158, 28, 7, 14, 4, 15, 428, 26, 4, 107, 0, 0, 334, 22, 8, 37, 1, 38, 244, 24, 8, 20, 4, 21, 180, 22, 12, 11, 4, 12, 461, 30, 3, 115, 1, 116, 365, 24, 4, 40, 5, 41, 261, 20, 11, 16, 5, 17, 197, 24, 11, 12, 5, 13, 523, 22, 5, 87, 1, 88, 415, 24, 5, 41, 5, 42, 295, 30, 5, 24, 7, 25, 223, 24, 11, 12, 7, 13, 589, 24, 5, 98, 1, 99, 453, 28, 7, 45, 3, 46, 325, 24, 15, 19, 2, 20, 253, 30, 3, 15, 13, 16, 647, 28, 1, 107, 5, 108, 507, 28, 10, 46, 1, 47, 367, 28, 1, 22, 15, 23, 283, 28, 2, 14, 17, 15, 721, 30, 5, 120, 1, 121, 563, 26, 9, 43, 4, 44, 397, 28, 17, 22, 1, 23, 313, 28, 2, 14, 19, 15, 795, 28, 3, 113, 4, 114, 627, 26, 3, 44, 11, 45, 445, 26, 17, 21, 4, 22, 341, 26, 9, 13, 16, 14, 861, 28, 3, 107, 5, 108, 669, 26, 3, 41, 13, 42, 485, 30, 15, 24, 5, 25, 385, 28, 15, 15, 10, 16, 932, 28, 4, 116, 4, 117, 714, 26, 17, 42, 0, 0, 512, 28, 17, 22, 6, 23, 406, 30, 19, 16, 6, 17, 1006, 28, 2, 111, 7, 112, 782, 28, 17, 46, 0, 0, 568, 30, 7, 24, 16, 25, 442, 24, 34, 13, 0, 0, 1094, 30, 4, 121, 5, 122, 860, 28, 4, 47, 14, 48, 614, 30, 11, 24, 14, 25, 464, 30, 16, 15, 14, 16, 1174, 30, 6, 117, 4, 118, 914, 28, 6, 45, 14, 46, 664, 30, 11, 24, 16, 25, 514, 30, 30, 16, 2, 17, 1276, 26, 8, 106, 4, 107, 1000, 28, 8, 47, 13, 48, 718, 30, 7, 24, 22, 25, 538, 30, 22, 15, 13, 16, 1370, 28, 10, 114, 2, 115, 1062, 28, 19, 46, 4, 47, 754, 28, 28, 22, 6, 23, 596, 30, 33, 16, 4, 17, 1468, 30, 8, 122, 4, 123, 1128, 28, 22, 45, 3, 46, 808, 30, 8, 23, 26, 24, 628, 30, 12, 15, 28, 16, 1531, 30, 3, 117, 10, 118, 1193, 28, 3, 45, 23, 46, 871, 30, 4, 24, 31, 25, 661, 30, 11, 15, 31, 16, 1631, 30, 7, 116, 7, 117, 1267, 28, 21, 45, 7, 46, 911, 30, 1, 23, 37, 24, 701, 30, 19, 15, 26, 16, 1735, 30, 5, 115, 10, 116, 1373, 28, 19, 47, 10, 48, 985, 30, 15, 24, 25, 25, 745, 30, 23, 15, 25, 16, 1843, 30, 13, 115, 3, 116, 1455, 28, 2, 46, 29, 47, 1033, 30, 42, 24, 1, 25, 793, 30, 23, 15, 28, 16, 1955, 30, 17, 115, 0, 0, 1541, 28, 10, 46, 23, 47, 1115, 30, 10, 24, 35, 25, 845, 30, 19, 15, 35, 16, 2071, 30, 17, 115, 1, 116, 1631, 28, 14, 46, 21, 47, 1171, 30, 29, 24, 19, 25, 901, 30, 11, 15, 46, 16, 2191, 30, 13, 115, 6, 116, 1725, 28, 14, 46, 23, 47, 1231, 30, 44, 24, 7, 25, 961, 30, 59, 16, 1, 17, 2306, 30, 12, 121, 7, 122, 1812, 28, 12, 47, 26, 48, 1286, 30, 39, 24, 14, 25, 986, 30, 22, 15, 41, 16, 2434, 30, 6, 121, 14, 122, 1914, 28, 6, 47, 34, 48, 1354, 30, 46, 24, 10, 25, 1054, 30, 2, 15, 64, 16, 2566, 30, 17, 122, 4, 123, 1992, 28, 29, 46, 14, 47, 1426, 30, 49, 24, 10, 25, 1096, 30, 24, 15, 46, 16, 2702, 30, 4, 122, 18, 123, 2102, 28, 13, 46, 32, 47, 1502, 30, 48, 24, 14, 25, 1142, 30, 42, 15, 32, 16, 2812, 30, 20, 117, 4, 118, 2216, 28, 40, 47, 7, 48, 1582, 30, 43, 24, 22, 25, 1222, 30, 10, 15, 67, 16, 2956, 30, 19, 118, 6, 119, 2334, 28, 18, 47, 31, 48, 1666, 30, 34, 24, 34, 25, 1276, 30, 20, 15, 61, 16 }; + private static readonly int[] _remainderBits = { 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 }; - public static readonly int[] GaloisFieldByExponentAlpha = { 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1 }; - public static readonly int[] GaloisFieldByIntegerValue = { 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 }; - public static readonly int[] RemainderBits = { 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 }; + /// + /// A list containing detailed capacity information for each version of QR codes. + /// The index in the capacity table corresponds to one less than the version number. + /// + private static readonly List _capacityTable = CreateCapacityTable(_capacityBaseValues); + + /// + /// A table containing the error correction capacities and data codeword information for different combinations of QR code versions and error correction levels. + /// + private static readonly List _capacityECCTable = CreateCapacityECCTable(_capacityECCBaseValues); + + /// + /// Retrieves the error correction information for a specific QR code version and error correction level. + /// + /// The version of the QR code (1 to 40). + /// The desired error correction level (L, M, Q, or H). Do not supply . + /// + /// An object containing the total number of data codewords, ECC per block, + /// block group details, and other parameters required for encoding error correction data. + /// + public static ECCInfo GetCapacityEccInfo(int version, ECCLevel eccLevel) + => _capacityECCTable.Single(x => x.Version == version && x.ErrorCorrectionLevel == eccLevel); + + /// + /// Retrieves the capacity information for a specific QR code version. + /// The returned structure contains detailed data capacity values for each error correction level (L, M, Q, H) + /// and encoding mode (Numeric, Alphanumeric, Byte, Kanji), indicating the maximum number of characters + /// that can be stored in a QR code of the specified version under each configuration. + /// + /// The version of the QR code (1 to 40). + /// + /// A object containing data capacity details for all error correction levels + /// and encoding modes for the specified version. + /// + public static VersionInfo GetCapacityInfo(int version) + => _capacityTable[version - 1]; + + /// + /// Retrieves the number of remainder bits required for a specific QR code version. + /// Remainder bits are added to the final bit stream to ensure proper alignment with byte boundaries, + /// as required by the QR code specification. + /// + /// The version of the QR code (1 to 40). + /// + /// The number of remainder bits (0 to 7) that must be appended to the encoded bit stream. + /// + public static int GetRemainderBits(int version) + => _remainderBits[version - 1]; + + /// + /// Determines the minimum QR code version required to encode a given amount of data with a specific encoding mode and error correction level. + /// If no suitable version is found, it throws an exception indicating that the data length exceeds the maximum capacity for the given settings. + /// + /// The length of the data to be encoded. + /// The encoding mode (e.g., Numeric, Alphanumeric, Byte). + /// The error correction level (e.g., Low, Medium, Quartile, High). + /// The minimum version of the QR code that can accommodate the given data and settings. + /// + /// Thrown when the data length exceeds the maximum capacity for the specified encoding mode and error correction level. + /// + public static int GetVersion(int length, EncodingMode encMode, ECCLevel eccLevel) + { + // capacity table is already sorted by version number ascending, so the smallest version that can hold the data is the first one found + foreach (var x in _capacityTable) + { + // find the requested ECC level and encoding mode in the capacity table + foreach (var y in x.Details) + { + if (y.ErrorCorrectionLevel == eccLevel && y.CapacityDict[encMode] >= length) + { + // if the capacity of the current version is enough, return the version number + return x.Version; + } + } + } + + // if no version was found, throw an exception + var maxSizeByte = _capacityTable.Where( + x => x.Details.Any( + y => (y.ErrorCorrectionLevel == eccLevel)) + ).Max(x => x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel).CapacityDict[encMode]); + throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte); + } /// /// Generates a table containing the error correction capacities and data codeword information for different QR code versions and error correction levels. @@ -22,7 +106,7 @@ private static class Tables /// as well as how robust the QR code will be against distortions or obstructions. /// /// A list of ECCInfo structures, each representing the ECC data and capacities for different combinations of QR code versions and ECC levels. - public static List CreateCapacityECCTable() + private static List CreateCapacityECCTable(int[] capacityECCBaseValues) { var localCapacityECCTable = new List(160); for (var i = 0; i < (4 * 6 * 40); i += (4 * 6)) @@ -32,41 +116,41 @@ public static List CreateCapacityECCTable() new ECCInfo( version: (i + 24) / 24, errorCorrectionLevel: ECCLevel.L, - totalDataCodewords: _capacityECCBaseValues[i], - eccPerBlock: _capacityECCBaseValues[i + 1], - blocksInGroup1: _capacityECCBaseValues[i + 2], - codewordsInGroup1: _capacityECCBaseValues[i + 3], - blocksInGroup2: _capacityECCBaseValues[i + 4], - codewordsInGroup2: _capacityECCBaseValues[i + 5]), + totalDataCodewords: capacityECCBaseValues[i], + eccPerBlock: capacityECCBaseValues[i + 1], + blocksInGroup1: capacityECCBaseValues[i + 2], + codewordsInGroup1: capacityECCBaseValues[i + 3], + blocksInGroup2: capacityECCBaseValues[i + 4], + codewordsInGroup2: capacityECCBaseValues[i + 5]), new ECCInfo( version: (i + 24) / 24, errorCorrectionLevel: ECCLevel.M, - totalDataCodewords: _capacityECCBaseValues[i + 6], - eccPerBlock: _capacityECCBaseValues[i + 7], - blocksInGroup1: _capacityECCBaseValues[i + 8], - codewordsInGroup1: _capacityECCBaseValues[i + 9], - blocksInGroup2: _capacityECCBaseValues[i + 10], - codewordsInGroup2: _capacityECCBaseValues[i + 11] + totalDataCodewords: capacityECCBaseValues[i + 6], + eccPerBlock: capacityECCBaseValues[i + 7], + blocksInGroup1: capacityECCBaseValues[i + 8], + codewordsInGroup1: capacityECCBaseValues[i + 9], + blocksInGroup2: capacityECCBaseValues[i + 10], + codewordsInGroup2: capacityECCBaseValues[i + 11] ), new ECCInfo( version: (i + 24) / 24, errorCorrectionLevel: ECCLevel.Q, - totalDataCodewords: _capacityECCBaseValues[i + 12], - eccPerBlock: _capacityECCBaseValues[i + 13], - blocksInGroup1: _capacityECCBaseValues[i + 14], - codewordsInGroup1: _capacityECCBaseValues[i + 15], - blocksInGroup2: _capacityECCBaseValues[i + 16], - codewordsInGroup2: _capacityECCBaseValues[i + 17] + totalDataCodewords: capacityECCBaseValues[i + 12], + eccPerBlock: capacityECCBaseValues[i + 13], + blocksInGroup1: capacityECCBaseValues[i + 14], + codewordsInGroup1: capacityECCBaseValues[i + 15], + blocksInGroup2: capacityECCBaseValues[i + 16], + codewordsInGroup2: capacityECCBaseValues[i + 17] ), new ECCInfo( version: (i + 24) / 24, errorCorrectionLevel: ECCLevel.H, - totalDataCodewords: _capacityECCBaseValues[i + 18], - eccPerBlock: _capacityECCBaseValues[i + 19], - blocksInGroup1: _capacityECCBaseValues[i + 20], - codewordsInGroup1: _capacityECCBaseValues[i + 21], - blocksInGroup2: _capacityECCBaseValues[i + 22], - codewordsInGroup2: _capacityECCBaseValues[i + 23] + totalDataCodewords: capacityECCBaseValues[i + 18], + eccPerBlock: capacityECCBaseValues[i + 19], + blocksInGroup1: capacityECCBaseValues[i + 20], + codewordsInGroup1: capacityECCBaseValues[i + 21], + blocksInGroup2: capacityECCBaseValues[i + 22], + codewordsInGroup2: capacityECCBaseValues[i + 23] ) }); } @@ -79,7 +163,7 @@ public static List CreateCapacityECCTable() /// The capacity table is crucial for QR code generation, as it determines how much data each QR code version can store depending on the encoding mode and error correction level used. /// The index in the capacity table corresponds to one less than the version number. /// - public static List CreateCapacityTable() + private static List CreateCapacityTable(int[] capacityBaseValues) { var localCapacityTable = new List(40); for (var i = 0; i < (16 * 40); i += 16) @@ -91,37 +175,37 @@ public static List CreateCapacityTable() new VersionInfoDetails( ECCLevel.L, new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i + 1] }, - { EncodingMode.Byte, _capacityBaseValues[i + 2] }, - { EncodingMode.Kanji, _capacityBaseValues[i + 3] }, + { EncodingMode.Numeric, capacityBaseValues[i] }, + { EncodingMode.Alphanumeric, capacityBaseValues[i + 1] }, + { EncodingMode.Byte, capacityBaseValues[i + 2] }, + { EncodingMode.Kanji, capacityBaseValues[i + 3] }, } ), new VersionInfoDetails( ECCLevel.M, new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i + 4] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i + 5] }, - { EncodingMode.Byte, _capacityBaseValues[i + 6] }, - { EncodingMode.Kanji, _capacityBaseValues[i + 7] }, + { EncodingMode.Numeric, capacityBaseValues[i + 4] }, + { EncodingMode.Alphanumeric, capacityBaseValues[i + 5] }, + { EncodingMode.Byte, capacityBaseValues[i + 6] }, + { EncodingMode.Kanji, capacityBaseValues[i + 7] }, } ), new VersionInfoDetails( ECCLevel.Q, new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i + 8] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i + 9] }, - { EncodingMode.Byte, _capacityBaseValues[i + 10] }, - { EncodingMode.Kanji, _capacityBaseValues[i + 11] }, + { EncodingMode.Numeric, capacityBaseValues[i + 8] }, + { EncodingMode.Alphanumeric, capacityBaseValues[i + 9] }, + { EncodingMode.Byte, capacityBaseValues[i + 10] }, + { EncodingMode.Kanji, capacityBaseValues[i + 11] }, } ), new VersionInfoDetails( ECCLevel.H, new Dictionary(){ - { EncodingMode.Numeric, _capacityBaseValues[i + 12] }, - { EncodingMode.Alphanumeric, _capacityBaseValues[i + 13] }, - { EncodingMode.Byte, _capacityBaseValues[i + 14] }, - { EncodingMode.Kanji, _capacityBaseValues[i + 15] }, + { EncodingMode.Numeric, capacityBaseValues[i + 12] }, + { EncodingMode.Alphanumeric, capacityBaseValues[i + 13] }, + { EncodingMode.Byte, capacityBaseValues[i + 14] }, + { EncodingMode.Kanji, capacityBaseValues[i + 15] }, } ) } diff --git a/QRCoder/QRCodeGenerator/GaloisField.cs b/QRCoder/QRCodeGenerator/GaloisField.cs new file mode 100644 index 00000000..0a84dbeb --- /dev/null +++ b/QRCoder/QRCodeGenerator/GaloisField.cs @@ -0,0 +1,48 @@ +using System; + +namespace QRCoder; + +public partial class QRCodeGenerator +{ + /// + /// Represents a Galois field of 256 elements (GF(256)) used in finite field arithmetic, + /// typically for error correction algorithms such as Reed-Solomon. + /// + /// Provides mappings between exponential and integer representations of field elements + /// using a primitive element (α). The field is constructed with respect to a generator + /// polynomial and used for efficient encoding and decoding operations. + /// + /// + private static class GaloisField + { + private static readonly int[] _galoisFieldByExponentAlpha = { 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1 }; + private static readonly int[] _galoisFieldByIntegerValue = { 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 }; + + /// + /// Retrieves the integer value from the Galois field that corresponds to a given exponent. + /// This is used in Reed-Solomon and other error correction calculations involving Galois fields. + /// + public static int GetIntValFromAlphaExp(int exp) + => _galoisFieldByExponentAlpha[exp]; + + /// + /// Retrieves the exponent from the Galois field that corresponds to a given integer value. + /// Throws an exception if the integer value is zero, as zero does not have a logarithmic representation in the field. + /// + public static int GetAlphaExpFromIntVal(int intVal) + { + if (intVal == 0) + ThrowIntValOutOfRangeException(); // Zero is not valid as it does not have an exponent representation. + return _galoisFieldByIntegerValue[intVal]; + + void ThrowIntValOutOfRangeException() => throw new ArgumentOutOfRangeException(nameof(intVal), "The provided integer value is out of range, as zero is not representable."); + } + + /// + /// Normalizes a Galois field exponent to ensure it remains within the bounds of the field's size. + /// This is particularly necessary when performing multiplications in the field which can result in exponents exceeding the field's maximum. + /// + public static int ShrinkAlphaExp(int alphaExp) + => (int)((alphaExp % 256) + Math.Floor((double)(alphaExp / 256))); + } +} diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index fce7544a..b2ec4441 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -21,8 +21,8 @@ public void validate_antilogtable() var gen = new QRCodeGenerator(); var checkString = string.Empty; - var tablesType = Type.GetType("QRCoder.QRCodeGenerator+Tables, QRCoder"); - var gField = tablesType.GetField("GaloisFieldByExponentAlpha", BindingFlags.Public | BindingFlags.Static).GetValue(null).ShouldBeOfType(); + var tablesType = Type.GetType("QRCoder.QRCodeGenerator+GaloisField, QRCoder"); + var gField = tablesType.GetField("_galoisFieldByExponentAlpha", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null).ShouldBeOfType(); gField.Length.ShouldBe(256); for (int i = 0; i < gField.Length; i++) { @@ -30,7 +30,7 @@ public void validate_antilogtable() } checkString.ShouldBe("0,1,:1,2,:2,4,:3,8,:4,16,:5,32,:6,64,:7,128,:8,29,:9,58,:10,116,:11,232,:12,205,:13,135,:14,19,:15,38,:16,76,:17,152,:18,45,:19,90,:20,180,:21,117,:22,234,:23,201,:24,143,:25,3,:26,6,:27,12,:28,24,:29,48,:30,96,:31,192,:32,157,:33,39,:34,78,:35,156,:36,37,:37,74,:38,148,:39,53,:40,106,:41,212,:42,181,:43,119,:44,238,:45,193,:46,159,:47,35,:48,70,:49,140,:50,5,:51,10,:52,20,:53,40,:54,80,:55,160,:56,93,:57,186,:58,105,:59,210,:60,185,:61,111,:62,222,:63,161,:64,95,:65,190,:66,97,:67,194,:68,153,:69,47,:70,94,:71,188,:72,101,:73,202,:74,137,:75,15,:76,30,:77,60,:78,120,:79,240,:80,253,:81,231,:82,211,:83,187,:84,107,:85,214,:86,177,:87,127,:88,254,:89,225,:90,223,:91,163,:92,91,:93,182,:94,113,:95,226,:96,217,:97,175,:98,67,:99,134,:100,17,:101,34,:102,68,:103,136,:104,13,:105,26,:106,52,:107,104,:108,208,:109,189,:110,103,:111,206,:112,129,:113,31,:114,62,:115,124,:116,248,:117,237,:118,199,:119,147,:120,59,:121,118,:122,236,:123,197,:124,151,:125,51,:126,102,:127,204,:128,133,:129,23,:130,46,:131,92,:132,184,:133,109,:134,218,:135,169,:136,79,:137,158,:138,33,:139,66,:140,132,:141,21,:142,42,:143,84,:144,168,:145,77,:146,154,:147,41,:148,82,:149,164,:150,85,:151,170,:152,73,:153,146,:154,57,:155,114,:156,228,:157,213,:158,183,:159,115,:160,230,:161,209,:162,191,:163,99,:164,198,:165,145,:166,63,:167,126,:168,252,:169,229,:170,215,:171,179,:172,123,:173,246,:174,241,:175,255,:176,227,:177,219,:178,171,:179,75,:180,150,:181,49,:182,98,:183,196,:184,149,:185,55,:186,110,:187,220,:188,165,:189,87,:190,174,:191,65,:192,130,:193,25,:194,50,:195,100,:196,200,:197,141,:198,7,:199,14,:200,28,:201,56,:202,112,:203,224,:204,221,:205,167,:206,83,:207,166,:208,81,:209,162,:210,89,:211,178,:212,121,:213,242,:214,249,:215,239,:216,195,:217,155,:218,43,:219,86,:220,172,:221,69,:222,138,:223,9,:224,18,:225,36,:226,72,:227,144,:228,61,:229,122,:230,244,:231,245,:232,247,:233,243,:234,251,:235,235,:236,203,:237,139,:238,11,:239,22,:240,44,:241,88,:242,176,:243,125,:244,250,:245,233,:246,207,:247,131,:248,27,:249,54,:250,108,:251,216,:252,173,:253,71,:254,142,:255,1,:"); - var gField2 = tablesType.GetField("GaloisFieldByIntegerValue", BindingFlags.Public | BindingFlags.Static).GetValue(null).ShouldBeOfType(); + var gField2 = tablesType.GetField("_galoisFieldByIntegerValue", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null).ShouldBeOfType(); gField2.Length.ShouldBe(256); var checkString2 = string.Empty; for (int i = 0; i < gField2.Length; i++) From e6fc48fa8aff49944cb3eda94cf9dc9adfb06d76 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 12 Apr 2025 00:16:51 -0400 Subject: [PATCH 11/22] update --- QRCoder/QRCodeGenerator.cs | 8 ++++---- QRCoder/QRCodeGenerator/CapacityTables.cs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index 24a6a5d3..8dcf7928 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -110,7 +110,7 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo var codedText = PlainTextToBinary(plainText, encoding, eciMode, utf8BOM, forceUtf8); var dataInputLength = GetDataLength(encoding, plainText, codedText, forceUtf8); int version = requestedVersion; - int minVersion = CapacityTables.GetVersion(dataInputLength + (eciMode != EciMode.Default ? 2 : 0), encoding, eccLevel); + int minVersion = CapacityTables.CalculateMinimumVersion(dataInputLength + (eciMode != EciMode.Default ? 2 : 0), encoding, eccLevel); if (version == -1) { version = minVersion; @@ -120,7 +120,7 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo //Version was passed as fixed version via parameter. Thus let's check if chosen version is valid. if (minVersion > version) { - var maxSizeByte = CapacityTables.GetCapacityInfo(version).Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding]; + var maxSizeByte = CapacityTables.GetVersionInfo(version).Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding]; throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte); } } @@ -160,7 +160,7 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel) { eccLevel = ValidateECCLevel(eccLevel); - int version = CapacityTables.GetVersion(binaryData.Length, EncodingMode.Byte, eccLevel); + int version = CapacityTables.CalculateMinimumVersion(binaryData.Length, EncodingMode.Byte, eccLevel); int countIndicatorLen = GetCountIndicatorLength(version, EncodingMode.Byte); // Convert byte array to bit array, with prefix padding for mode indicator and count indicator @@ -200,7 +200,7 @@ private static ECCLevel ValidateECCLevel(ECCLevel eccLevel) /// A QRCodeData structure containing the full QR code matrix, which can be used for rendering or analysis. private static QRCodeData GenerateQrCode(BitArray bitArray, ECCLevel eccLevel, int version) { - var eccInfo = CapacityTables.GetCapacityEccInfo(version, eccLevel); + var eccInfo = CapacityTables.GetEccInfo(version, eccLevel); // Fill up data code word PadData(); diff --git a/QRCoder/QRCodeGenerator/CapacityTables.cs b/QRCoder/QRCodeGenerator/CapacityTables.cs index 393aa8f1..f1231105 100644 --- a/QRCoder/QRCodeGenerator/CapacityTables.cs +++ b/QRCoder/QRCodeGenerator/CapacityTables.cs @@ -36,7 +36,7 @@ private static class CapacityTables /// An object containing the total number of data codewords, ECC per block, /// block group details, and other parameters required for encoding error correction data. /// - public static ECCInfo GetCapacityEccInfo(int version, ECCLevel eccLevel) + public static ECCInfo GetEccInfo(int version, ECCLevel eccLevel) => _capacityECCTable.Single(x => x.Version == version && x.ErrorCorrectionLevel == eccLevel); /// @@ -50,7 +50,7 @@ public static ECCInfo GetCapacityEccInfo(int version, ECCLevel eccLevel) /// A object containing data capacity details for all error correction levels /// and encoding modes for the specified version. /// - public static VersionInfo GetCapacityInfo(int version) + public static VersionInfo GetVersionInfo(int version) => _capacityTable[version - 1]; /// @@ -76,7 +76,7 @@ public static int GetRemainderBits(int version) /// /// Thrown when the data length exceeds the maximum capacity for the specified encoding mode and error correction level. /// - public static int GetVersion(int length, EncodingMode encMode, ECCLevel eccLevel) + public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCLevel eccLevel) { // capacity table is already sorted by version number ascending, so the smallest version that can hold the data is the first one found foreach (var x in _capacityTable) From 4bd72272dfa3d4a18048fe190125bd6fa98e1532 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 12 Apr 2025 00:38:52 -0400 Subject: [PATCH 12/22] remove using --- QRCoder/QRCodeGenerator/CapacityTables.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/QRCoder/QRCodeGenerator/CapacityTables.cs b/QRCoder/QRCodeGenerator/CapacityTables.cs index f1231105..59f0d2c9 100644 --- a/QRCoder/QRCodeGenerator/CapacityTables.cs +++ b/QRCoder/QRCodeGenerator/CapacityTables.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; From cca83adb38b9032ae6e917b640ea54442ce30476 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 13 Apr 2025 01:26:14 -0400 Subject: [PATCH 13/22] Progress --- QRCoder/QRCodeData.cs | 6 +- QRCoder/QRCodeGenerator.cs | 161 +++++++++++++++- QRCoder/QRCodeGenerator/AlignmentPatterns.cs | 10 +- QRCoder/QRCodeGenerator/CapacityTables.cs | 176 +++++++++++++++++- QRCoder/QRCodeGenerator/ECCInfo.cs | 27 +++ .../ModulePlacer.MaskPattern.cs | 22 +++ QRCoder/QRCodeGenerator/ModulePlacer.cs | 130 ++++++++++--- QRCoderTests/QRGeneratorTests.cs | 21 +++ 8 files changed, 512 insertions(+), 41 deletions(-) diff --git a/QRCoder/QRCodeData.cs b/QRCoder/QRCodeData.cs index 0c27ff55..7fb8a68b 100644 --- a/QRCoder/QRCodeData.cs +++ b/QRCoder/QRCodeData.cs @@ -194,10 +194,12 @@ public void SaveRawData(string filePath, Compression compressMode) /// /// Gets the number of modules per side from the specified version. /// - /// The version of the QR code. + /// The version of the QR code (1 to 40, or -1 to -4 for Micro QR codes). /// Returns the number of modules per side. private static int ModulesPerSideFromVersion(int version) - => 21 + (version - 1) * 4; + => version > 0 + ? 21 + (version - 1) * 4 + : 11 + (-version - 1) * 2; /// /// Releases all resources used by the . diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index 8dcf7928..ac40d47d 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -150,6 +150,85 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo return GenerateQrCode(completeBitArray, eccLevel, version); } + /// + /// Calculates the Micro QR code data which than can be used in one of the rendering classes to generate a graphical representation. + /// + /// The payload which shall be encoded in the QR code + /// The level of error correction data + /// Set fixed Micro QR code target version; must be -1 to -4 representing M1 to M4, or 0 for default. + /// Thrown when the payload is too big to be encoded in a QR code. + /// Returns the raw QR code data which can be used for rendering. + public static QRCodeData GenerateMicroQrCode(string plainText, ECCLevel eccLevel = ECCLevel.Default, int requestedVersion = 0) + { + if (requestedVersion < -4 || requestedVersion > 0) + throw new ArgumentOutOfRangeException(nameof(requestedVersion), requestedVersion, "Requested version must be -1 to -4 representing M1 to M4, or 0 for default."); + ValidateECCLevel(eccLevel); + if (eccLevel == ECCLevel.H) + throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Micro QR codes does not support error correction level H."); + if (eccLevel == ECCLevel.Q && requestedVersion != -4) + throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Micro QR codes only supports error correction level Q for version M4."); + if (eccLevel != ECCLevel.Default && requestedVersion == -1) + throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Please specify ECCLevel.Default for version M1."); + if (plainText == null) + throw new ArgumentNullException(nameof(plainText)); + + var encoding = GetEncodingFromPlaintext(plainText, false); + var codedText = PlainTextToBinary(plainText, encoding, EciMode.Default, false, false); + var dataInputLength = GetDataLength(encoding, plainText, codedText, false); + int version = requestedVersion; + int minVersion = CapacityTables.CalculateMinimumMicroVersion(dataInputLength, encoding, eccLevel); + + if (version == 0) + { + version = minVersion; + } + else + { + //Version was passed as fixed version via parameter. Thus let's check if chosen version is valid. + if (minVersion < version) + { + var matchedEncoding = CapacityTables.GetVersionInfo(version).Details + .First(x => (x.ErrorCorrectionLevel == eccLevel || (eccLevel == ECCLevel.Default && x.ErrorCorrectionLevel == ECCLevel.L))) + .CapacityDict.TryGetValue(encoding, out var maxSizeByte); + if (!matchedEncoding) + throw new ArgumentOutOfRangeException(nameof(encoding), encoding, "Encoding not supported for this version."); + throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte); + } + } + if (version < -1 && eccLevel == ECCLevel.Default) + eccLevel = ECCLevel.L; + + var modeIndicatorLength = -version - 1; // 0 for M1, 1 for M2, 2 for M3, 3 for M4 + var countIndicatorLength = GetCountIndicatorLength(version, encoding); + var completeBitArrayLength = modeIndicatorLength + countIndicatorLength + codedText.Length; + + var completeBitArray = new BitArray(completeBitArrayLength); + + // write mode indicator + var completeBitArrayIndex = 0; + if (version < 0) + { + var encodingValue = + encoding == EncodingMode.Numeric ? 0 : + encoding == EncodingMode.Alphanumeric ? 1 : + encoding == EncodingMode.Byte ? 2 : 3; + completeBitArrayIndex = DecToBin(encodingValue, modeIndicatorLength, completeBitArray, completeBitArrayIndex); + } + else + { + completeBitArrayIndex = DecToBin((int)encoding, 4, completeBitArray, completeBitArrayIndex); + } + // write count indicator + completeBitArrayIndex = DecToBin(dataInputLength, countIndicatorLength, completeBitArray, completeBitArrayIndex); + // write data + for (int i = 0; i < codedText.Length; i++) + { + completeBitArray[completeBitArrayIndex++] = codedText[i]; + } + + return GenerateQrCode(completeBitArray, eccLevel, version); + } + /// /// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation. /// @@ -325,14 +404,14 @@ QRCodeData PlaceModules() using (var blockedModules = new ModulePlacer.BlockedModules(size)) { ModulePlacer.PlaceFinderPatterns(qr, blockedModules); - ModulePlacer.ReserveSeperatorAreas(size, blockedModules); + ModulePlacer.ReserveSeperatorAreas(version, size, blockedModules); ModulePlacer.PlaceAlignmentPatterns(qr, AlignmentPatterns.FromVersion(version).PatternPositions, blockedModules); ModulePlacer.PlaceTimingPatterns(qr, blockedModules); ModulePlacer.PlaceDarkModule(qr, version, blockedModules); ModulePlacer.ReserveVersionAreas(size, version, blockedModules); ModulePlacer.PlaceDataWords(qr, interleavedData, blockedModules); var maskVersion = ModulePlacer.MaskCode(qr, version, blockedModules, eccLevel); - GetFormatString(tempBitArray, eccLevel, maskVersion); + GetFormatString(tempBitArray, version, eccLevel, maskVersion); ModulePlacer.PlaceFormat(qr, tempBitArray, true); } @@ -352,15 +431,19 @@ QRCodeData PlaceModules() /// Generates a BitArray containing the format string for a QR code based on the error correction level and mask pattern version. /// The format string includes the error correction level, mask pattern version, and error correction coding. /// - /// The to write to, or null to create a new one. + /// The to write to, or null to create a new one. + /// The version number of the QR Code (1-40, or -1 to -4 for Micro QR codes). /// The error correction level to be encoded in the format string. /// The mask pattern version to be encoded in the format string. /// A BitArray containing the 15-bit format string used in QR code generation. - private static void GetFormatString(BitArray fStrEcc, ECCLevel level, int maskVersion) + private static void GetFormatString(BitArray fStrEcc, int version, ECCLevel level, int maskVersion) { fStrEcc.Length = 15; fStrEcc.SetAll(false); - WriteEccLevelAndVersion(); + if (version < 0) + WriteMicroEccLevelAndVersion(); + else + WriteEccLevelAndVersion(); // Apply the format generator polynomial to add error correction to the format string. int index = 0; @@ -405,6 +488,46 @@ void WriteEccLevelAndVersion() // Insert the 3-bit mask version directly after the error correction level bits. DecToBin(maskVersion, 3, fStrEcc, 2); } + + void WriteMicroEccLevelAndVersion() + { + switch (version) + { + case -1: // M1 + break; + case -2: // M2 + fStrEcc[level == ECCLevel.L ? 2 : 1] = true; // 001 for L and 010 for M + break; + case -3: // M3 + if (level == ECCLevel.L) + { + fStrEcc[1] = true; // 011 for L + fStrEcc[2] = true; + } + else + fStrEcc[0] = true; // 100 for M + break; + default: // M4 + fStrEcc[0] = true; + if (level == ECCLevel.L) // 101 for L + fStrEcc[2] = true; + else if (level == ECCLevel.M) // 110 for M + fStrEcc[1] = true; + else // 111 for Q + { + fStrEcc[1] = true; + fStrEcc[2] = true; + } + break; + } + + // Insert the 2-bit mask version directly after the version / error correction level bits. + var microMaskVersion = + maskVersion == 1 ? 0 : + maskVersion == 4 ? 1 : + maskVersion == 6 ? 2 : 3; + DecToBin(microMaskVersion, 2, fStrEcc, 3); + } } #if !NETFRAMEWORK || NET45_OR_GREATER @@ -670,7 +793,33 @@ private static int DecToBin(int decNum, int bits, BitArray bitList, int index) private static int GetCountIndicatorLength(int version, EncodingMode encMode) { // Different versions and encoding modes require different lengths of bits to represent the character count efficiently - if (version < 10) + if (version == -1) + { + return 3; + } + else if (version == -2) + { + return encMode == EncodingMode.Numeric ? 4 : 3; + } + else if (version == -3) + { + if (encMode == EncodingMode.Numeric) + return 5; + else if (encMode == EncodingMode.Kanji) + return 3; + else + return 4; + } + else if (version == -4) + { + if (encMode == EncodingMode.Numeric) + return 6; + else if (encMode == EncodingMode.Kanji) + return 4; + else + return 5; + } + else if (version < 10) { if (encMode == EncodingMode.Numeric) return 10; diff --git a/QRCoder/QRCodeGenerator/AlignmentPatterns.cs b/QRCoder/QRCodeGenerator/AlignmentPatterns.cs index fe879623..55db9cc6 100644 --- a/QRCoder/QRCodeGenerator/AlignmentPatterns.cs +++ b/QRCoder/QRCodeGenerator/AlignmentPatterns.cs @@ -28,7 +28,7 @@ private static class AlignmentPatterns private static Dictionary CreateAlignmentPatternTable() { var alignmentPatternBaseValues = new int[] { 0, 0, 0, 0, 0, 0, 0, 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 }; - var localAlignmentPatternTable = new Dictionary(40); + var localAlignmentPatternTable = new Dictionary(40 + 8); for (var i = 0; i < (7 * 40); i += 7) { @@ -56,6 +56,14 @@ private static Dictionary CreateAlignmentPatternTable() PatternPositions = points }); } + + // Micro QR codes do not have alignment patterns. + var emptyPointList = new List(); + localAlignmentPatternTable.Add(-1, new AlignmentPattern { Version = -1, PatternPositions = emptyPointList }); + localAlignmentPatternTable.Add(-2, new AlignmentPattern { Version = -2, PatternPositions = emptyPointList }); + localAlignmentPatternTable.Add(-3, new AlignmentPattern { Version = -3, PatternPositions = emptyPointList }); + localAlignmentPatternTable.Add(-4, new AlignmentPattern { Version = -4, PatternPositions = emptyPointList }); + return localAlignmentPatternTable; } } diff --git a/QRCoder/QRCodeGenerator/CapacityTables.cs b/QRCoder/QRCodeGenerator/CapacityTables.cs index 59f0d2c9..f190d588 100644 --- a/QRCoder/QRCodeGenerator/CapacityTables.cs +++ b/QRCoder/QRCodeGenerator/CapacityTables.cs @@ -20,6 +20,7 @@ private static class CapacityTables /// The index in the capacity table corresponds to one less than the version number. /// private static readonly List _capacityTable = CreateCapacityTable(_capacityBaseValues); + private static readonly List _microCapacityTable = CreateMicroCapacityTable(); /// /// A table containing the error correction capacities and data codeword information for different combinations of QR code versions and error correction levels. @@ -29,7 +30,7 @@ private static class CapacityTables /// /// Retrieves the error correction information for a specific QR code version and error correction level. /// - /// The version of the QR code (1 to 40). + /// The version of the QR code (1 to 40, or -1 to -4 for M1 to M4). /// The desired error correction level (L, M, Q, or H). Do not supply . /// /// An object containing the total number of data codewords, ECC per block, @@ -44,25 +45,25 @@ public static ECCInfo GetEccInfo(int version, ECCLevel eccLevel) /// and encoding mode (Numeric, Alphanumeric, Byte, Kanji), indicating the maximum number of characters /// that can be stored in a QR code of the specified version under each configuration. /// - /// The version of the QR code (1 to 40). + /// The version of the QR code (1 to 40, or -1 to -4 for M1 to M4). /// /// A object containing data capacity details for all error correction levels /// and encoding modes for the specified version. /// public static VersionInfo GetVersionInfo(int version) - => _capacityTable[version - 1]; + => version < 0 ? _microCapacityTable[-version - 1] : _capacityTable[version - 1]; /// /// Retrieves the number of remainder bits required for a specific QR code version. /// Remainder bits are added to the final bit stream to ensure proper alignment with byte boundaries, /// as required by the QR code specification. /// - /// The version of the QR code (1 to 40). + /// The version of the QR code (1 to 40, or -1 to -4 for M1 to M4). /// /// The number of remainder bits (0 to 7) that must be appended to the encoded bit stream. /// public static int GetRemainderBits(int version) - => _remainderBits[version - 1]; + => version < 0 ? 0 : _remainderBits[version - 1]; /// /// Determines the minimum QR code version required to encode a given amount of data with a specific encoding mode and error correction level. @@ -77,6 +78,7 @@ public static int GetRemainderBits(int version) /// public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCLevel eccLevel) { + // only iterates through non-micro QR codes // capacity table is already sorted by version number ascending, so the smallest version that can hold the data is the first one found foreach (var x in _capacityTable) { @@ -99,6 +101,50 @@ public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCL throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte); } + + /// + /// Determines the minimum Micro QR code version required to encode a given amount of data with a specific encoding mode and error correction level. + /// If no suitable version is found, it throws an exception indicating that the data length exceeds the maximum capacity for the given settings. + /// + /// The length of the data to be encoded. + /// The encoding mode (e.g., Numeric, Alphanumeric, Byte). + /// The error correction level (e.g., Default, Low, Medium, Quartile, High). + /// The minimum version of the QR code (-1 to -4) that can accommodate the given data and settings. + /// + /// Thrown when the data length exceeds the maximum capacity for the specified encoding mode and error correction level. + /// + public static int CalculateMinimumMicroVersion(int length, EncodingMode encMode, ECCLevel eccLevel) + { + // only iterates through non-micro QR codes + // capacity table is already sorted by version number ascending, so the smallest version that can hold the data is the first one found + foreach (var x in _microCapacityTable) + { + // find the requested ECC level and encoding mode in the capacity table + foreach (var y in x.Details) + { + // Use ECC level L for Micro QR Code versions 2, 3 and 4 when Default is specified + if (y.ErrorCorrectionLevel == eccLevel || (eccLevel == ECCLevel.Default && y.ErrorCorrectionLevel == ECCLevel.L)) + { + // Not all versions support all encoding modes, so check if the encoding mode is supported + if (y.CapacityDict.TryGetValue(encMode, out int maxLength) && maxLength >= length) + { + // if the capacity of the current version is enough, return the version number + return x.Version; + } + } + } + } + + // if no version was found, throw an exception + var maxSizeByte = _microCapacityTable + .SelectMany(x => x.Details) + .Where(y => (y.ErrorCorrectionLevel == eccLevel || (eccLevel == ECCLevel.Default && y.ErrorCorrectionLevel == ECCLevel.L)) + && y.CapacityDict.ContainsKey(encMode)) + .Max(y => y.CapacityDict[encMode]); + + throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte); + } + /// /// Generates a table containing the error correction capacities and data codeword information for different QR code versions and error correction levels. /// This table is essential for determining how much data can be encoded in a QR code of a specific version and ECC level, @@ -107,7 +153,7 @@ public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCL /// A list of ECCInfo structures, each representing the ECC data and capacities for different combinations of QR code versions and ECC levels. private static List CreateCapacityECCTable(int[] capacityECCBaseValues) { - var localCapacityECCTable = new List(160); + var localCapacityECCTable = new List(160 + 8); for (var i = 0; i < (4 * 6 * 40); i += (4 * 6)) { localCapacityECCTable.AddRange(new[] @@ -153,6 +199,31 @@ private static List CreateCapacityECCTable(int[] capacityECCBaseValues) ) }); } + + localCapacityECCTable.AddRange(new ECCInfo[] + { + // Micro QR Code Version M1 - only supports ECCLevel.Default (none) + new ECCInfo( + version: -1, + errorCorrectionLevel: ECCLevel.Default, + totalDataCodewords: 3, + totalDataBits: 20, + eccPerBlock: 0), + + // Micro QR Code Version M2 + new ECCInfo(-2, ECCLevel.L, 5, 40, 5), + new ECCInfo(-2, ECCLevel.M, 4, 32, 6), + + // Micro QR Code Version M3 + new ECCInfo(-3, ECCLevel.L, 11, 84, 6), + new ECCInfo(-3, ECCLevel.M, 9, 68, 8), + + // Micro QR Code Version M4 + new ECCInfo(-4, ECCLevel.L, 16, 128, 8), + new ECCInfo(-4, ECCLevel.M, 14, 112, 10), + new ECCInfo(-4, ECCLevel.Q, 10, 80, 14), + }); + return localCapacityECCTable; } @@ -212,5 +283,98 @@ private static List CreateCapacityTable(int[] capacityBaseValues) } return localCapacityTable; } + + /// + private static List CreateMicroCapacityTable() + { + var tbl = new List(4); + + var m1details = new List(1) + { + new VersionInfoDetails( + ECCLevel.Default, // none + new Dictionary(1) { + { EncodingMode.Numeric, 5 }, + } + ) + }; + tbl.Add(new VersionInfo(-1, m1details)); + + var m2details = new List(2) + { + new VersionInfoDetails( + ECCLevel.L, + new Dictionary(2) { + { EncodingMode.Numeric, 10 }, + { EncodingMode.Alphanumeric, 6 }, + } + ), + new VersionInfoDetails( + ECCLevel.M, + new Dictionary(2) { + { EncodingMode.Numeric, 8 }, + { EncodingMode.Alphanumeric, 5 }, + } + ), + }; + tbl.Add(new VersionInfo(-2, m2details)); + + var m3details = new List(2) + { + new VersionInfoDetails( + ECCLevel.L, + new Dictionary(4) { + { EncodingMode.Numeric, 23 }, + { EncodingMode.Alphanumeric, 14 }, + { EncodingMode.Byte, 9 }, + { EncodingMode.Kanji, 6 }, + } + ), + new VersionInfoDetails( + ECCLevel.M, + new Dictionary(4) { + { EncodingMode.Numeric, 18 }, + { EncodingMode.Alphanumeric, 11 }, + { EncodingMode.Byte, 7 }, + { EncodingMode.Kanji, 4 }, + } + ), + }; + tbl.Add(new VersionInfo(-3, m3details)); + + var m4details = new List(3) + { + new VersionInfoDetails( + ECCLevel.L, + new Dictionary(4) { + { EncodingMode.Numeric, 35 }, + { EncodingMode.Alphanumeric, 21 }, + { EncodingMode.Byte, 15 }, + { EncodingMode.Kanji, 9 }, + } + ), + new VersionInfoDetails( + ECCLevel.M, + new Dictionary(4) { + { EncodingMode.Numeric, 30 }, + { EncodingMode.Alphanumeric, 18 }, + { EncodingMode.Byte, 13 }, + { EncodingMode.Kanji, 8 }, + } + ), + new VersionInfoDetails( + ECCLevel.Q, + new Dictionary(4) { + { EncodingMode.Numeric, 21 }, + { EncodingMode.Alphanumeric, 13 }, + { EncodingMode.Byte, 9 }, + { EncodingMode.Kanji, 5 }, + } + ), + }; + tbl.Add(new VersionInfo(-4, m4details)); + + return tbl; + } } } diff --git a/QRCoder/QRCodeGenerator/ECCInfo.cs b/QRCoder/QRCodeGenerator/ECCInfo.cs index 919288ca..9e8b787e 100644 --- a/QRCoder/QRCodeGenerator/ECCInfo.cs +++ b/QRCoder/QRCodeGenerator/ECCInfo.cs @@ -24,6 +24,7 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword Version = version; ErrorCorrectionLevel = errorCorrectionLevel; TotalDataCodewords = totalDataCodewords; + TotalDataBits = totalDataCodewords * 8; ECCPerBlock = eccPerBlock; BlocksInGroup1 = blocksInGroup1; CodewordsInGroup1 = codewordsInGroup1; @@ -31,6 +32,27 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword CodewordsInGroup2 = codewordsInGroup2; } + /// + /// Initializes a new instance of the ECCInfo struct with specified properties for Micro QR codes. + /// + /// The version number of the QR code. + /// The error correction level used in the QR code. + /// The total number of data codewords for this version and error correction level. + /// The total number of data bits for this version and error correction level. + /// The number of error correction codewords per block. + public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int totalDataBits, int eccPerBlock) + { + Version = version; + ErrorCorrectionLevel = errorCorrectionLevel; + TotalDataCodewords = totalDataCodewords; + TotalDataBits = totalDataBits; + ECCPerBlock = eccPerBlock; + BlocksInGroup1 = 1; + CodewordsInGroup1 = totalDataCodewords; + BlocksInGroup2 = 0; + CodewordsInGroup2 = 0; + } + /// /// Gets the version number of the QR code. /// @@ -46,6 +68,11 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword /// public int TotalDataCodewords { get; } + /// + /// Gets the total number of data codewords for this version and error correction level. + /// + public int TotalDataBits { get; } + /// /// Gets the number of error correction codewords per block. /// diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs b/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs index 4b46dac6..1d5b80e8 100644 --- a/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs +++ b/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs @@ -78,6 +78,28 @@ public static bool Pattern7(int x, int y) public static bool Pattern8(int x, int y) => (((x + y) % 2) + ((x * y) % 3)) % 2 == 0; + /// + /// Calculates a penalty score for a Micro QR code to evaluate the effectiveness of a mask pattern. + /// A lower score indicates a QR code that is easier for decoders to read accurately. + /// + /// The QR code data structure to be evaluated. + /// The total penalty score of the QR code. + public static int ScoreMicro(QRCodeData qrCode) + { + var size = qrCode.ModuleMatrix.Count; + int sum1 = 0; + int sum2 = 0; + for (var i = 1; i < size; i++) + { + if (qrCode.ModuleMatrix[size - 1][i]) + sum1++; + if (qrCode.ModuleMatrix[i][size - 1]) + sum2++; + } + int total = sum1 < sum2 ? sum1 * 16 + sum2 : sum2 * 16 + sum1; + return -total; // negate so that lower is better + } + /// /// Calculates a penalty score for a QR code to evaluate the effectiveness of a mask pattern. /// A lower score indicates a QR code that is easier for decoders to read accurately. diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.cs b/QRCoder/QRCodeGenerator/ModulePlacer.cs index f452e6b3..381843e4 100644 --- a/QRCoder/QRCodeGenerator/ModulePlacer.cs +++ b/QRCoder/QRCodeGenerator/ModulePlacer.cs @@ -35,11 +35,15 @@ public static void PlaceVersion(QRCodeData qrCode, BitArray versionStr, bool off /// /// The QR code data structure to modify. /// The bit array containing the format information. + /// Specifies whether an offset should be applied. public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr, bool offset) { + var isMicro = qrCode.Version < 0; // Negative versions indicate Micro QR codes. var offsetValue = offset ? 4 : 0; var size = qrCode.ModuleMatrix.Count - offsetValue - offsetValue; + // Standard QR Code Format Positions: + // // { x1, y1, x2, y2 } i // =============================== // { 8, 0, size - 1, 8 }, // 0 @@ -58,16 +62,50 @@ public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr, bool offse // { 1, 8, 8, size - 2 }, // 13 // { 0, 8, 8, size - 1 } }; // 14 + // Micro QR Code Format Positions: + // + // i { x1, y1 } + // =============== + // 0 { 1, 8 } + // 1 { 2, 8 } + // 2 { 3, 8 } + // 3 { 4, 8 } + // 4 { 5, 8 } + // 5 { 6, 8 } + // 6 { 7, 8 } + // 7 { 8, 8 } + // 8 { 8, 7 } + // 9 { 8, 6 } + // 10 { 8, 5 } + // 11 { 8, 4 } + // 12 { 8, 3 } + // 13 { 8, 2 } + // 14 { 8, 1 } + for (var i = 0; i < 15; i++) { - // values computed to follow table above - var x1 = i < 8 ? 8 : i == 8 ? 7 : 14 - i; - var y1 = i < 6 ? i : i < 7 ? i + 1 : 8; - var x2 = i < 8 ? size - 1 - i : 8; - var y2 = i < 8 ? 8 : size - (15 - i); - - qrCode.ModuleMatrix[y1 + offsetValue][x1 + offsetValue] = formatStr[14 - i]; - qrCode.ModuleMatrix[y2 + offsetValue][x2 + offsetValue] = formatStr[14 - i]; + int x1, y1, x2, y2; + + if (isMicro) + { + // Micro QR format positions + x1 = i < 8 ? i + 1 : 8; + y1 = i < 8 ? 8 : 15 - i; + + // Micro QR only uses one set of format positions, no duplication. + qrCode.ModuleMatrix[y1 + offsetValue][x1 + offsetValue] = formatStr[14 - i]; + } + else + { + // Standard QR format positions + x1 = i < 8 ? 8 : i == 8 ? 7 : 14 - i; + y1 = i < 6 ? i : i < 7 ? i + 1 : 8; + x2 = i < 8 ? size - 1 - i : 8; + y2 = i < 8 ? 8 : size - (15 - i); + + qrCode.ModuleMatrix[y1 + offsetValue][x1 + offsetValue] = formatStr[14 - i]; + qrCode.ModuleMatrix[y2 + offsetValue][x2 + offsetValue] = formatStr[14 - i]; + } } } @@ -98,6 +136,9 @@ public static int MaskCode(QRCodeData qrCode, int version, BlockedModules blocke var formatStr = new BitArray(15); for (var maskPattern = 0; maskPattern < 8; maskPattern++) { + if (version < 0 && (maskPattern == 0 || maskPattern == 2 || maskPattern == 3 || maskPattern == 5)) + continue; // Micro QR codes only support certain mask patterns. + var patternFunc = MaskPattern.Patterns[maskPattern]; // Reset the temporary QR code to the current state of the actual QR code. @@ -110,7 +151,7 @@ public static int MaskCode(QRCodeData qrCode, int version, BlockedModules blocke } // Place format information using the current mask pattern. - GetFormatString(formatStr, eccLevel, maskPattern); + GetFormatString(formatStr, version, eccLevel, maskPattern); ModulePlacer.PlaceFormat(qrTemp, formatStr, false); // Place version information if applicable. @@ -137,7 +178,7 @@ public static int MaskCode(QRCodeData qrCode, int version, BlockedModules blocke } } - var score = MaskPattern.Score(qrTemp); + var score = version < 0 ? MaskPattern.ScoreMicro(qrTemp) : MaskPattern.Score(qrTemp); // Select the pattern with the lowest score, indicating better QR code readability. if (patternScore > score) @@ -224,15 +265,21 @@ public static void PlaceDataWords(QRCodeData qrCode, BitArray data, BlockedModul /// /// The size of the QR code matrix. /// A list of rectangles representing areas that must not be overwritten. - public static void ReserveSeperatorAreas(int size, BlockedModules blockedModules) + public static void ReserveSeperatorAreas(int version, int size, BlockedModules blockedModules) { - // Block areas around the finder patterns, which are located near three corners of the QR code. + // Block areas around the top-left finder pattern blockedModules.Add(new Rectangle(7, 0, 1, 8)); // Vertical block near the top left finder pattern blockedModules.Add(new Rectangle(0, 7, 7, 1)); // Horizontal block near the top left finder pattern - blockedModules.Add(new Rectangle(0, size - 8, 8, 1)); // Horizontal block near the bottom left finder pattern - blockedModules.Add(new Rectangle(7, size - 7, 1, 7)); // Vertical block near the bottom left finder pattern - blockedModules.Add(new Rectangle(size - 8, 0, 1, 8)); // Vertical block near the top right finder pattern - blockedModules.Add(new Rectangle(size - 7, 7, 7, 1)); // Horizontal block near the top right finder pattern + + if (version > 0) // Non-micro QR codes have 3 finder patterns + { + // Block areas around the bottom-left finder pattern + blockedModules.Add(new Rectangle(0, size - 8, 8, 1)); // Horizontal block near the bottom left finder pattern + blockedModules.Add(new Rectangle(7, size - 7, 1, 7)); // Vertical block near the bottom left finder pattern + // Block areas around the top-right finder pattern + blockedModules.Add(new Rectangle(size - 8, 0, 1, 8)); // Vertical block near the top right finder pattern + blockedModules.Add(new Rectangle(size - 7, 7, 7, 1)); // Horizontal block near the top right finder pattern + } } /// @@ -243,6 +290,13 @@ public static void ReserveSeperatorAreas(int size, BlockedModules blockedModules /// A list of rectangles representing areas that must not be overwritten. public static void ReserveVersionAreas(int size, int version, BlockedModules blockedModules) { + if (version < 0) // Micro QR codes + { + blockedModules.Add(new Rectangle(0, 8, 8, 1)); + blockedModules.Add(new Rectangle(8, 0, 1, 8)); + return; + } + // Reserve areas near the timing patterns for version and format information. blockedModules.Add(new Rectangle(8, 0, 1, 6)); // Near the top timing pattern blockedModules.Add(new Rectangle(8, 7, 1, 1)); // Small square near the top left finder pattern @@ -267,6 +321,9 @@ public static void ReserveVersionAreas(int size, int version, BlockedModules blo /// A list of rectangles representing areas that must not be overwritten, updated to include the dark module. public static void PlaceDarkModule(QRCodeData qrCode, int version, BlockedModules blockedModules) { + // Micro QR codes do not have a dark module + if (version < 0) + return; // Place the dark module, which is always required to be black. qrCode.ModuleMatrix[4 * version + 9 + 4][8 + 4] = true; // Block the dark module area to prevent overwriting during further QR code generation steps. @@ -283,7 +340,8 @@ public static void PlaceFinderPatterns(QRCodeData qrCode, BlockedModules blocked var size = qrCode.ModuleMatrix.Count - 8; // Loop to place three finder patterns in the top-left, top-right, and bottom-left corners of the QR code. - for (var i = 0; i < 3; i++) + var count = qrCode.Version < 0 ? 1 : 3; // Micro QR codes have only one finder pattern. + for (var i = 0; i < count; i++) { // Calculate the x and y starting positions for each finder pattern based on the index. var locationX = i == 1 ? size - 7 : 0; // Place at top-right if i is 1, otherwise at left side (top or bottom). @@ -355,21 +413,41 @@ public static void PlaceAlignmentPatterns(QRCodeData qrCode, List alignme /// A list of rectangles representing areas that must not be overwritten. Updated with the areas occupied by timing patterns. public static void PlaceTimingPatterns(QRCodeData qrCode, BlockedModules blockedModules) { - var size = qrCode.ModuleMatrix.Count - 8; // Get the size of the QR code matrix. + // Get the size of the QR code matrix excluding padding. + var size = qrCode.ModuleMatrix.Count - 8; - // Place timing patterns starting from the 8th module to the size - 8 to avoid overlapping with finder patterns. - for (var i = 8; i < size - 8; i++) + if (qrCode.Version > 0) { - if (i % 2 == 0) // Place a dark module every other module to create the alternating pattern. + // Place timing patterns starting from the 8th module to the size - 8 to avoid overlapping with finder patterns. + for (var i = 8; i < size - 8; i++) { - qrCode.ModuleMatrix[6 + 4][i + 4] = true; // Horizontal timing pattern - qrCode.ModuleMatrix[i + 4][6 + 4] = true; // Vertical timing pattern + if (i % 2 == 0) // Place a dark module every other module to create the alternating pattern. + { + qrCode.ModuleMatrix[6 + 4][i + 4] = true; // Horizontal timing pattern + qrCode.ModuleMatrix[i + 4][6 + 4] = true; // Vertical timing pattern + } } + + // Add the areas occupied by the timing patterns to the list of blocked modules. + blockedModules.Add(new Rectangle(6, 8, 1, size - 16)); // Horizontal timing pattern area + blockedModules.Add(new Rectangle(8, 6, size - 16, 1)); // Vertical timing pattern area } + else // Micro QR codes + { + // Place timing patterns starting from the 8th module to avoid overlapping with finder patterns. + for (var i = 8; i < size; i++) + { + if (i % 2 == 0) // Place a dark module every other module to create the alternating pattern. + { + qrCode.ModuleMatrix[4][i + 4] = true; // Horizontal timing pattern + qrCode.ModuleMatrix[i + 4][4] = true; // Vertical timing pattern + } + } - // Add the areas occupied by the timing patterns to the list of blocked modules. - blockedModules.Add(new Rectangle(6, 8, 1, size - 16)); // Horizontal timing pattern area - blockedModules.Add(new Rectangle(8, 6, size - 16, 1)); // Vertical timing pattern area + // Add the areas occupied by the timing patterns to the list of blocked modules. + blockedModules.Add(new Rectangle(0, 8, 1, size - 8)); // Horizontal timing pattern area + blockedModules.Add(new Rectangle(8, 0, size - 8, 1)); // Vertical timing pattern area + } } } } diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index b2ec4441..57117ec0 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; @@ -15,6 +16,26 @@ namespace QRCoderTests; public class QRGeneratorTests { + [Fact] + public void micro() + { + var input = "1"; + var expectedSize = 11; + + var qrData = QRCodeGenerator.GenerateMicroQrCode(input); + (qrData.ModuleMatrix.Count - 8).ShouldBe(expectedSize); // exclude padding + var encoder = new AsciiQRCode(qrData); + var txt = encoder.GetGraphicSmall(false, true, Environment.NewLine); + Debug.WriteLine(txt); + txt = encoder.GetGraphic(1, drawQuietZones: false, endOfLine: Environment.NewLine); + Debug.WriteLine(txt); + + //var result = string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray()); + //var hash = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(result)); + //var hashString = Convert.ToBase64String(hash); + //hashString.TrimEnd('=').ShouldBe(expectedHash); + } + [Fact] public void validate_antilogtable() { From e71760ba5ac433a0bedb147c6a377fab20057a83 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 13 Apr 2025 12:29:14 -0400 Subject: [PATCH 14/22] update format information writing for micro codes --- QRCoder/QRCodeGenerator.cs | 9 +++++++-- QRCoderTests/QRGeneratorTests.cs | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index ac40d47d..fa2ff7a4 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -427,6 +427,8 @@ QRCodeData PlaceModules() private static readonly BitArray _getFormatGenerator = new BitArray(new bool[] { true, false, true, false, false, true, true, false, true, true, true }); private static readonly BitArray _getFormatMask = new BitArray(new bool[] { true, false, true, false, true, false, false, false, false, false, true, false, false, true, false }); + private static readonly BitArray _getFormatMicroMask = new BitArray(new bool[] { true, false, false, false, true, false, false, false, true, false, false, false, true, false, true }); + /// /// Generates a BitArray containing the format string for a QR code based on the error correction level and mask pattern version. /// The format string includes the error correction level, mask pattern version, and error correction coding. @@ -462,10 +464,13 @@ private static void GetFormatString(BitArray fStrEcc, int version, ECCLevel leve // Prefix the error correction bits with the ECC level and version number. fStrEcc.Length = 10 + 5; ShiftAwayFromBit0(fStrEcc, (10 - count) + 5); - WriteEccLevelAndVersion(); + if (version < 0) + WriteMicroEccLevelAndVersion(); + else + WriteEccLevelAndVersion(); // XOR the format string with a predefined mask to add robustness against errors. - fStrEcc.Xor(_getFormatMask); + fStrEcc.Xor(version < 0 ? _getFormatMicroMask : _getFormatMask); void WriteEccLevelAndVersion() { diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 57117ec0..485b8d99 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -19,10 +19,10 @@ public class QRGeneratorTests [Fact] public void micro() { - var input = "1"; - var expectedSize = 11; + var input = "000000"; + var expectedSize = 13; - var qrData = QRCodeGenerator.GenerateMicroQrCode(input); + var qrData = QRCodeGenerator.GenerateMicroQrCode(input, ECCLevel.M, -2); (qrData.ModuleMatrix.Count - 8).ShouldBe(expectedSize); // exclude padding var encoder = new AsciiQRCode(qrData); var txt = encoder.GetGraphicSmall(false, true, Environment.NewLine); From 6e9048eec631a7a3d2a38b1c3caaffe9e8fc8b4b Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 13 Apr 2025 23:36:01 -0400 Subject: [PATCH 15/22] Update --- QRCoder/QRCodeGenerator/CapacityTables.cs | 2 +- QRCoder/QRCodeGenerator/ModulePlacer.cs | 64 +++++++++-------------- QRCoder/QRCodeGenerator/Polynom.cs | 3 +- QRCoderTests/QRGeneratorTests.cs | 2 +- 4 files changed, 30 insertions(+), 41 deletions(-) diff --git a/QRCoder/QRCodeGenerator/CapacityTables.cs b/QRCoder/QRCodeGenerator/CapacityTables.cs index f190d588..df885273 100644 --- a/QRCoder/QRCodeGenerator/CapacityTables.cs +++ b/QRCoder/QRCodeGenerator/CapacityTables.cs @@ -208,7 +208,7 @@ private static List CreateCapacityECCTable(int[] capacityECCBaseValues) errorCorrectionLevel: ECCLevel.Default, totalDataCodewords: 3, totalDataBits: 20, - eccPerBlock: 0), + eccPerBlock: 2), // Micro QR Code Version M2 new ECCInfo(-2, ECCLevel.L, 5, 40, 5), diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.cs b/QRCoder/QRCodeGenerator/ModulePlacer.cs index 381843e4..6f78f432 100644 --- a/QRCoder/QRCodeGenerator/ModulePlacer.cs +++ b/QRCoder/QRCodeGenerator/ModulePlacer.cs @@ -66,21 +66,21 @@ public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr, bool offse // // i { x1, y1 } // =============== - // 0 { 1, 8 } - // 1 { 2, 8 } - // 2 { 3, 8 } - // 3 { 4, 8 } - // 4 { 5, 8 } - // 5 { 6, 8 } - // 6 { 7, 8 } + // 0 { 8, 1 } + // 1 { 8, 2 } + // 2 { 8, 3 } + // 3 { 8, 4 } + // 4 { 8, 5 } + // 5 { 8, 6 } + // 6 { 8, 7 } // 7 { 8, 8 } - // 8 { 8, 7 } - // 9 { 8, 6 } - // 10 { 8, 5 } - // 11 { 8, 4 } - // 12 { 8, 3 } - // 13 { 8, 2 } - // 14 { 8, 1 } + // 8 { 7, 8 } + // 9 { 6, 8 } + // 10 { 5, 8 } + // 11 { 4, 8 } + // 12 { 3, 8 } + // 13 { 2, 8 } + // 14 { 1, 8 } for (var i = 0; i < 15; i++) { @@ -89,8 +89,8 @@ public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr, bool offse if (isMicro) { // Micro QR format positions - x1 = i < 8 ? i + 1 : 8; - y1 = i < 8 ? 8 : 15 - i; + x1 = i < 8 ? 8 : 14 - i + 1; + y1 = i < 8 ? i + 1 : 8; // Micro QR only uses one set of format positions, no duplication. qrCode.ModuleMatrix[y1 + offsetValue][x1 + offsetValue] = formatStr[14 - i]; @@ -227,34 +227,22 @@ public static void PlaceDataWords(QRCodeData qrCode, BitArray data, BlockedModul for (var x = size - 1; x >= 0; x -= 2) { // Skip the timing pattern column at position 6. - if (x == 6) + if (qrCode.Version > 0 && x == 6) x = 5; // Loop through each row in the current column set. for (var yMod = 1; yMod <= size; yMod++) { - int y; // Actual y position to place data in the matrix. - // Determine the actual y position based on the current fill direction. - if (up) - { - y = size - yMod; // Calculate y for upward direction. - // Place data if within data length, current position is not blocked, and leftward column is in bounds. - if (index < count && !blockedModules.IsBlocked(x, y)) - qrCode.ModuleMatrix[y + 4][x + 4] = data[index++]; - if (index < count && x > 0 && !blockedModules.IsBlocked(x - 1, y)) - qrCode.ModuleMatrix[y + 4][x - 1 + 4] = data[index++]; - } - else - { - y = yMod - 1; // Calculate y for downward direction. - // Similar checks and data placement for the downward direction. - if (index < count && !blockedModules.IsBlocked(x, y)) - qrCode.ModuleMatrix[y + 4][x + 4] = data[index++]; - if (index < count && x > 0 && !blockedModules.IsBlocked(x - 1, y)) - qrCode.ModuleMatrix[y + 4][x - 1 + 4] = data[index++]; - } + int y = up ? size - yMod : yMod - 1; + + // Place data if within data length and current position is not blocked. + if (index < count && !blockedModules.IsBlocked(x, y)) + qrCode.ModuleMatrix[y + 4][x + 4] = data[index++]; + if (index < count && !blockedModules.IsBlocked(x - 1, y)) + qrCode.ModuleMatrix[y + 4][x - 1 + 4] = data[index++]; } + // Switch the fill direction after completing each column set. up = !up; } @@ -292,7 +280,7 @@ public static void ReserveVersionAreas(int size, int version, BlockedModules blo { if (version < 0) // Micro QR codes { - blockedModules.Add(new Rectangle(0, 8, 8, 1)); + blockedModules.Add(new Rectangle(0, 8, 9, 1)); blockedModules.Add(new Rectangle(8, 0, 1, 8)); return; } diff --git a/QRCoder/QRCodeGenerator/Polynom.cs b/QRCoder/QRCodeGenerator/Polynom.cs index 6fb3c302..6cb6115d 100644 --- a/QRCoder/QRCodeGenerator/Polynom.cs +++ b/QRCoder/QRCodeGenerator/Polynom.cs @@ -154,8 +154,9 @@ public override string ToString() { var sb = new StringBuilder(); - foreach (var polyItem in _polyItems) + for (int i = 0; i < Count; i++) { + var polyItem = _polyItems[i]; sb.Append("a^" + polyItem.Coefficient + "*x^" + polyItem.Exponent + " + "); } diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 485b8d99..ecb07836 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -19,7 +19,7 @@ public class QRGeneratorTests [Fact] public void micro() { - var input = "000000"; + var input = "00000000"; var expectedSize = 13; var qrData = QRCodeGenerator.GenerateMicroQrCode(input, ECCLevel.M, -2); From defb5821e03c794f4c2cdc0b1a472c24c0ac4861 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 13 Apr 2025 23:37:08 -0400 Subject: [PATCH 16/22] Update api approvals --- .../QRCoder.approved.txt | 1 + QRCoderApiTests/net60-windows/QRCoder.approved.txt | 1 + QRCoderApiTests/net60/QRCoder.approved.txt | 1 + QRCoderApiTests/netstandard13/QRCoder.approved.txt | 1 + 4 files changed, 4 insertions(+) diff --git a/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20/QRCoder.approved.txt b/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20/QRCoder.approved.txt index 0a75bc99..20c384af 100644 --- a/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20/QRCoder.approved.txt +++ b/QRCoderApiTests/net35+net40+net50+net50-windows+netstandard20/QRCoder.approved.txt @@ -894,6 +894,7 @@ namespace QRCoder public QRCoder.QRCodeData CreateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } public QRCoder.QRCodeData CreateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { } public void Dispose() { } + public static QRCoder.QRCodeData GenerateMicroQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel = -1, int requestedVersion = 0) { } public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload) { } public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } public static QRCoder.QRCodeData GenerateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } diff --git a/QRCoderApiTests/net60-windows/QRCoder.approved.txt b/QRCoderApiTests/net60-windows/QRCoder.approved.txt index 99b5f961..5c4386b0 100644 --- a/QRCoderApiTests/net60-windows/QRCoder.approved.txt +++ b/QRCoderApiTests/net60-windows/QRCoder.approved.txt @@ -902,6 +902,7 @@ namespace QRCoder public QRCoder.QRCodeData CreateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } public QRCoder.QRCodeData CreateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { } public void Dispose() { } + public static QRCoder.QRCodeData GenerateMicroQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel = -1, int requestedVersion = 0) { } public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload) { } public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } public static QRCoder.QRCodeData GenerateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } diff --git a/QRCoderApiTests/net60/QRCoder.approved.txt b/QRCoderApiTests/net60/QRCoder.approved.txt index 6d269d08..8ee10f23 100644 --- a/QRCoderApiTests/net60/QRCoder.approved.txt +++ b/QRCoderApiTests/net60/QRCoder.approved.txt @@ -836,6 +836,7 @@ namespace QRCoder public QRCoder.QRCodeData CreateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } public QRCoder.QRCodeData CreateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { } public void Dispose() { } + public static QRCoder.QRCodeData GenerateMicroQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel = -1, int requestedVersion = 0) { } public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload) { } public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } public static QRCoder.QRCodeData GenerateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } diff --git a/QRCoderApiTests/netstandard13/QRCoder.approved.txt b/QRCoderApiTests/netstandard13/QRCoder.approved.txt index 343903e1..4b1075b0 100644 --- a/QRCoderApiTests/netstandard13/QRCoder.approved.txt +++ b/QRCoderApiTests/netstandard13/QRCoder.approved.txt @@ -800,6 +800,7 @@ namespace QRCoder public QRCoder.QRCodeData CreateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } public QRCoder.QRCodeData CreateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { } public void Dispose() { } + public static QRCoder.QRCodeData GenerateMicroQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel = -1, int requestedVersion = 0) { } public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload) { } public static QRCoder.QRCodeData GenerateQrCode(QRCoder.PayloadGenerator.Payload payload, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } public static QRCoder.QRCodeData GenerateQrCode(byte[] binaryData, QRCoder.QRCodeGenerator.ECCLevel eccLevel) { } From cb663d8cf22ff3ab30c1db231400bef4029bfaa2 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 13 Apr 2025 23:45:53 -0400 Subject: [PATCH 17/22] update --- QRCoder/QRCodeGenerator/ModulePlacer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.cs b/QRCoder/QRCodeGenerator/ModulePlacer.cs index 6f78f432..a30e3c7d 100644 --- a/QRCoder/QRCodeGenerator/ModulePlacer.cs +++ b/QRCoder/QRCodeGenerator/ModulePlacer.cs @@ -226,7 +226,7 @@ public static void PlaceDataWords(QRCodeData qrCode, BitArray data, BlockedModul // Loop from the rightmost column to the leftmost column, skipping one column each time. for (var x = size - 1; x >= 0; x -= 2) { - // Skip the timing pattern column at position 6. + // Skip the timing pattern column at position 6 (for normal QR codes only, not Micro QR codes). if (qrCode.Version > 0 && x == 6) x = 5; From e759337288c568220f9db2377f60981eeb841cc7 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Mon, 14 Apr 2025 16:50:55 -0400 Subject: [PATCH 18/22] progress --- QRCoder/QRCodeGenerator.cs | 39 ++++++++++++++++--- QRCoder/QRCodeGenerator/ModulePlacer.cs | 5 ++- QRCoderTests/QRGeneratorTests.cs | 50 +++++++++++++++++++------ 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index fa2ff7a4..346f2e24 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -302,7 +302,7 @@ private static QRCodeData GenerateQrCode(BitArray bitArray, ECCLevel eccLevel, i // fills the bit array with a repeating pattern to reach the required length void PadData() { - var dataLength = eccInfo.TotalDataCodewords * 8; + var dataLength = eccInfo.TotalDataBits; var lengthDiff = dataLength - bitArray.Length; if (lengthDiff > 0) { @@ -343,6 +343,7 @@ List CalculateECCBlocks() void AddCodeWordBlocks(int blockNum, int blocksInGroup, int codewordsInGroup, int offset2, int count, Polynom generatorPolynom) { var groupLength = codewordsInGroup * 8; + groupLength = groupLength > count ? count : groupLength; for (var i = 0; i < blocksInGroup; i++) { var eccWordList = CalculateECCWords(bitArray, offset2, groupLength, eccInfo, generatorPolynom); @@ -714,14 +715,42 @@ private static bool IsInRange(char c, char min, char max) /// Calculates the message polynomial from a bit array which represents the encoded data. /// /// A polynomial representation of the message. - private static Polynom CalculateMessagePolynom(BitArray bitArray, int offset, int count) + //private static Polynom CalculateMessagePolynom(BitArray bitArray, int offset, int count) + //{ + // if (count % 8 != 0) + // throw new InvalidOperationException("test"); + // count /= 8; + // var messagePol = new Polynom(count); + // for (var i = count - 1; i >= 0; i--) + // { + // messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, 8), i)); + // offset += 8; + // } + // return messagePol; + //} + private static Polynom CalculateMessagePolynom(BitArray bitArray, int offset, int bitCount) { - var messagePol = new Polynom(count /= 8); - for (var i = count - 1; i >= 0; i--) + var fullBytes = bitCount / 8; + var remainingBits = bitCount % 8; + + var polynomLength = fullBytes + (remainingBits > 0 ? 1 : 0); + var messagePol = new Polynom(polynomLength); + + int exponent = polynomLength - 1; + + // Process full 8-bit codewords + for (int i = 0; i < fullBytes; i++) { - messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, 8), i)); + messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, 8), exponent--)); offset += 8; } + + // Process the final codeword if it's shorter (e.g., 4 bits for M1 and M3) + if (remainingBits > 0) + { + messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, remainingBits), exponent)); + } + return messagePol; } diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.cs b/QRCoder/QRCodeGenerator/ModulePlacer.cs index a30e3c7d..774d13aa 100644 --- a/QRCoder/QRCodeGenerator/ModulePlacer.cs +++ b/QRCoder/QRCodeGenerator/ModulePlacer.cs @@ -82,6 +82,9 @@ public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr, bool offse // 13 { 2, 8 } // 14 { 1, 8 } + // The bit pattern is considered an entire 'word' and LSB goes in position 0 + // So, we need to reverse the order of the generated bit pattern, hence the (14 - i) below + for (var i = 0; i < 15; i++) { int x1, y1, x2, y2; @@ -239,7 +242,7 @@ public static void PlaceDataWords(QRCodeData qrCode, BitArray data, BlockedModul // Place data if within data length and current position is not blocked. if (index < count && !blockedModules.IsBlocked(x, y)) qrCode.ModuleMatrix[y + 4][x + 4] = data[index++]; - if (index < count && !blockedModules.IsBlocked(x - 1, y)) + if (index < count && x > 0 && !blockedModules.IsBlocked(x - 1, y)) qrCode.ModuleMatrix[y + 4][x - 1 + 4] = data[index++]; } diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index ecb07836..78a47eb7 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -17,23 +17,35 @@ namespace QRCoderTests; public class QRGeneratorTests { [Fact] - public void micro() + public void micro_debug_m1() { - var input = "00000000"; - var expectedSize = 13; + var input = "54321"; + var expectedSize = 11; - var qrData = QRCodeGenerator.GenerateMicroQrCode(input, ECCLevel.M, -2); + var qrData = QRCodeGenerator.GenerateMicroQrCode(input, ECCLevel.Default, -1); (qrData.ModuleMatrix.Count - 8).ShouldBe(expectedSize); // exclude padding var encoder = new AsciiQRCode(qrData); - var txt = encoder.GetGraphicSmall(false, true, Environment.NewLine); + var txt = encoder.GetGraphicSmall(drawQuietZones: false, invert: true, endOfLine: Environment.NewLine); Debug.WriteLine(txt); - txt = encoder.GetGraphic(1, drawQuietZones: false, endOfLine: Environment.NewLine); - Debug.WriteLine(txt); - - //var result = string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray()); - //var hash = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(result)); - //var hashString = Convert.ToBase64String(hash); - //hashString.TrimEnd('=').ShouldBe(expectedHash); + var txt2 = encoder.GetGraphic(1, drawQuietZones: false, endOfLine: Environment.NewLine); + Debug.WriteLine(txt2); + + // expected + _ = @" +██████████████ ██ ██ +██ ██ ██ +██ ██████ ██ ██ ██ +██ ██████ ██ ██ +██ ██████ ██ ██ +██ ██ ██████ +██████████████ ██ + ██████ +████ ██ + ██ ██████ ████ +██ ██ ██████████ +"; + + //txt2.ShouldBe(expected); } [Fact] @@ -62,6 +74,20 @@ public void validate_antilogtable() } #if !NETFRAMEWORK // [Theory] is not supported in xunit < 2.0.0 + [Theory] + [InlineData("00000000", ECCLevel.M, "lEBc3nKaK0UpMfenT5FTX02Zgfg", 13)] //verified + [InlineData("123456789", ECCLevel.L, "gCY4Cj1uLhI/0JjWG1F9kC4S1+I", 13)] //verified + [InlineData("abcd56789012345", ECCLevel.L, "kqcKfCCdu1VTjjtsmK4iBav9FTs", 17)] //verified + public void validate_micro_qr_code(string input, ECCLevel eccLevel, string expectedHash, int expectedSize) + { + var qrData = QRCodeGenerator.GenerateMicroQrCode(input, eccLevel); + (qrData.ModuleMatrix.Count - 8).ShouldBe(expectedSize); // exclude padding + var result = string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray()); + var hash = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(result)); + var hashString = Convert.ToBase64String(hash); + hashString.TrimEnd('=').ShouldBe(expectedHash); + } + [Theory] // version 1 numeric [InlineData("1", "KWw84nkWZLMh5LqAJ/4s/4mW/08", 21)] From 696dc86645e0463f9c5b24d4a2ff819f7631a982 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Tue, 15 Apr 2025 22:39:23 -0400 Subject: [PATCH 19/22] Fix ECC for M1 and M3 sizes --- QRCoder/QRCodeGenerator.cs | 68 +++++++++++++++++++++----------- QRCoderTests/QRGeneratorTests.cs | 9 ++--- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index 346f2e24..16b3c6ef 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -357,7 +357,13 @@ void AddCodeWordBlocks(int blockNum, int blocksInGroup, int codewordsInGroup, in int CalculateInterleavedLength() { var length = 0; - for (var i = 0; i < Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); i++) + var codewords = Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); + if (version == -1 || version == -3) + { + codewords--; + length += 4; + } + for (var i = 0; i < codewords; i++) { foreach (var codeBlock in codeWordWithECC) if ((uint)codeBlock.CodeWordsLength / 8 > i) @@ -378,6 +384,9 @@ BitArray InterleaveData() { var data = new BitArray(interleavedLength); int pos = 0; + int codewords = Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); + if (version == -1 || version == -3) + codewords--; for (var i = 0; i < Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); i++) { foreach (var codeBlock in codeWordWithECC) @@ -386,6 +395,10 @@ BitArray InterleaveData() pos = bitArray.CopyTo(data, (int)((uint)i * 8) + codeBlock.CodeWordsOffset, pos, 8); } } + if (version == -1 || version == -3) + { + pos = bitArray.CopyTo(data, (int)((uint)codewords * 8) + codeWordWithECC[0].CodeWordsOffset, pos, 4); + } for (var i = 0; i < eccInfo.ECCPerBlock; i++) { foreach (var codeBlock in codeWordWithECC) @@ -712,45 +725,52 @@ private static bool IsInRange(char c, char min, char max) => (uint)(c - min) <= (uint)(max - min); /// - /// Calculates the message polynomial from a bit array which represents the encoded data. + /// Converts a segment of a BitArray representing QR code data into a polynomial, + /// padding the final byte if necessary (for Micro QR variants like M1 or M3). /// - /// A polynomial representation of the message. - //private static Polynom CalculateMessagePolynom(BitArray bitArray, int offset, int count) - //{ - // if (count % 8 != 0) - // throw new InvalidOperationException("test"); - // count /= 8; - // var messagePol = new Polynom(count); - // for (var i = count - 1; i >= 0; i--) - // { - // messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, 8), i)); - // offset += 8; - // } - // return messagePol; - //} + /// The full bit array representing encoded QR code data. + /// Starting position in the bit array. + /// Total number of bits to convert into codewords. + /// A polynomial representing the message codewords. private static Polynom CalculateMessagePolynom(BitArray bitArray, int offset, int bitCount) { + // Calculate how many full 8-bit codewords are present var fullBytes = bitCount / 8; + + // Determine if there is a remaining partial byte (e.g., 4 bits for Micro QR M1 and M3 versions) var remainingBits = bitCount % 8; + if (remainingBits > 0) + { + // Pad the last byte with zero bits to make it a full 8-bit codeword + var addlBits = 8 - remainingBits; + var minBitArrayLength = offset + bitCount + addlBits; + + // Extend BitArray length if needed to fit the padded bits + if (bitArray.Length < minBitArrayLength) + bitArray.Length = minBitArrayLength; + + // Pad the remaining bits with false (0) values + for (int i = 0; i < addlBits; i++) + bitArray[offset + bitCount + i] = false; + } + + // Total number of codewords (includes extra for partial byte if present) var polynomLength = fullBytes + (remainingBits > 0 ? 1 : 0); + + // Initialize the polynomial var messagePol = new Polynom(polynomLength); + // Exponent for polynomial terms starts from highest degree int exponent = polynomLength - 1; - // Process full 8-bit codewords - for (int i = 0; i < fullBytes; i++) + // Convert each 8-bit segment into a decimal value and add it to the polynomial + for (int i = 0; i < polynomLength; i++) { messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, 8), exponent--)); offset += 8; } - // Process the final codeword if it's shorter (e.g., 4 bits for M1 and M3) - if (remainingBits > 0) - { - messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, remainingBits), exponent)); - } - return messagePol; } diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 78a47eb7..66fa1afd 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -19,14 +19,12 @@ public class QRGeneratorTests [Fact] public void micro_debug_m1() { - var input = "54321"; - var expectedSize = 11; + var input = "abc"; + var expectedSize = 15; - var qrData = QRCodeGenerator.GenerateMicroQrCode(input, ECCLevel.Default, -1); + var qrData = QRCodeGenerator.GenerateMicroQrCode(input, ECCLevel.M, -3); (qrData.ModuleMatrix.Count - 8).ShouldBe(expectedSize); // exclude padding var encoder = new AsciiQRCode(qrData); - var txt = encoder.GetGraphicSmall(drawQuietZones: false, invert: true, endOfLine: Environment.NewLine); - Debug.WriteLine(txt); var txt2 = encoder.GetGraphic(1, drawQuietZones: false, endOfLine: Environment.NewLine); Debug.WriteLine(txt2); @@ -75,6 +73,7 @@ public void validate_antilogtable() #if !NETFRAMEWORK // [Theory] is not supported in xunit < 2.0.0 [Theory] + [InlineData("54321", ECCLevel.Default, "ZfnO93tpy9jjaACKXue2VsACXxY", 11)] //verified [InlineData("00000000", ECCLevel.M, "lEBc3nKaK0UpMfenT5FTX02Zgfg", 13)] //verified [InlineData("123456789", ECCLevel.L, "gCY4Cj1uLhI/0JjWG1F9kC4S1+I", 13)] //verified [InlineData("abcd56789012345", ECCLevel.L, "kqcKfCCdu1VTjjtsmK4iBav9FTs", 17)] //verified From faac596bf84f79e56e64946366a3a4a5925f9ba8 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Wed, 16 Apr 2025 16:21:41 -0400 Subject: [PATCH 20/22] Update QRCoder/QRCodeGenerator/GaloisField.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- QRCoder/QRCodeGenerator/GaloisField.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QRCoder/QRCodeGenerator/GaloisField.cs b/QRCoder/QRCodeGenerator/GaloisField.cs index 0a84dbeb..d1e95f96 100644 --- a/QRCoder/QRCodeGenerator/GaloisField.cs +++ b/QRCoder/QRCodeGenerator/GaloisField.cs @@ -43,6 +43,6 @@ public static int GetAlphaExpFromIntVal(int intVal) /// This is particularly necessary when performing multiplications in the field which can result in exponents exceeding the field's maximum. /// public static int ShrinkAlphaExp(int alphaExp) - => (int)((alphaExp % 256) + Math.Floor((double)(alphaExp / 256))); + => (alphaExp % 256) + (alphaExp / 256); } } From ec828eb9f824b2c75b31ca496286dc642b41c985 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 19 Apr 2025 02:09:58 -0400 Subject: [PATCH 21/22] update --- QRCoder/QRCodeGenerator.cs | 12 ++++++++++-- QRCoderTests/QRGeneratorTests.cs | 32 ++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index 16b3c6ef..95d7b85d 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -310,11 +310,19 @@ void PadData() var index = bitArray.Length; // extend bit array to required length bitArray.Length = dataLength; - // pad with 4 zeros (or less if lengthDiff < 4) - index += Math.Min(lengthDiff, 4); + // compute padding length + var padLength = version > 0 ? 4 : + version == -1 ? 3 : + version == -2 ? 5 : + version == -3 ? 7 : 9; + // pad with zeros (or less if not enough room) + index += padLength; // pad to nearest 8 bit boundary if ((uint)index % 8 != 0) index += 8 - (int)((uint)index % 8); + // for m1 and m3 sizes don't fill last 4 bits with repeating pattern + if (version == -1 || version == -3) + dataLength -= 4; // pad with repeating pattern var repeatingPatternIndex = 0; while (index < dataLength) diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 66fa1afd..81d7bf05 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -17,7 +17,7 @@ namespace QRCoderTests; public class QRGeneratorTests { [Fact] - public void micro_debug_m1() + public void micro_debug() { var input = "abc"; var expectedSize = 15; @@ -29,21 +29,25 @@ public void micro_debug_m1() Debug.WriteLine(txt2); // expected - _ = @" -██████████████ ██ ██ -██ ██ ██ -██ ██████ ██ ██ ██ -██ ██████ ██ ██ -██ ██████ ██ ██ -██ ██ ██████ -██████████████ ██ - ██████ -████ ██ - ██ ██████ ████ -██ ██ ██████████ + var expected = @" +██████████████ ██ ██ ██ ██ +██ ██ ████ ████ +██ ██████ ██ ██ ██████ +██ ██████ ██ ██ ██████ +██ ██████ ██ ██████ +██ ██ ██ ██████ +██████████████ ██ ██████ + ██ ██ +██ ████ ████ ██ + ██ ██████ ██ ██ ██ +██ ████████████████ ████ + ██ ██ ██ ████████ +██████ ██████████ + ██ ██ ██ ██████ +██ ██ ██████ ████ ██████ "; - //txt2.ShouldBe(expected); + txt2.Trim('\r', '\n').ShouldBe(expected.Trim('\r', '\n'), StringCompareShould.IgnoreLineEndings); } [Fact] From c7a71b105e367afd83afcd7cee0d34d3f2152df9 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sat, 19 Apr 2025 11:13:13 -0400 Subject: [PATCH 22/22] update --- QRCoderTests/QRGeneratorTests.cs | 35 +------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 81d7bf05..2f5921cd 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -16,40 +16,6 @@ namespace QRCoderTests; public class QRGeneratorTests { - [Fact] - public void micro_debug() - { - var input = "abc"; - var expectedSize = 15; - - var qrData = QRCodeGenerator.GenerateMicroQrCode(input, ECCLevel.M, -3); - (qrData.ModuleMatrix.Count - 8).ShouldBe(expectedSize); // exclude padding - var encoder = new AsciiQRCode(qrData); - var txt2 = encoder.GetGraphic(1, drawQuietZones: false, endOfLine: Environment.NewLine); - Debug.WriteLine(txt2); - - // expected - var expected = @" -██████████████ ██ ██ ██ ██ -██ ██ ████ ████ -██ ██████ ██ ██ ██████ -██ ██████ ██ ██ ██████ -██ ██████ ██ ██████ -██ ██ ██ ██████ -██████████████ ██ ██████ - ██ ██ -██ ████ ████ ██ - ██ ██████ ██ ██ ██ -██ ████████████████ ████ - ██ ██ ██ ████████ -██████ ██████████ - ██ ██ ██ ██████ -██ ██ ██████ ████ ██████ -"; - - txt2.Trim('\r', '\n').ShouldBe(expected.Trim('\r', '\n'), StringCompareShould.IgnoreLineEndings); - } - [Fact] public void validate_antilogtable() { @@ -81,6 +47,7 @@ public void validate_antilogtable() [InlineData("00000000", ECCLevel.M, "lEBc3nKaK0UpMfenT5FTX02Zgfg", 13)] //verified [InlineData("123456789", ECCLevel.L, "gCY4Cj1uLhI/0JjWG1F9kC4S1+I", 13)] //verified [InlineData("abcd56789012345", ECCLevel.L, "kqcKfCCdu1VTjjtsmK4iBav9FTs", 17)] //verified + [InlineData("abc", ECCLevel.M, "334sxrtY5KkNZRGj1pBgb87/cFc", 15)] //reads fine, but unable to verify repeating pattern public void validate_micro_qr_code(string input, ECCLevel eccLevel, string expectedHash, int expectedSize) { var qrData = QRCodeGenerator.GenerateMicroQrCode(input, eccLevel);