Skip to content

Instantly share code, notes, and snippets.

@yasirkula
Created June 15, 2024 23:32
Show Gist options
  • Save yasirkula/bb381a0dc8cf4aec7171ea1a909b4f57 to your computer and use it in GitHub Desktop.
Save yasirkula/bb381a0dc8cf4aec7171ea1a909b4f57 to your computer and use it in GitHub Desktop.
Generate QR codes inside Unity
/// = Sources =
/// https://github.com/codebude/QRCoder
/// https://github.com/codebude/QRCoder.Unity
///
/// = License =
/// The MIT License (MIT)
///
/// Copyright(c) 2013 - 2018 Raffael Herrmann
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy of
/// this software and associated documentation files (the "Software"), to deal in
/// the Software without restriction, including without limitation the rights to
/// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
/// the Software, and to permit persons to whom the Software is furnished to do so,
/// subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
/// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
/// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
/// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
/// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
///
/// = Changes =
/// I've removed the non-essential and Unity-incompatible classes, combined everything into
/// a single script and modified the <see cref="QRCoder.QRCodeData.ToTexture2D"/> function.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using UnityEngine;
namespace QRCoder.Exceptions
{
public class DataTooLongException : Exception
{
public DataTooLongException(string eccLevel, string encodingMode, int maxSizeByte) : base(
$"The given payload exceeds the maximum size of the QR code standard. The maximum size allowed for the choosen paramters (ECC level={eccLevel}, EncodingMode={encodingMode}) is {maxSizeByte} byte."
)
{ }
public DataTooLongException(string eccLevel, string encodingMode, int version, int maxSizeByte) : base(
$"The given payload exceeds the maximum size of the QR code standard. The maximum size allowed for the choosen paramters (ECC level={eccLevel}, EncodingMode={encodingMode}, FixedVersion={version}) is {maxSizeByte} byte."
)
{ }
}
}
namespace QRCoder
{
public class QRCodeData
{
public readonly List<BitArray> ModuleMatrix;
/// <param name="originalSize">QR code's original width/height value in pixels.</param>
/// <returns>The multiplier for <paramref name="originalSize"/>. So, if it's 2, the resulting texture will be twice as big. If it's 5, the texture will be 5 times as big.</returns>
public delegate int SizeMultiplierDelegate(int originalSize);
public QRCodeData(int version)
{
int size = 21 + (version - 1) * 4;
ModuleMatrix = new List<BitArray>(size);
for (int i = 0; i < size; i++)
ModuleMatrix.Add(new BitArray(size));
}
public Texture2D ToTexture2D(bool markTextureNonReadable = false, SizeMultiplierDelegate sizeMultiplierFunc = null)
{
int padding = 4; // See: https://github.com/codebude/QRCoder/issues/90
int originalSize = ModuleMatrix.Count - padding * 2;
int sizeMultiplier = sizeMultiplierFunc?.Invoke(originalSize) ?? 1;
int size = originalSize * sizeMultiplier;
Color32[] pixels = new Color32[size * size];
Color32 darkColor = Color.black, lightColor = Color.white;
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
pixels[y * size + x] = ModuleMatrix[y / sizeMultiplier + padding][x / sizeMultiplier + padding] ? darkColor : lightColor;
}
Texture2D texture = new Texture2D(size, size, TextureFormat.RGBA32, false) { filterMode = FilterMode.Point };
texture.SetPixels32(pixels);
texture.Apply(false, markTextureNonReadable);
return texture;
}
}
public class QRCodeGenerator
{
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 List<AlignmentPattern> alignmentPatternTable = CreateAlignmentPatternTable();
private static readonly List<ECCInfo> capacityECCTable = CreateCapacityECCTable();
private static readonly List<VersionInfo> capacityTable = CreateCapacityTable();
private static readonly List<Antilog> galoisField = CreateAntilogTable();
private static readonly Dictionary<char, int> alphanumEncDict = CreateAlphanumEncDict();
public enum EciMode
{
Default = 0,
Iso8859_1 = 3,
Iso8859_2 = 4,
Utf8 = 26
}
/// <summary>
/// Initializes the QR code generator
/// </summary>
public QRCodeGenerator()
{
}
/// <summary>
/// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation.
/// </summary>
/// <param name="plainText">The payload which shall be encoded in the QR code</param>
/// <param name="eccLevel">The level of error correction data</param>
/// <param name="forceUtf8">Shall the generator be forced to work in UTF-8 mode?</param>
/// <param name="utf8BOM">Should the byte-order-mark be used?</param>
/// <param name="eciMode">Which ECI mode shall be used?</param>
/// <param name="requestedVersion">Set fixed QR code target version.</param>
/// <exception cref="QRCoder.Exceptions.DataTooLongException">Thrown when the payload is too big to be encoded in a QR code.</exception>
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
public QRCodeData CreateQrCode(string plainText, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1)
{
return GenerateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion);
}
/// <inheritdoc cref="CreateQrCode(string, ECCLevel, bool, bool, EciMode, int)"/>
/// <param name="binaryData">A byte array which shall be encoded/stored in the QR code</param>
public QRCodeData CreateQrCode(byte[] binaryData, ECCLevel eccLevel)
{
return GenerateQrCode(binaryData, eccLevel);
}
/// <inheritdoc cref="CreateQrCode(string, ECCLevel, bool, bool, EciMode, int)"/>
public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1)
{
EncodingMode encoding = GetEncodingFromPlaintext(plainText, forceUtf8);
var codedText = PlainTextToBinary(plainText, encoding, eciMode, utf8BOM, forceUtf8);
var dataInputLength = GetDataLength(encoding, plainText, codedText, forceUtf8);
int version = requestedVersion;
if (version == -1)
{
version = GetVersion(dataInputLength + (eciMode != EciMode.Default ? 2 : 0), encoding, eccLevel);
}
else
{
//Version was passed as fixed version via parameter. Thus let's check if chosen version is valid.
var minVersion = GetVersion(dataInputLength + (eciMode != EciMode.Default ? 2 : 0), encoding, eccLevel);
if (minVersion > version)
{
var maxSizeByte = capacityTable[version - 1].Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding];
throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte);
}
}
string modeIndicator = String.Empty;
if (eciMode != EciMode.Default)
{
modeIndicator = DecToBin((int)EncodingMode.ECI, 4);
modeIndicator += DecToBin((int)eciMode, 8);
}
modeIndicator += DecToBin((int)encoding, 4);
var countIndicator = DecToBin(dataInputLength, GetCountIndicatorLength(version, encoding));
var bitString = modeIndicator + countIndicator + codedText;
return GenerateQrCode(bitString, eccLevel, version);
}
/// <inheritdoc cref="CreateQrCode(byte[], ECCLevel)"/>
public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel)
{
int version = GetVersion(binaryData.Length, EncodingMode.Byte, eccLevel);
string modeIndicator = DecToBin((int)EncodingMode.Byte, 4);
int countIndicatorLen = GetCountIndicatorLength(version, EncodingMode.Byte);
string countIndicator = DecToBin(binaryData.Length, countIndicatorLen);
StringBuilder sb = new StringBuilder(capacity: 4 + countIndicatorLen + (binaryData.Length * 8));
sb.Append(modeIndicator).Append(countIndicator);
foreach (byte b in binaryData)
{
sb.Append(DecToBin(b, 8));
}
string bitString = sb.ToString();
return GenerateQrCode(bitString, eccLevel, version);
}
private static QRCodeData GenerateQrCode(string bitString, ECCLevel eccLevel, int version)
{
//Fill up data code word
var eccInfo = capacityECCTable.Single(x => x.Version == version && x.ErrorCorrectionLevel == eccLevel);
var dataLength = eccInfo.TotalDataCodewords * 8;
var lengthDiff = dataLength - bitString.Length;
if (lengthDiff > 0)
bitString += new string('0', Math.Min(lengthDiff, 4));
if ((bitString.Length % 8) != 0)
bitString += new string('0', 8 - (bitString.Length % 8));
while (bitString.Length < dataLength)
bitString += "1110110000010001";
if (bitString.Length > dataLength)
bitString = bitString.Substring(0, dataLength);
//Calculate error correction words
var codeWordWithECC = new List<CodewordBlock>(eccInfo.BlocksInGroup1 + eccInfo.BlocksInGroup2);
for (var i = 0; i < eccInfo.BlocksInGroup1; i++)
{
var bitStr = bitString.Substring(i * eccInfo.CodewordsInGroup1 * 8, eccInfo.CodewordsInGroup1 * 8);
var bitBlockList = BinaryStringToBitBlockList(bitStr);
var bitBlockListDec = BinaryStringListToDecList(bitBlockList);
var eccWordList = CalculateECCWords(bitStr, eccInfo);
var eccWordListDec = BinaryStringListToDecList(eccWordList);
codeWordWithECC.Add(
new CodewordBlock(1,
i + 1,
bitStr,
bitBlockList,
eccWordList,
bitBlockListDec,
eccWordListDec)
);
}
bitString = bitString.Substring(eccInfo.BlocksInGroup1 * eccInfo.CodewordsInGroup1 * 8);
for (var i = 0; i < eccInfo.BlocksInGroup2; i++)
{
var bitStr = bitString.Substring(i * eccInfo.CodewordsInGroup2 * 8, eccInfo.CodewordsInGroup2 * 8);
var bitBlockList = BinaryStringToBitBlockList(bitStr);
var bitBlockListDec = BinaryStringListToDecList(bitBlockList);
var eccWordList = CalculateECCWords(bitStr, eccInfo);
var eccWordListDec = BinaryStringListToDecList(eccWordList);
codeWordWithECC.Add(new CodewordBlock(2,
i + 1,
bitStr,
bitBlockList,
eccWordList,
bitBlockListDec,
eccWordListDec)
);
}
//Interleave code words
var interleavedWordsSb = new StringBuilder();
for (var i = 0; i < Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); i++)
{
foreach (var codeBlock in codeWordWithECC)
if (codeBlock.CodeWords.Count > i)
interleavedWordsSb.Append(codeBlock.CodeWords[i]);
}
for (var i = 0; i < eccInfo.ECCPerBlock; i++)
{
foreach (var codeBlock in codeWordWithECC)
if (codeBlock.ECCWords.Count > i)
interleavedWordsSb.Append(codeBlock.ECCWords[i]);
}
interleavedWordsSb.Append(new string('0', remainderBits[version - 1]));
var interleavedData = interleavedWordsSb.ToString();
//Place interleaved data on module matrix
var qr = new QRCodeData(version);
var blockedModules = new List<Rectangle>();
ModulePlacer.PlaceFinderPatterns(ref qr, ref blockedModules);
ModulePlacer.ReserveSeperatorAreas(qr.ModuleMatrix.Count, ref blockedModules);
ModulePlacer.PlaceAlignmentPatterns(ref qr, alignmentPatternTable.Where(x => x.Version == version).Select(x => x.PatternPositions).First(), ref blockedModules);
ModulePlacer.PlaceTimingPatterns(ref qr, ref blockedModules);
ModulePlacer.PlaceDarkModule(ref qr, version, ref blockedModules);
ModulePlacer.ReserveVersionAreas(qr.ModuleMatrix.Count, version, ref blockedModules);
ModulePlacer.PlaceDataWords(ref qr, interleavedData, ref blockedModules);
var maskVersion = ModulePlacer.MaskCode(ref qr, version, ref blockedModules, eccLevel);
var formatStr = GetFormatString(eccLevel, maskVersion);
ModulePlacer.PlaceFormat(ref qr, formatStr);
if (version >= 7)
{
var versionString = GetVersionString(version);
ModulePlacer.PlaceVersion(ref qr, versionString);
}
ModulePlacer.AddQuietZone(ref qr);
return qr;
}
private static string GetFormatString(ECCLevel level, int maskVersion)
{
var generator = "10100110111";
var fStrMask = "101010000010010";
var fStr = (level == ECCLevel.L) ? "01" : (level == ECCLevel.M) ? "00" : (level == ECCLevel.Q) ? "11" : "10";
fStr += DecToBin(maskVersion, 3);
var fStrEcc = fStr.PadRight(15, '0').TrimStart('0');
while (fStrEcc.Length > 10)
{
var sb = new StringBuilder();
generator = generator.PadRight(fStrEcc.Length, '0');
for (var i = 0; i < fStrEcc.Length; i++)
sb.Append((Convert.ToInt32(fStrEcc[i]) ^ Convert.ToInt32(generator[i])).ToString());
fStrEcc = sb.ToString().TrimStart('0');
}
fStrEcc = fStrEcc.PadLeft(10, '0');
fStr += fStrEcc;
var sbMask = new StringBuilder();
for (var i = 0; i < fStr.Length; i++)
sbMask.Append((Convert.ToInt32(fStr[i]) ^ Convert.ToInt32(fStrMask[i])).ToString());
return sbMask.ToString();
}
private static string GetVersionString(int version)
{
var generator = "1111100100101";
var vStr = DecToBin(version, 6);
var vStrEcc = vStr.PadRight(18, '0').TrimStart('0');
while (vStrEcc.Length > 12)
{
var sb = new StringBuilder();
generator = generator.PadRight(vStrEcc.Length, '0');
for (var i = 0; i < vStrEcc.Length; i++)
sb.Append((Convert.ToInt32(vStrEcc[i]) ^ Convert.ToInt32(generator[i])).ToString());
vStrEcc = sb.ToString().TrimStart('0');
}
vStrEcc = vStrEcc.PadLeft(12, '0');
vStr += vStrEcc;
return vStr;
}
private static class ModulePlacer
{
public static void AddQuietZone(ref QRCodeData qrCode)
{
var quietLine = new bool[qrCode.ModuleMatrix.Count + 8];
for (var i = 0; i < quietLine.Length; i++)
quietLine[i] = false;
for (var i = 0; i < 4; i++)
qrCode.ModuleMatrix.Insert(0, new BitArray(quietLine));
for (var i = 0; i < 4; i++)
qrCode.ModuleMatrix.Add(new BitArray(quietLine));
for (var i = 4; i < qrCode.ModuleMatrix.Count - 4; i++)
{
bool[] quietPart = { false, false, false, false };
var tmpLine = new List<bool>(quietPart);
tmpLine.AddRange(qrCode.ModuleMatrix[i].Cast<bool>());
tmpLine.AddRange(quietPart);
qrCode.ModuleMatrix[i] = new BitArray(tmpLine.ToArray());
}
}
private static string ReverseString(string inp)
{
string newStr = string.Empty;
if (inp.Length > 0)
{
for (int i = inp.Length - 1; i >= 0; i--)
newStr += inp[i];
}
return newStr;
}
public static void PlaceVersion(ref QRCodeData qrCode, string versionStr)
{
var size = qrCode.ModuleMatrix.Count;
var vStr = ReverseString(versionStr);
for (var x = 0; x < 6; x++)
{
for (var y = 0; y < 3; y++)
{
qrCode.ModuleMatrix[y + size - 11][x] = vStr[x * 3 + y] == '1';
qrCode.ModuleMatrix[x][y + size - 11] = vStr[x * 3 + y] == '1';
}
}
}
public static void PlaceFormat(ref QRCodeData qrCode, string formatStr)
{
var size = qrCode.ModuleMatrix.Count;
var fStr = ReverseString(formatStr);
var modules = new[,] {
{ 8, 0, size - 1, 8 },
{ 8, 1, size - 2, 8 },
{ 8, 2, size - 3, 8 },
{ 8, 3, size - 4, 8 },
{ 8, 4, size - 5, 8 },
{ 8, 5, size - 6, 8 },
{ 8, 7, size - 7, 8 },
{ 8, 8, size - 8, 8 },
{ 7, 8, 8, size - 7 },
{ 5, 8, 8, size - 6 },
{ 4, 8, 8, size - 5 },
{ 3, 8, 8, size - 4 },
{ 2, 8, 8, size - 3 },
{ 1, 8, 8, size - 2 },
{ 0, 8, 8, size - 1 } };
for (var i = 0; i < 15; i++)
{
var p1 = new Point(modules[i, 0], modules[i, 1]);
var p2 = new Point(modules[i, 2], modules[i, 3]);
qrCode.ModuleMatrix[p1.Y][p1.X] = fStr[i] == '1';
qrCode.ModuleMatrix[p2.Y][p2.X] = fStr[i] == '1';
}
}
public static int MaskCode(ref QRCodeData qrCode, int version, ref List<Rectangle> blockedModules, ECCLevel eccLevel)
{
int? selectedPattern = null;
var patternScore = 0;
var size = qrCode.ModuleMatrix.Count;
var methods = new Dictionary<int, Func<int, int, bool>>(8) {
{ 1, MaskPattern.Pattern1 }, {2, MaskPattern.Pattern2 }, {3, MaskPattern.Pattern3 }, {4, MaskPattern.Pattern4 },
{5, MaskPattern.Pattern5 }, {6, MaskPattern.Pattern6 }, {7, MaskPattern.Pattern7 }, {8, MaskPattern.Pattern8 }
};
foreach (var pattern in methods)
{
var qrTemp = new QRCodeData(version);
for (var y = 0; y < size; y++)
{
for (var x = 0; x < size; x++)
{
qrTemp.ModuleMatrix[y][x] = qrCode.ModuleMatrix[y][x];
}
}
var formatStr = GetFormatString(eccLevel, pattern.Key - 1);
ModulePlacer.PlaceFormat(ref qrTemp, formatStr);
if (version >= 7)
{
var versionString = GetVersionString(version);
ModulePlacer.PlaceVersion(ref qrTemp, versionString);
}
for (var x = 0; x < size; x++)
{
for (var y = 0; y < x; y++)
{
if (!IsBlocked(new Rectangle(x, y, 1, 1), blockedModules))
{
qrTemp.ModuleMatrix[y][x] ^= pattern.Value(x, y);
qrTemp.ModuleMatrix[x][y] ^= pattern.Value(y, x);
}
}
if (!IsBlocked(new Rectangle(x, x, 1, 1), blockedModules))
{
qrTemp.ModuleMatrix[x][x] ^= pattern.Value(x, x);
}
}
var score = MaskPattern.Score(ref qrTemp);
if (!selectedPattern.HasValue || patternScore > score)
{
selectedPattern = pattern.Key;
patternScore = score;
}
}
for (var x = 0; x < size; x++)
{
for (var y = 0; y < x; y++)
{
if (!IsBlocked(new Rectangle(x, y, 1, 1), blockedModules))
{
qrCode.ModuleMatrix[y][x] ^= methods[selectedPattern.Value](x, y);
qrCode.ModuleMatrix[x][y] ^= methods[selectedPattern.Value](y, x);
}
}
if (!IsBlocked(new Rectangle(x, x, 1, 1), blockedModules))
{
qrCode.ModuleMatrix[x][x] ^= methods[selectedPattern.Value](x, x);
}
}
return selectedPattern.Value - 1;
}
public static void PlaceDataWords(ref QRCodeData qrCode, string data, ref List<Rectangle> blockedModules)
{
var size = qrCode.ModuleMatrix.Count;
var up = true;
var datawords = new Queue<bool>();
for (int i = 0; i < data.Length; i++)
{
datawords.Enqueue(data[i] != '0');
}
for (var x = size - 1; x >= 0; x = x - 2)
{
if (x == 6)
x = 5;
for (var yMod = 1; yMod <= size; yMod++)
{
int y;
if (up)
{
y = size - yMod;
if (datawords.Count > 0 && !IsBlocked(new Rectangle(x, y, 1, 1), blockedModules))
qrCode.ModuleMatrix[y][x] = datawords.Dequeue();
if (datawords.Count > 0 && x > 0 && !IsBlocked(new Rectangle(x - 1, y, 1, 1), blockedModules))
qrCode.ModuleMatrix[y][x - 1] = datawords.Dequeue();
}
else
{
y = yMod - 1;
if (datawords.Count > 0 && !IsBlocked(new Rectangle(x, y, 1, 1), blockedModules))
qrCode.ModuleMatrix[y][x] = datawords.Dequeue();
if (datawords.Count > 0 && x > 0 && !IsBlocked(new Rectangle(x - 1, y, 1, 1), blockedModules))
qrCode.ModuleMatrix[y][x - 1] = datawords.Dequeue();
}
}
up = !up;
}
}
public static void ReserveSeperatorAreas(int size, ref List<Rectangle> blockedModules)
{
blockedModules.AddRange(new[]{
new Rectangle(7, 0, 1, 8),
new Rectangle(0, 7, 7, 1),
new Rectangle(0, size-8, 8, 1),
new Rectangle(7, size-7, 1, 7),
new Rectangle(size-8, 0, 1, 8),
new Rectangle(size-7, 7, 7, 1)
});
}
public static void ReserveVersionAreas(int size, int version, ref List<Rectangle> blockedModules)
{
blockedModules.AddRange(new[]{
new Rectangle(8, 0, 1, 6),
new Rectangle(8, 7, 1, 1),
new Rectangle(0, 8, 6, 1),
new Rectangle(7, 8, 2, 1),
new Rectangle(size-8, 8, 8, 1),
new Rectangle(8, size-7, 1, 7)
});
if (version >= 7)
{
blockedModules.AddRange(new[]{
new Rectangle(size-11, 0, 3, 6),
new Rectangle(0, size-11, 6, 3)
});
}
}
public static void PlaceDarkModule(ref QRCodeData qrCode, int version, ref List<Rectangle> blockedModules)
{
qrCode.ModuleMatrix[4 * version + 9][8] = true;
blockedModules.Add(new Rectangle(8, 4 * version + 9, 1, 1));
}
public static void PlaceFinderPatterns(ref QRCodeData qrCode, ref List<Rectangle> blockedModules)
{
var size = qrCode.ModuleMatrix.Count;
int[] locations = { 0, 0, size - 7, 0, 0, size - 7 };
for (var i = 0; i < 6; i = i + 2)
{
for (var x = 0; x < 7; x++)
{
for (var y = 0; y < 7; y++)
{
if (!(((x == 1 || x == 5) && y > 0 && y < 6) || (x > 0 && x < 6 && (y == 1 || y == 5))))
{
qrCode.ModuleMatrix[y + locations[i + 1]][x + locations[i]] = true;
}
}
}
blockedModules.Add(new Rectangle(locations[i], locations[i + 1], 7, 7));
}
}
public static void PlaceAlignmentPatterns(ref QRCodeData qrCode, List<Point> alignmentPatternLocations, ref List<Rectangle> blockedModules)
{
foreach (var loc in alignmentPatternLocations)
{
var alignmentPatternRect = new Rectangle(loc.X, loc.Y, 5, 5);
var blocked = false;
foreach (var blockedRect in blockedModules)
{
if (Intersects(alignmentPatternRect, blockedRect))
{
blocked = true;
break;
}
}
if (blocked)
continue;
for (var x = 0; x < 5; x++)
{
for (var y = 0; y < 5; y++)
{
if (y == 0 || y == 4 || x == 0 || x == 4 || (x == 2 && y == 2))
{
qrCode.ModuleMatrix[loc.Y + y][loc.X + x] = true;
}
}
}
blockedModules.Add(new Rectangle(loc.X, loc.Y, 5, 5));
}
}
public static void PlaceTimingPatterns(ref QRCodeData qrCode, ref List<Rectangle> blockedModules)
{
var size = qrCode.ModuleMatrix.Count;
for (var i = 8; i < size - 8; i++)
{
if (i % 2 == 0)
{
qrCode.ModuleMatrix[6][i] = true;
qrCode.ModuleMatrix[i][6] = true;
}
}
blockedModules.AddRange(new[]{
new Rectangle(6, 8, 1, size-16),
new Rectangle(8, 6, size-16, 1)
});
}
private static bool Intersects(Rectangle r1, Rectangle r2)
{
return r2.X < r1.X + r1.Width && r1.X < r2.X + r2.Width && r2.Y < r1.Y + r1.Height && r1.Y < r2.Y + r2.Height;
}
private static bool IsBlocked(Rectangle r1, List<Rectangle> blockedModules)
{
foreach (var blockedMod in blockedModules)
{
if (Intersects(blockedMod, r1))
return true;
}
return false;
}
private static class MaskPattern
{
public static bool Pattern1(int x, int y)
{
return (x + y) % 2 == 0;
}
public static bool Pattern2(int x, int y)
{
return y % 2 == 0;
}
public static bool Pattern3(int x, int y)
{
return x % 3 == 0;
}
public static bool Pattern4(int x, int y)
{
return (x + y) % 3 == 0;
}
public static bool Pattern5(int x, int y)
{
return ((int)(Math.Floor(y / 2d) + Math.Floor(x / 3d)) % 2) == 0;
}
public static bool Pattern6(int x, int y)
{
return ((x * y) % 2) + ((x * y) % 3) == 0;
}
public static bool Pattern7(int x, int y)
{
return (((x * y) % 2) + ((x * y) % 3)) % 2 == 0;
}
public static bool Pattern8(int x, int y)
{
return (((x + y) % 2) + ((x * y) % 3)) % 2 == 0;
}
public static int Score(ref QRCodeData qrCode)
{
int score1 = 0,
score2 = 0,
score3 = 0,
score4 = 0;
var size = qrCode.ModuleMatrix.Count;
//Penalty 1
for (var y = 0; y < size; y++)
{
var modInRow = 0;
var modInColumn = 0;
var lastValRow = qrCode.ModuleMatrix[y][0];
var lastValColumn = qrCode.ModuleMatrix[0][y];
for (var x = 0; x < size; x++)
{
if (qrCode.ModuleMatrix[y][x] == lastValRow)
modInRow++;
else
modInRow = 1;
if (modInRow == 5)
score1 += 3;
else if (modInRow > 5)
score1++;
lastValRow = qrCode.ModuleMatrix[y][x];
if (qrCode.ModuleMatrix[x][y] == lastValColumn)
modInColumn++;
else
modInColumn = 1;
if (modInColumn == 5)
score1 += 3;
else if (modInColumn > 5)
score1++;
lastValColumn = qrCode.ModuleMatrix[x][y];
}
}
//Penalty 2
for (var y = 0; y < size - 1; y++)
{
for (var x = 0; x < size - 1; x++)
{
if (qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y][x + 1] &&
qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y + 1][x] &&
qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y + 1][x + 1])
score2 += 3;
}
}
//Penalty 3
for (var y = 0; y < size; y++)
{
for (var x = 0; x < size - 10; x++)
{
if ((qrCode.ModuleMatrix[y][x] &&
!qrCode.ModuleMatrix[y][x + 1] &&
qrCode.ModuleMatrix[y][x + 2] &&
qrCode.ModuleMatrix[y][x + 3] &&
qrCode.ModuleMatrix[y][x + 4] &&
!qrCode.ModuleMatrix[y][x + 5] &&
qrCode.ModuleMatrix[y][x + 6] &&
!qrCode.ModuleMatrix[y][x + 7] &&
!qrCode.ModuleMatrix[y][x + 8] &&
!qrCode.ModuleMatrix[y][x + 9] &&
!qrCode.ModuleMatrix[y][x + 10]) ||
(!qrCode.ModuleMatrix[y][x] &&
!qrCode.ModuleMatrix[y][x + 1] &&
!qrCode.ModuleMatrix[y][x + 2] &&
!qrCode.ModuleMatrix[y][x + 3] &&
qrCode.ModuleMatrix[y][x + 4] &&
!qrCode.ModuleMatrix[y][x + 5] &&
qrCode.ModuleMatrix[y][x + 6] &&
qrCode.ModuleMatrix[y][x + 7] &&
qrCode.ModuleMatrix[y][x + 8] &&
!qrCode.ModuleMatrix[y][x + 9] &&
qrCode.ModuleMatrix[y][x + 10]))
{
score3 += 40;
}
if ((qrCode.ModuleMatrix[x][y] &&
!qrCode.ModuleMatrix[x + 1][y] &&
qrCode.ModuleMatrix[x + 2][y] &&
qrCode.ModuleMatrix[x + 3][y] &&
qrCode.ModuleMatrix[x + 4][y] &&
!qrCode.ModuleMatrix[x + 5][y] &&
qrCode.ModuleMatrix[x + 6][y] &&
!qrCode.ModuleMatrix[x + 7][y] &&
!qrCode.ModuleMatrix[x + 8][y] &&
!qrCode.ModuleMatrix[x + 9][y] &&
!qrCode.ModuleMatrix[x + 10][y]) ||
(!qrCode.ModuleMatrix[x][y] &&
!qrCode.ModuleMatrix[x + 1][y] &&
!qrCode.ModuleMatrix[x + 2][y] &&
!qrCode.ModuleMatrix[x + 3][y] &&
qrCode.ModuleMatrix[x + 4][y] &&
!qrCode.ModuleMatrix[x + 5][y] &&
qrCode.ModuleMatrix[x + 6][y] &&
qrCode.ModuleMatrix[x + 7][y] &&
qrCode.ModuleMatrix[x + 8][y] &&
!qrCode.ModuleMatrix[x + 9][y] &&
qrCode.ModuleMatrix[x + 10][y]))
{
score3 += 40;
}
}
}
//Penalty 4
double blackModules = 0;
foreach (var row in qrCode.ModuleMatrix)
foreach (bool bit in row)
if (bit)
blackModules++;
var percent = (blackModules / (qrCode.ModuleMatrix.Count * qrCode.ModuleMatrix.Count)) * 100;
var prevMultipleOf5 = Math.Abs((int)Math.Floor(percent / 5) * 5 - 50) / 5;
var nextMultipleOf5 = Math.Abs((int)Math.Floor(percent / 5) * 5 - 45) / 5;
score4 = Math.Min(prevMultipleOf5, nextMultipleOf5) * 10;
return score1 + score2 + score3 + score4;
}
}
}
private static List<string> CalculateECCWords(string bitString, ECCInfo eccInfo)
{
var eccWords = eccInfo.ECCPerBlock;
var messagePolynom = CalculateMessagePolynom(bitString);
var generatorPolynom = CalculateGeneratorPolynom(eccWords);
for (var i = 0; i < messagePolynom.PolyItems.Count; i++)
messagePolynom.PolyItems[i] = new PolynomItem(messagePolynom.PolyItems[i].Coefficient,
messagePolynom.PolyItems[i].Exponent + eccWords);
for (var i = 0; i < generatorPolynom.PolyItems.Count; i++)
generatorPolynom.PolyItems[i] = new PolynomItem(generatorPolynom.PolyItems[i].Coefficient,
generatorPolynom.PolyItems[i].Exponent + (messagePolynom.PolyItems.Count - 1));
var leadTermSource = messagePolynom;
for (var i = 0; (leadTermSource.PolyItems.Count > 0 && leadTermSource.PolyItems[leadTermSource.PolyItems.Count - 1].Exponent > 0); i++)
{
if (leadTermSource.PolyItems[0].Coefficient == 0)
{
leadTermSource.PolyItems.RemoveAt(0);
leadTermSource.PolyItems.Add(new PolynomItem(0, leadTermSource.PolyItems[leadTermSource.PolyItems.Count - 1].Exponent - 1));
}
else
{
var resPoly = MultiplyGeneratorPolynomByLeadterm(generatorPolynom, ConvertToAlphaNotation(leadTermSource).PolyItems[0], i);
resPoly = ConvertToDecNotation(resPoly);
resPoly = XORPolynoms(leadTermSource, resPoly);
leadTermSource = resPoly;
}
}
return leadTermSource.PolyItems.Select(x => DecToBin(x.Coefficient, 8)).ToList();
}
private static Polynom ConvertToAlphaNotation(Polynom poly)
{
var newPoly = new Polynom();
for (var i = 0; i < poly.PolyItems.Count; i++)
newPoly.PolyItems.Add(
new PolynomItem(
(poly.PolyItems[i].Coefficient != 0
? GetAlphaExpFromIntVal(poly.PolyItems[i].Coefficient)
: 0), poly.PolyItems[i].Exponent));
return newPoly;
}
private static Polynom ConvertToDecNotation(Polynom poly)
{
var newPoly = new Polynom();
for (var i = 0; i < poly.PolyItems.Count; i++)
newPoly.PolyItems.Add(new PolynomItem(GetIntValFromAlphaExp(poly.PolyItems[i].Coefficient), poly.PolyItems[i].Exponent));
return newPoly;
}
private static int GetVersion(int length, EncodingMode encMode, ECCLevel eccLevel)
{
var fittingVersions = capacityTable.Where(
x => x.Details.Any(
y => (y.ErrorCorrectionLevel == eccLevel
&& y.CapacityDict[encMode] >= Convert.ToInt32(length)
)
)
).Select(x => new
{
version = x.Version,
capacity = x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel)
.CapacityDict[encMode]
});
if (fittingVersions.Any())
return fittingVersions.Min(x => x.version);
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);
}
private static EncodingMode GetEncodingFromPlaintext(string plainText, bool forceUtf8)
{
if (forceUtf8) return EncodingMode.Byte;
EncodingMode result = EncodingMode.Numeric; // assume numeric
foreach (char c in plainText)
{
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)) continue; // alphanumeric
return EncodingMode.Byte; // not numeric or alphanumeric, assume byte
}
return result; // either numeric or alphanumeric
}
private static bool IsInRange(char c, char min, char max)
{
return (uint)(c - min) <= (uint)(max - min);
}
private static Polynom CalculateMessagePolynom(string bitString)
{
var messagePol = new Polynom();
for (var i = bitString.Length / 8 - 1; i >= 0; i--)
{
messagePol.PolyItems.Add(new PolynomItem(BinToDec(bitString.Substring(0, 8)), i));
bitString = bitString.Remove(0, 8);
}
return messagePol;
}
private static Polynom CalculateGeneratorPolynom(int numEccWords)
{
var generatorPolynom = new Polynom();
generatorPolynom.PolyItems.AddRange(new[]{
new PolynomItem(0,1),
new PolynomItem(0,0)
});
for (var i = 1; i <= numEccWords - 1; i++)
{
var multiplierPolynom = new Polynom();
multiplierPolynom.PolyItems.AddRange(new[]{
new PolynomItem(0,1),
new PolynomItem(i,0)
});
generatorPolynom = MultiplyAlphaPolynoms(generatorPolynom, multiplierPolynom);
}
return generatorPolynom;
}
private static List<string> BinaryStringToBitBlockList(string bitString)
{
const int blockSize = 8;
var numberOfBlocks = (int)Math.Ceiling(bitString.Length / (double)blockSize);
var blocklist = new List<string>(numberOfBlocks);
for (int i = 0; i < bitString.Length; i += blockSize)
{
blocklist.Add(bitString.Substring(i, blockSize));
}
return blocklist;
}
private static List<int> BinaryStringListToDecList(List<string> binaryStringList)
{
return binaryStringList.Select(binaryString => BinToDec(binaryString)).ToList();
}
private static int BinToDec(string binStr)
{
return Convert.ToInt32(binStr, 2);
}
private static string DecToBin(int decNum)
{
return Convert.ToString(decNum, 2);
}
private static string DecToBin(int decNum, int padLeftUpTo)
{
var binStr = DecToBin(decNum);
return binStr.PadLeft(padLeftUpTo, '0');
}
private static int GetCountIndicatorLength(int version, EncodingMode encMode)
{
if (version < 10)
{
if (encMode == EncodingMode.Numeric)
return 10;
else if (encMode == EncodingMode.Alphanumeric)
return 9;
else
return 8;
}
else if (version < 27)
{
if (encMode == EncodingMode.Numeric)
return 12;
else if (encMode == EncodingMode.Alphanumeric)
return 11;
else if (encMode == EncodingMode.Byte)
return 16;
else
return 10;
}
else
{
if (encMode == EncodingMode.Numeric)
return 14;
else if (encMode == EncodingMode.Alphanumeric)
return 13;
else if (encMode == EncodingMode.Byte)
return 16;
else
return 12;
}
}
private static int GetDataLength(EncodingMode encoding, string plainText, string codedText, bool forceUtf8)
{
return forceUtf8 || IsUtf8(encoding, plainText, forceUtf8) ? (codedText.Length / 8) : plainText.Length;
}
private static bool IsUtf8(EncodingMode encoding, string plainText, bool forceUtf8)
{
return (encoding == EncodingMode.Byte && (!IsValidISO(plainText) || forceUtf8));
}
private static bool IsValidISO(string input)
{
var bytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(input);
var result = Encoding.GetEncoding("ISO-8859-1").GetString(bytes);
return String.Equals(input, result);
}
private static string PlainTextToBinary(string plainText, EncodingMode encMode, EciMode eciMode, bool utf8BOM, bool forceUtf8)
{
switch (encMode)
{
case EncodingMode.Alphanumeric:
return PlainTextToBinaryAlphanumeric(plainText);
case EncodingMode.Numeric:
return PlainTextToBinaryNumeric(plainText);
case EncodingMode.Byte:
return PlainTextToBinaryByte(plainText, eciMode, utf8BOM, forceUtf8);
case EncodingMode.Kanji:
return string.Empty;
case EncodingMode.ECI:
default:
return string.Empty;
}
}
private static string PlainTextToBinaryNumeric(string plainText)
{
var codeText = string.Empty;
while (plainText.Length >= 3)
{
var dec = Convert.ToInt32(plainText.Substring(0, 3));
codeText += DecToBin(dec, 10);
plainText = plainText.Substring(3);
}
if (plainText.Length == 2)
{
var dec = Convert.ToInt32(plainText);
codeText += DecToBin(dec, 7);
}
else if (plainText.Length == 1)
{
var dec = Convert.ToInt32(plainText);
codeText += DecToBin(dec, 4);
}
return codeText;
}
private static string PlainTextToBinaryAlphanumeric(string plainText)
{
var codeText = string.Empty;
while (plainText.Length >= 2)
{
var token = plainText.Substring(0, 2);
var dec = alphanumEncDict[token[0]] * 45 + alphanumEncDict[token[1]];
codeText += DecToBin(dec, 11);
plainText = plainText.Substring(2);
}
if (plainText.Length > 0)
{
codeText += DecToBin(alphanumEncDict[plainText[0]], 6);
}
return codeText;
}
private string PlainTextToBinaryECI(string plainText)
{
var codeText = string.Empty;
byte[] _bytes = Encoding.GetEncoding("ascii").GetBytes(plainText);
foreach (byte _byte in _bytes)
{
codeText += DecToBin(_byte, 8);
}
return codeText;
}
private static string ConvertToIso8859(string value, string Iso = "ISO-8859-2")
{
Encoding iso = Encoding.GetEncoding(Iso);
Encoding utf8 = Encoding.UTF8;
byte[] utfBytes = utf8.GetBytes(value);
byte[] isoBytes = Encoding.Convert(utf8, iso, utfBytes);
return iso.GetString(isoBytes);
}
private static string PlainTextToBinaryByte(string plainText, EciMode eciMode, bool utf8BOM, bool forceUtf8)
{
byte[] codeBytes;
var codeText = string.Empty;
if (IsValidISO(plainText) && !forceUtf8)
codeBytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(plainText);
else
{
switch (eciMode)
{
case EciMode.Iso8859_1:
codeBytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(ConvertToIso8859(plainText, "ISO-8859-1"));
break;
case EciMode.Iso8859_2:
codeBytes = Encoding.GetEncoding("ISO-8859-2").GetBytes(ConvertToIso8859(plainText, "ISO-8859-2"));
break;
case EciMode.Default:
case EciMode.Utf8:
default:
codeBytes = utf8BOM ? Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(plainText)).ToArray() : Encoding.UTF8.GetBytes(plainText);
break;
}
}
foreach (var b in codeBytes)
codeText += DecToBin(b, 8);
return codeText;
}
private static Polynom XORPolynoms(Polynom messagePolynom, Polynom resPolynom)
{
var resultPolynom = new Polynom();
Polynom longPoly, shortPoly;
if (messagePolynom.PolyItems.Count >= resPolynom.PolyItems.Count)
{
longPoly = messagePolynom;
shortPoly = resPolynom;
}
else
{
longPoly = resPolynom;
shortPoly = messagePolynom;
}
for (var i = 0; i < longPoly.PolyItems.Count; i++)
{
var polItemRes = new PolynomItem
(
longPoly.PolyItems[i].Coefficient ^
(shortPoly.PolyItems.Count > i ? shortPoly.PolyItems[i].Coefficient : 0),
messagePolynom.PolyItems[0].Exponent - i
);
resultPolynom.PolyItems.Add(polItemRes);
}
resultPolynom.PolyItems.RemoveAt(0);
return resultPolynom;
}
private static Polynom MultiplyGeneratorPolynomByLeadterm(Polynom genPolynom, PolynomItem leadTerm, int lowerExponentBy)
{
var resultPolynom = new Polynom();
foreach (var polItemBase in genPolynom.PolyItems)
{
var polItemRes = new PolynomItem(
(polItemBase.Coefficient + leadTerm.Coefficient) % 255,
polItemBase.Exponent - lowerExponentBy
);
resultPolynom.PolyItems.Add(polItemRes);
}
return resultPolynom;
}
private static Polynom MultiplyAlphaPolynoms(Polynom polynomBase, Polynom polynomMultiplier)
{
var resultPolynom = new Polynom();
foreach (var polItemBase in polynomMultiplier.PolyItems)
{
foreach (var polItemMulti in polynomBase.PolyItems)
{
var polItemRes = new PolynomItem
(
ShrinkAlphaExp(polItemBase.Coefficient + polItemMulti.Coefficient),
(polItemBase.Exponent + polItemMulti.Exponent)
);
resultPolynom.PolyItems.Add(polItemRes);
}
}
var exponentsToGlue = resultPolynom.PolyItems.GroupBy(x => x.Exponent).Where(x => x.Count() > 1).Select(x => x.First().Exponent);
var toGlue = exponentsToGlue as IList<int> ?? exponentsToGlue.ToList();
var gluedPolynoms = new List<PolynomItem>(toGlue.Count);
foreach (var exponent in toGlue)
{
var coefficient = resultPolynom.PolyItems.Where(x => x.Exponent == exponent).Aggregate(0, (current, polynomOld)
=> current ^ GetIntValFromAlphaExp(polynomOld.Coefficient));
var polynomFixed = new PolynomItem(GetAlphaExpFromIntVal(coefficient), exponent);
gluedPolynoms.Add(polynomFixed);
}
resultPolynom.PolyItems.RemoveAll(x => toGlue.Contains(x.Exponent));
resultPolynom.PolyItems.AddRange(gluedPolynoms);
resultPolynom.PolyItems.Sort((x, y) => -x.Exponent.CompareTo(y.Exponent));
return resultPolynom;
}
private static int GetIntValFromAlphaExp(int exp)
{
return galoisField.Find(alog => alog.ExponentAlpha == exp).IntegerValue;
}
private static int GetAlphaExpFromIntVal(int intVal)
{
return galoisField.Find(alog => alog.IntegerValue == intVal).ExponentAlpha;
}
private static int ShrinkAlphaExp(int alphaExp)
{
// ReSharper disable once PossibleLossOfFraction
return (int)((alphaExp % 256) + Math.Floor((double)(alphaExp / 256)));
}
private static Dictionary<char, int> CreateAlphanumEncDict()
{
var localAlphanumEncDict = new Dictionary<char, int>(45);
//Add numbers
for (int i = 0; i < 10; i++)
localAlphanumEncDict.Add($"{i}"[0], i);
//Add chars
for (char c = 'A'; c <= 'Z'; c++)
localAlphanumEncDict.Add(c, localAlphanumEncDict.Count());
//Add special chars
for (int i = 0; i < alphanumEncTable.Length; i++)
localAlphanumEncDict.Add(alphanumEncTable[i], localAlphanumEncDict.Count());
return localAlphanumEncDict;
}
private static List<AlignmentPattern> CreateAlignmentPatternTable()
{
var localAlignmentPatternTable = new List<AlignmentPattern>(40);
for (var i = 0; i < (7 * 40); i = i + 7)
{
var points = new List<Point>();
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);
}
}
}
}
localAlignmentPatternTable.Add(new AlignmentPattern()
{
Version = (i + 7) / 7,
PatternPositions = points
}
);
}
return localAlignmentPatternTable;
}
private static List<ECCInfo> CreateCapacityECCTable()
{
var localCapacityECCTable = new List<ECCInfo>(160);
for (var i = 0; i < (4 * 6 * 40); i = 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;
}
private static List<VersionInfo> CreateCapacityTable()
{
var localCapacityTable = new List<VersionInfo>(40);
for (var i = 0; i < (16 * 40); i = i + 16)
{
localCapacityTable.Add(new VersionInfo(
(i + 16) / 16,
new List<VersionInfoDetails>(4)
{
new VersionInfoDetails(
ECCLevel.L,
new Dictionary<EncodingMode,int>(){
{ 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,int>(){
{ 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,int>(){
{ 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,int>(){
{ EncodingMode.Numeric, capacityBaseValues[i+12] },
{ EncodingMode.Alphanumeric, capacityBaseValues[i+13] },
{ EncodingMode.Byte, capacityBaseValues[i+14] },
{ EncodingMode.Kanji, capacityBaseValues[i+15] },
}
)
}
));
}
return localCapacityTable;
}
private static List<Antilog> CreateAntilogTable()
{
var localGaloisField = new List<Antilog>(256);
int gfItem = 1;
for (var i = 0; i < 256; i++)
{
localGaloisField.Add(new Antilog(i, gfItem));
gfItem *= 2;
if (gfItem > 255)
gfItem ^= 285;
}
return localGaloisField;
}
/// <summary>
/// Error correction level. These define the tolerance levels for how much of the code can be lost before the code cannot be recovered.
/// </summary>
public enum ECCLevel
{
/// <summary>
/// 7% may be lost before recovery is not possible
/// </summary>
L,
/// <summary>
/// 15% may be lost before recovery is not possible
/// </summary>
M,
/// <summary>
/// 25% may be lost before recovery is not possible
/// </summary>
Q,
/// <summary>
/// 30% may be lost before recovery is not possible
/// </summary>
H
}
private enum EncodingMode
{
Numeric = 1,
Alphanumeric = 2,
Byte = 4,
Kanji = 8,
ECI = 7
}
private struct AlignmentPattern
{
public int Version;
public List<Point> PatternPositions;
}
private struct CodewordBlock
{
public CodewordBlock(int groupNumber, int blockNumber, string bitString, List<string> codeWords,
List<string> eccWords, List<int> codeWordsInt, List<int> eccWordsInt)
{
this.GroupNumber = groupNumber;
this.BlockNumber = blockNumber;
this.BitString = bitString;
this.CodeWords = codeWords;
this.ECCWords = eccWords;
this.CodeWordsInt = codeWordsInt;
this.ECCWordsInt = eccWordsInt;
}
public int GroupNumber { get; }
public int BlockNumber { get; }
public string BitString { get; }
public List<string> CodeWords { get; }
public List<int> CodeWordsInt { get; }
public List<string> ECCWords { get; }
public List<int> ECCWordsInt { get; }
}
private struct ECCInfo
{
public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int eccPerBlock, int blocksInGroup1,
int codewordsInGroup1, int blocksInGroup2, int codewordsInGroup2)
{
this.Version = version;
this.ErrorCorrectionLevel = errorCorrectionLevel;
this.TotalDataCodewords = totalDataCodewords;
this.ECCPerBlock = eccPerBlock;
this.BlocksInGroup1 = blocksInGroup1;
this.CodewordsInGroup1 = codewordsInGroup1;
this.BlocksInGroup2 = blocksInGroup2;
this.CodewordsInGroup2 = codewordsInGroup2;
}
public int Version { get; }
public ECCLevel ErrorCorrectionLevel { get; }
public int TotalDataCodewords { get; }
public int ECCPerBlock { get; }
public int BlocksInGroup1 { get; }
public int CodewordsInGroup1 { get; }
public int BlocksInGroup2 { get; }
public int CodewordsInGroup2 { get; }
}
private struct VersionInfo
{
public VersionInfo(int version, List<VersionInfoDetails> versionInfoDetails)
{
this.Version = version;
this.Details = versionInfoDetails;
}
public int Version { get; }
public List<VersionInfoDetails> Details { get; }
}
private struct VersionInfoDetails
{
public VersionInfoDetails(ECCLevel errorCorrectionLevel, Dictionary<EncodingMode, int> capacityDict)
{
this.ErrorCorrectionLevel = errorCorrectionLevel;
this.CapacityDict = capacityDict;
}
public ECCLevel ErrorCorrectionLevel { get; }
public Dictionary<EncodingMode, int> CapacityDict { get; }
}
private struct Antilog
{
public Antilog(int exponentAlpha, int integerValue)
{
this.ExponentAlpha = exponentAlpha;
this.IntegerValue = integerValue;
}
public int ExponentAlpha { get; }
public int IntegerValue { get; }
}
private struct PolynomItem
{
public PolynomItem(int coefficient, int exponent)
{
this.Coefficient = coefficient;
this.Exponent = exponent;
}
public int Coefficient { get; }
public int Exponent { get; }
}
private class Polynom
{
public Polynom()
{
this.PolyItems = new List<PolynomItem>();
}
public List<PolynomItem> PolyItems { get; set; }
public override string ToString()
{
var sb = new StringBuilder();
//this.PolyItems.ForEach(x => sb.Append("a^" + x.Coefficient + "*x^" + x.Exponent + " + "));
foreach (var polyItem in this.PolyItems)
{
sb.Append("a^" + polyItem.Coefficient + "*x^" + polyItem.Exponent + " + ");
}
return sb.ToString().TrimEnd(new[] { ' ', '+' });
}
}
private class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
this.X = x;
this.Y = y;
}
}
private class Rectangle
{
public int X { get; }
public int Y { get; }
public int Width { get; }
public int Height { get; }
public Rectangle(int x, int y, int w, int h)
{
this.X = x;
this.Y = y;
this.Width = w;
this.Height = h;
}
}
}
}
@yasirkula
Copy link
Author

yasirkula commented Jun 15, 2024

How To

I've basically combined QRCoder library (MIT License) into a single script while removing NuGet dependencies, non-essential classes and Unity-incompatible classes. The resulting script works inside Unity without requiring any external dependencies.

Texture2D qrCodeTexture = QRCodeGenerator.GenerateQrCode("https://gist.github.com/yasirkula", QRCodeGenerator.ECCLevel.M).ToTexture2D(false, (qrSize) =>
{
    // QR code will be magnified 5x times. So, if the original size (qrSize) was 50x50, the resulting Texture2D will be 250x250.
    // If you'll be displaying the QR code on a mesh or UI, there is no need to magnify.
    return 5;
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment