/* * QR code generator library (.NET) * * Copyright (c) Manuel Bleichenbacher (MIT License) * https://github.com/manuelbl/QrCodeGenerator * Copyright (c) Project Nayuki (MIT License) * https://www.nayuki.io/page/qr-code-generator-library * * 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. */ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Text; namespace zero.Rendering.QrCode; /// /// Represents a QR code containing text or binary data. /// /// Instances of this class represent an immutable square grid of dark and light pixels /// (called modules by the QR code specification). /// Static factory methods are provided to create QR codes from text or binary data. /// Some of the methods provide detailed control about the encoding parameters such a QR /// code size (called version by the standard), error correction level and mask. /// /// /// QR codes are a type of two-dimensional barcodes, invented by Denso Wave and /// described in the ISO/IEC 18004 standard. /// /// /// This class covers the QR Code Model 2 specification, supporting all versions (sizes) /// from 1 to 40, all 4 error correction levels, and 4 character encoding modes. /// /// /// /// To create a QR code instance: /// /// /// /// public class QrCode { #region Static factory functions (high level) /// /// Creates a QR code representing the specified text using the specified error correction level. /// /// As a conservative upper bound, this function is guaranteed to succeed for strings with up to 738 /// Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible /// QR code version (size) is automatically chosen. The resulting ECC level will be higher than the one /// specified if it can be achieved without increasing the size (version). /// /// /// The text to be encoded. The full range of Unicode characters may be used. /// The minimum error correction level to use. /// The created QR code instance representing the specified text. /// or is null. /// The text is too long to fit in the largest QR code size (version) /// at the specified error correction level. public static QrCode EncodeText(string text, Ecc ecl) { Objects.RequireNonNull(text); Objects.RequireNonNull(ecl); var segments = QrSegment.MakeSegments(text); return EncodeSegments(segments, ecl); } /// /// Creates a QR code representing the specified binary data using the specified error correction level. /// /// This function encodes the data in the binary segment mode. The maximum number of /// bytes allowed is 2953. The smallest possible QR code version is automatically chosen. /// The resulting ECC level will be higher than the one specified if it can be achieved without increasing the size (version). /// /// /// The binary data to encode. /// The minimum error correction level to use. /// The created QR code representing the specified data. /// or is null. /// The specified data is too long to fit in the largest QR code size (version) /// at the specified error correction level. public static QrCode EncodeBinary(byte[] data, Ecc ecl) { Objects.RequireNonNull(data); Objects.RequireNonNull(ecl); var seg = QrSegment.MakeBytes(data); return EncodeSegments(new List { seg }, ecl); } #endregion #region Static factory functions (mid level) /// /// Creates a QR code representing the specified segments with the specified encoding parameters. /// /// The smallest possible QR code version (size) is used. The range of versions can be /// restricted by the and parameters. /// /// /// If is true, the resulting ECC level will be higher than the /// one specified if it can be achieved without increasing the size (version). /// /// /// The QR code mask is usually automatically chosen. It can be explicitly set with the /// parameter by using a value between 0 to 7 (inclusive). -1 is for automatic mode (which may be slow). /// /// /// This function allows the user to create a custom sequence of segments that switches /// between modes (such as alphanumeric and byte) to encode text in less space and gives full control over all /// encoding parameters. /// /// /// /// This is a mid-level API; the high-level APIs are /// and . /// /// The segments to encode. /// The minimal or fixed error correction level to use . /// The minimum version (size) of the QR code (between 1 and 40). /// The maximum version (size) of the QR code (between 1 and 40). /// The mask number to use (between 0 and 7), or -1 for automatic mask selection. /// If true the ECC level wil be increased if it can be achieved without increasing the size (version). /// The created QR code representing the segments. /// , any list element, or is null. /// 1 ≤ minVersion ≤ maxVersion ≤ 40 /// or -1 ≤ mask ≤ 7 is violated. /// The segments are too long to fit in the largest QR code size (version) /// at the specified error correction level. public static QrCode EncodeSegments(List segments, Ecc ecl, int minVersion = MIN_VERSION, int maxVersion = MAX_VERSION, int mask = -1, bool boostEcl = true) { Objects.RequireNonNull(segments); Objects.RequireNonNull(ecl); if (minVersion < MIN_VERSION || minVersion > maxVersion) { throw new ArgumentOutOfRangeException(nameof(minVersion), "Invalid value"); } if (maxVersion > MAX_VERSION) { throw new ArgumentOutOfRangeException(nameof(maxVersion), "Invalid value"); } if (mask < -1 || mask > 7) { throw new ArgumentOutOfRangeException(nameof(mask), "Invalid value"); } // Find the minimal version number to use int version, dataUsedBits; for (version = minVersion;; version++) { var numDataBits = GetNumDataCodewords(version, ecl) * 8; // Number of data bits available dataUsedBits = QrSegment.GetTotalBits(segments, version); if (dataUsedBits != -1 && dataUsedBits <= numDataBits) { break; // This version number is found to be suitable } if (version < maxVersion) { continue; } // All versions in the range could not fit the given data var msg = "Segment too long"; if (dataUsedBits != -1) { msg = $"Data length = {dataUsedBits} bits, Max capacity = {numDataBits} bits"; } throw new DataTooLongException(msg); } // Increase the error correction level while the data still fits in the current version number foreach (var newEcl in Ecc.AllValues) { // From low to high if (boostEcl && dataUsedBits <= GetNumDataCodewords(version, newEcl) * 8) { ecl = newEcl; } } // Concatenate all segments to create the data bit string var ba = new BitArray(0); foreach (var seg in segments) { ba.AppendBits(seg.EncodingMode.ModeBits, 4); ba.AppendBits((uint)seg.NumChars, seg.EncodingMode.NumCharCountBits(version)); ba.AppendData(seg.GetData()); } Debug.Assert(ba.Length == dataUsedBits); // Add terminator and pad up to a byte if applicable var dataCapacityBits = GetNumDataCodewords(version, ecl) * 8; Debug.Assert(ba.Length <= dataCapacityBits); ba.AppendBits(0, Math.Min(4, dataCapacityBits - ba.Length)); ba.AppendBits(0, (8 - ba.Length % 8) % 8); Debug.Assert(ba.Length % 8 == 0); // Pad with alternating bytes until data capacity is reached for (uint padByte = 0xEC; ba.Length < dataCapacityBits; padByte ^= 0xEC ^ 0x11) { ba.AppendBits(padByte, 8); } // Pack bits into bytes in big endian var dataCodewords = new byte[ba.Length / 8]; for (var i = 0; i < ba.Length; i++) { if (ba.Get(i)) { dataCodewords[i >> 3] |= (byte)(1 << (7 - (i & 7))); } } // Create the QR code object return new QrCode(version, ecl, dataCodewords, mask); } #endregion #region Public immutable properties /// /// The version (size) of this QR code (between 1 for the smallest and 40 for the biggest). /// /// The QR code version (size). public int Version { get; } /// /// The width and height of this QR code, in modules (pixels). /// The size is a value between 21 and 177. /// This is equal to version × 4 + 17. /// /// The QR code size. public int Size { get; } /// /// The error correction level used for this QR code. /// /// The error correction level. public Ecc ErrorCorrectionLevel { get; } /// /// The index of the mask pattern used fort this QR code (between 0 and 7). /// /// Even if a QR code is created with automatic mask selection (mask = 1), /// this property returns the effective mask used. /// /// /// The mask pattern index. public int Mask { get; } #endregion #region Private grids of modules/pixels, with dimensions of size * size // The modules of this QR code (false = light, true = dark). // Immutable after constructor finishes. Accessed through GetModule(). private readonly bool[] _modules; // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. private readonly bool[] _isFunction; #endregion #region Constructor (low level) /// /// Constructs a QR code with the specified version number, /// error correction level, data codeword bytes, and mask number. /// /// /// This is a low-level API that most users should not use directly. A mid-level /// API is the function. /// /// The version (size) to use (between 1 to 40). /// The error correction level to use. /// The bytes representing segments to encode (without ECC). /// The mask pattern to use (either -1 for automatic selection, or a value from 0 to 7 for fixed choice). /// or is null. /// The version or mask value is out of range, /// or the data has an invalid length for the specified version and error correction level. public QrCode(int version, Ecc ecl, byte[] dataCodewords, int mask = -1) { // Check arguments and initialize fields if (version < MIN_VERSION || version > MAX_VERSION) { throw new ArgumentOutOfRangeException(nameof(version), "Version value out of range"); } if (mask < -1 || mask > 7) { throw new ArgumentOutOfRangeException(nameof(mask), "Mask value out of range"); } Version = version; Size = version * 4 + 17; Objects.RequireNonNull(ecl); ErrorCorrectionLevel = ecl; Objects.RequireNonNull(dataCodewords); _modules = new bool[Size * Size]; // Initially all light _isFunction = new bool[Size * Size]; // Compute ECC, draw modules, do masking DrawFunctionPatterns(); var allCodewords = AddEccAndInterleave(dataCodewords); DrawCodewords(allCodewords); Mask = HandleConstructorMasking(mask); _isFunction = null; } #endregion #region Public methods /// /// Gets the color of the module (pixel) at the specified coordinates. /// /// The top left corner has the coordinates (x=0, y=0). x-coordinates extend from left to right, /// y-coordinates extend from top to bottom. /// /// /// If coordinates outside the bounds of this QR code are specified, light (false) is returned. /// /// /// The x coordinate. /// The y coordinate. /// The color of the specified module: true for dark modules and false /// for light modules (or if the coordinates are outside the bounds). public bool GetModule(int x, int y) { return 0 <= x && x < Size && 0 <= y && y < Size && _modules[y * Size + x]; } /// /// Creates an SVG image of this QR code. /// /// The images uses Unix newlines (\n), regardless of the platform. /// /// /// The border width, as a factor of the module (QR code pixel) size /// The SVG image as a string. public string ToSvgString(int border) { return ToSvgString(border, "#000000", "#ffffff"); } /// /// Creates an SVG image of this QR code. /// /// The images uses Unix newlines (\n), regardless of the platform. /// /// /// Colors are specified using CSS color data type. Examples of valid values are /// "#339966", "fuchsia", "rgba(137, 23, 89, 0.3)". /// /// /// The border width, as a factor of the module (QR code pixel) size /// The foreground color. /// The background color. public string ToSvgString(int border, string foreground, string background) { if (border < 0) { throw new ArgumentOutOfRangeException(nameof(border), "Border must be non-negative"); } var dim = Size + border * 2; var sb = new StringBuilder() .Append("\n") .Append( "\n") .Append( $"\n") .Append($"\t\n") .Append("\t\n") .Append("\n") .ToString(); } /// /// Creates a graphics path of this QR code valid in SVG or XAML. /// /// The graphics path uses a coordinate system where each module is 1 unit wide and tall, /// and the top left module is offset vertically and horizontally by border units. /// /// /// Note that a border width other than 0 only make sense if the bounding box of the QR code /// is explicitly set by the graphics using this path. If the bounding box of this path is /// automatically derived, at least the right and bottom border will be missing. /// /// /// The path will look like this: M3,3h7v1h-7z M12,3h1v4h-1z ... M70,71h1v1h-1z. It /// is valid for SVG (<path d="M3,3h..." />) and for XAML /// (<Path Data="M3,3h..." />). For programmatic geometry creation in WPF see /// Geometry.Parse(String). /// /// /// The border width, as a factor of the module (QR code pixel) size /// The graphics path /// Thrown if border is negative public string ToGraphicsPath(int border = 0) { if (border < 0) { throw new ArgumentOutOfRangeException(nameof(border), "Border must be non-negative"); } // Work on copy as it is destructive var modules = CopyModules(); var path = new StringBuilder(); CreatePath(path, modules, border); return path.ToString(); } #endregion #region Graphics path // Append a SVG/XAML path for the QR code to the provided string builder private static void CreatePath(StringBuilder path, bool[,] modules, int border) { // Simple algorithms to reduce the number of rectangles for drawing the QR code // and reduce SVG/XAML size. var size = modules.GetLength(0); for (var y = 0; y < size; y++) { for (var x = 0; x < size; x++) { if (modules[y, x]) { DrawLargestRectangle(path, modules, x, y, border); } } } } // Find, draw and clear largest rectangle with (x, y) as the top left corner private static void DrawLargestRectangle(StringBuilder path, bool[,] modules, int x, int y, int border) { var size = modules.GetLength(0); var bestW = 1; var bestH = 1; var maxArea = 1; var xLimit = size; var iy = y; while (iy < size && modules[iy, x]) { var w = 0; while (x + w < xLimit && modules[iy, x + w]) { w++; } var area = w * (iy - y + 1); if (area > maxArea) { maxArea = area; bestW = w; bestH = iy - y + 1; } xLimit = x + w; iy++; } // append path command if (x != 0 || y != 0) { path.Append(" "); } path.Append($"M{x + border},{y + border}h{bestW}v{bestH}h{-bestW}z"); // clear processed modules ClearRectangle(modules, x, y, bestW, bestH); } // Clear a rectangle of modules private static void ClearRectangle(bool[,] modules, int x, int y, int width, int height) { for (var iy = y; iy < y + height; iy++) { for (var ix = x; ix < x + width; ix++) { modules[iy, ix] = false; } } } // Create a copy of the modules (in row-major order) private bool[,] CopyModules() { var modules = new bool[Size, Size]; var index = 0; for (var y = 0; y < Size; y++) { for (var x = 0; x < Size; x++) { modules[y, x] = _modules[index]; index += 1; } } return modules; } #endregion #region Private helper methods for constructor: Drawing function modules // Reads this object's version field, and draws and marks all function modules. private void DrawFunctionPatterns() { // Draw horizontal and vertical timing patterns for (var i = 0; i < Size; i++) { SetFunctionModule(6, i, i % 2 == 0); SetFunctionModule(i, 6, i % 2 == 0); } // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) DrawFinderPattern(3, 3); DrawFinderPattern(Size - 4, 3); DrawFinderPattern(3, Size - 4); // Draw numerous alignment patterns var alignPatPos = GetAlignmentPatternPositions(); var numAlign = alignPatPos.Length; for (var i = 0; i < numAlign; i++) { for (var j = 0; j < numAlign; j++) { // Don't draw on the three finder corners if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0)) { DrawAlignmentPattern(alignPatPos[i], alignPatPos[j]); } } } // Draw configuration data DrawFormatBits(0); // Dummy mask value; overwritten later in the constructor DrawVersion(); } // Draws two copies of the format bits (with its own error correction code) // based on the given mask and this object's error correction level field. private void DrawFormatBits(uint mask) { // Calculate error correction code and pack bits var data = (ErrorCorrectionLevel.FormatBits << 3) | mask; // errCorrLvl is uint2, mask is uint3 var rem = data; for (var i = 0; i < 10; i++) { rem = (rem << 1) ^ ((rem >> 9) * 0x537); } var bits = ((data << 10) | rem) ^ 0x5412; // uint15 Debug.Assert(bits >> 15 == 0); // Draw first copy for (var i = 0; i <= 5; i++) { SetFunctionModule(8, i, GetBit(bits, i)); } SetFunctionModule(8, 7, GetBit(bits, 6)); SetFunctionModule(8, 8, GetBit(bits, 7)); SetFunctionModule(7, 8, GetBit(bits, 8)); for (var i = 9; i < 15; i++) { SetFunctionModule(14 - i, 8, GetBit(bits, i)); } // Draw second copy for (var i = 0; i < 8; i++) { SetFunctionModule(Size - 1 - i, 8, GetBit(bits, i)); } for (var i = 8; i < 15; i++) { SetFunctionModule(8, Size - 15 + i, GetBit(bits, i)); } SetFunctionModule(8, Size - 8, true); // Always dark } // Draws two copies of the version bits (with its own error correction code), // based on this object's version field, iff 7 <= version <= 40. private void DrawVersion() { if (Version < 7) { return; } // Calculate error correction code and pack bits var rem = (uint)Version; // version is uint6, in the range [7, 40] for (var i = 0; i < 12; i++) { rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); } var bits = ((uint)Version << 12) | rem; // uint18 Debug.Assert(bits >> 18 == 0); // Draw two copies for (var i = 0; i < 18; i++) { var bit = GetBit(bits, i); var a = Size - 11 + i % 3; var b = i / 3; SetFunctionModule(a, b, bit); SetFunctionModule(b, a, bit); } } // Draws a 9*9 finder pattern including the border separator, // with the center module at (x, y). Modules can be out of bounds. private void DrawFinderPattern(int x, int y) { for (var dy = -4; dy <= 4; dy++) { for (var dx = -4; dx <= 4; dx++) { var dist = Math.Max(Math.Abs(dx), Math.Abs(dy)); // Chebyshev/infinity norm int xx = x + dx, yy = y + dy; if (0 <= xx && xx < Size && 0 <= yy && yy < Size) { SetFunctionModule(xx, yy, dist != 2 && dist != 4); } } } } // Draws a 5*5 alignment pattern, with the center module // at (x, y). All modules must be in bounds. private void DrawAlignmentPattern(int x, int y) { for (var dy = -2; dy <= 2; dy++) { for (var dx = -2; dx <= 2; dx++) { SetFunctionModule(x + dx, y + dy, Math.Max(Math.Abs(dx), Math.Abs(dy)) != 1); } } } // Sets the color of a module and marks it as a function module. // Only used by the constructor. Coordinates must be in bounds. private void SetFunctionModule(int x, int y, bool isDark) { _modules[y * Size + x] = isDark; _isFunction[y * Size + x] = true; } #endregion #region Private helper methods for constructor: Codewords and masking // Returns a new byte string representing the given data with the appropriate error correction // codewords appended to it, based on this object's version and error correction level. private byte[] AddEccAndInterleave(byte[] data) { Objects.RequireNonNull(data); if (data.Length != GetNumDataCodewords(Version, ErrorCorrectionLevel)) { throw new ArgumentOutOfRangeException(); } // Calculate parameter numbers int numBlocks = NumErrorCorrectionBlocks[ErrorCorrectionLevel.Ordinal, Version]; int blockEccLen = EccCodewordsPerBlock[ErrorCorrectionLevel.Ordinal, Version]; var rawCodewords = GetNumRawDataModules(Version) / 8; var numShortBlocks = numBlocks - rawCodewords % numBlocks; var shortBlockLen = rawCodewords / numBlocks; // Split data into blocks and append ECC to each block var blocks = new byte[numBlocks][]; var rs = new ReedSolomonGenerator(blockEccLen); for (int i = 0, k = 0; i < numBlocks; i++) { var dat = CopyOfRange(data, k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)); k += dat.Length; var block = CopyOf(dat, shortBlockLen + 1); var ecc = rs.GetRemainder(dat); Array.Copy(ecc, 0, block, block.Length - blockEccLen, ecc.Length); blocks[i] = block; } // Interleave (not concatenate) the bytes from every block into a single sequence var result = new byte[rawCodewords]; for (int i = 0, k = 0; i < blocks[0].Length; i++) { for (var j = 0; j < blocks.Length; j++) { // Skip the padding byte in short blocks if (i == shortBlockLen - blockEccLen && j < numShortBlocks) { continue; } result[k] = blocks[j][i]; k++; } } return result; } // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire // data area of this QR code. Function modules need to be marked off before this is called. private void DrawCodewords(byte[] data) { Objects.RequireNonNull(data); if (data.Length != GetNumRawDataModules(Version) / 8) { throw new ArgumentOutOfRangeException(); } var i = 0; // Bit index into the data // Do the funny zigzag scan for (var right = Size - 1; right >= 1; right -= 2) { // Index of right column in each column pair if (right == 6) { right = 5; } for (var vert = 0; vert < Size; vert++) { // Vertical counter for (var j = 0; j < 2; j++) { var x = right - j; // Actual x coordinate var upward = ((right + 1) & 2) == 0; var y = upward ? Size - 1 - vert : vert; // Actual y coordinate if (_isFunction[y * Size + x] || i >= data.Length * 8) { continue; } _modules[y * Size + x] = GetBit(data[(uint)i >> 3], 7 - (i & 7)); i++; // If this QR code has any remainder bits (0 to 7), they were assigned as // 0/false/light by the constructor and are left unchanged by this method } } } Debug.Assert(i == data.Length * 8); } // XORs the codeword modules in this QR code with the given mask pattern. // The function modules must be marked and the codeword bits must be drawn // before masking. Due to the arithmetic of XOR, calling applyMask() with // the same mask value a second time will undo the mask. A final well-formed // QR code needs exactly one (not zero, two, etc.) mask applied. private void ApplyMask(uint mask) { if (mask > 7) { throw new ArgumentOutOfRangeException(nameof(mask), "Mask value out of range"); } var index = 0; for (var y = 0; y < Size; y++) { for (var x = 0; x < Size; x++) { var invert = false; switch (mask) { case 0: invert = (x + y) % 2 == 0; break; case 1: invert = y % 2 == 0; break; case 2: invert = x % 3 == 0; break; case 3: invert = (x + y) % 3 == 0; break; case 4: invert = (x / 3 + y / 2) % 2 == 0; break; case 5: invert = x * y % 2 + x * y % 3 == 0; break; case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; } _modules[index] ^= invert & !_isFunction[index]; index++; } } } // A messy helper function for the constructor. This QR code must be in an unmasked state when this // method is called. The given argument is the requested mask, which is -1 for auto or 0 to 7 for fixed. // This method applies and returns the actual mask chosen, from 0 to 7. private int HandleConstructorMasking(int mask) { if (mask == -1) { // Automatically choose best mask var minPenalty = int.MaxValue; for (uint i = 0; i < 8; i++) { ApplyMask(i); DrawFormatBits(i); var penalty = GetPenaltyScore(); if (penalty < minPenalty) { mask = (int)i; minPenalty = penalty; } ApplyMask(i); // Undoes the mask due to XOR } } Debug.Assert(0 <= mask && mask <= 7); ApplyMask((uint)mask); // Apply the final choice of mask DrawFormatBits((uint)mask); // Overwrite old format bits return mask; // The caller shall assign this value to the final-declared field } // Calculates and returns the penalty score based on state of this QR code's current modules. // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. private int GetPenaltyScore() { var result = 0; var finderPenalty = new FinderPenalty(Size); // Adjacent modules in row having same color, and finder-like patterns var index = 0; for (var y = 0; y < Size; y++) { var runColor = false; var runX = 0; finderPenalty.Reset(); for (var x = 0; x < Size; x++) { if (_modules[index] == runColor) { runX++; if (runX == 5) { result += PenaltyN1; } else if (runX > 5) { result++; } } else { finderPenalty.AddHistory(runX); if (!runColor) { result += finderPenalty.CountPatterns() * PenaltyN3; } runColor = _modules[index]; runX = 1; } index++; } result += finderPenalty.TerminateAndCount(runColor, runX) * PenaltyN3; } // Adjacent modules in column having same color, and finder-like patterns for (var x = 0; x < Size; x++) { index = x; var runColor = false; var runY = 0; finderPenalty.Reset(); for (var y = 0; y < Size; y++) { if (_modules[index] == runColor) { runY++; if (runY == 5) { result += PenaltyN1; } else if (runY > 5) { result++; } } else { finderPenalty.AddHistory(runY); if (!runColor) { result += finderPenalty.CountPatterns() * PenaltyN3; } runColor = _modules[index]; runY = 1; } index += Size; } result += finderPenalty.TerminateAndCount(runColor, runY) * PenaltyN3; } // 2*2 blocks of modules having same color index = 0; for (var y = 0; y < Size - 1; y++) { for (var x = 0; x < Size - 1; x++) { var color = _modules[index]; if (color == _modules[index + 1] && color == _modules[index + Size] && color == _modules[index + Size + 1]) { result += PenaltyN2; } index++; } index++; } // Balance of dark and light modules var dark = 0; index = 0; for (var y = 0; y < Size; y++) { for (var x = 0; x < Size; x++) { if (_modules[index]) { dark++; } index++; } } var total = Size * Size; // Note that size is odd, so dark/total != 1/2 // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% var k = (Math.Abs(dark * 20 - total * 10) + total - 1) / total - 1; result += k * PenaltyN4; return result; } #endregion #region Private helper functions // Returns an ascending list of positions of alignment patterns for this version number. // Each position is in the range [0,177), and are used on both the x and y axes. // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. private int[] GetAlignmentPatternPositions() { if (Version == 1) { return new int[] { }; } var numAlign = Version / 7 + 2; int step; if (Version == 32) // Special snowflake { step = 26; } else // step = ceil[(size - 13) / (numAlign*2 - 2)] * 2 { step = (Version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; } var result = new int[numAlign]; result[0] = 6; for (int i = result.Length - 1, pos = Size - 7; i >= 1; i--, pos -= step) { result[i] = pos; } return result; } // Returns the number of data bits that can be stored in a QR code of the given version number, after // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. private static int GetNumRawDataModules(int ver) { if (ver < MIN_VERSION || ver > MAX_VERSION) { throw new ArgumentOutOfRangeException(nameof(ver), "Version number out of range"); } var size = ver * 4 + 17; var result = size * size; // Number of modules in the whole QR code square result -= 8 * 8 * 3; // Subtract the three finders with separators result -= 15 * 2 + 1; // Subtract the format information and dark module result -= (size - 16) * 2; // Subtract the timing patterns (excluding finders) // The five lines above are equivalent to: int result = (16 * ver + 128) * ver + 64; if (ver < 2) { return result; } var numAlign = ver / 7 + 2; result -= (numAlign - 1) * (numAlign - 1) * 25; // Subtract alignment patterns not overlapping with timing patterns result -= (numAlign - 2) * 2 * 20; // Subtract alignment patterns that overlap with timing patterns // The two lines above are equivalent to: result -= (25 * numAlign - 10) * numAlign - 55; if (ver >= 7) { result -= 6 * 3 * 2; // Subtract version information } Debug.Assert(208 <= result && result <= 29648); return result; } // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any // QR code of the given version number and error correction level, with remainder bits discarded. // This stateless pure function could be implemented as a (40*4)-cell lookup table. internal static int GetNumDataCodewords(int ver, Ecc ecl) { return GetNumRawDataModules(ver) / 8 - EccCodewordsPerBlock[ecl.Ordinal, ver] * NumErrorCorrectionBlocks[ecl.Ordinal, ver]; } // Helper class for getPenaltyScore(). // Internal the run history is organized in reverse order // (compared to Nayuki's code) to avoid the copying when // adding to the history. private struct FinderPenalty { private int _length; private readonly short[] _runHistory; private readonly int _size; internal FinderPenalty(int size) { _length = 0; _runHistory = new short[177]; _size = size; } internal void Reset() { _length = 0; } // Can only be called immediately after a light run is added, and // returns either 0, 1, or 2. internal int CountPatterns() { if (_length < 7) { return 0; } int n = _runHistory[_length - 6]; Debug.Assert(n <= _size * 3); var core = n > 0 && _runHistory[_length - 5] == n && _runHistory[_length - 4] == n * 3 && _runHistory[_length - 3] == n && _runHistory[_length - 2] == n; return (core && _runHistory[_length - 7] >= n * 4 && _runHistory[_length - 1] >= n ? 1 : 0) + (core && _runHistory[_length - 1] >= n * 4 && _runHistory[_length - 7] >= n ? 1 : 0); } // Pushes the given value to the front and drops the last value. internal int TerminateAndCount(bool currentRunColor, int currentRunLength) { if (currentRunColor) { // Terminate dark run AddHistory(currentRunLength); currentRunLength = 0; } currentRunLength += _size; // Add light border to final run AddHistory(currentRunLength); return CountPatterns(); } // Adds the given value to the run history internal void AddHistory(int run) { _runHistory[_length] = (short)run; _length++; } } private static byte[] CopyOfRange(byte[] original, int from, int to) { var result = new byte[to - from]; Array.Copy(original, from, result, 0, to - from); return result; } private static byte[] CopyOf(byte[] original, int newLength) { var result = new byte[newLength]; Array.Copy(original, result, Math.Min(original.Length, newLength)); return result; } // Returns true iff the i'th bit of x is set to 1. private static bool GetBit(uint x, int i) { return ((x >> i) & 1) != 0; } #endregion #region Constants and tables /// /// The minimum version (size) supported in the QR Code Model 2 standard – namely 1. /// /// The minimum version. public const int MIN_VERSION = 1; /// /// The maximum version (size) supported in the QR Code Model 2 standard – namely 40. /// /// The maximum version. public const int MAX_VERSION = 40; // For use in getPenaltyScore(), when evaluating which mask is best. private const int PenaltyN1 = 3; private const int PenaltyN2 = 3; private const int PenaltyN3 = 40; private const int PenaltyN4 = 10; private static readonly byte[,] EccCodewordsPerBlock = { // Version: (note that index 0 is for padding, and is set to an illegal value) // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level { 255, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }, // Low { 255, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }, // Low { 255, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 }, // Medium { 255, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }, // Quartile { 255, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 } // High }; private static readonly byte[,] NumErrorCorrectionBlocks = { // Version: (note that index 0 is for padding, and is set to an illegal value) // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level { 255, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 }, // Low { 255, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 }, // Medium { 255, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 }, // Quartile { 255, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 } // High }; #endregion #region Public helper enumeration /// /// Error correction level in QR code symbol. /// public sealed class Ecc { /// /// Low error correction level. The QR code can tolerate about 7% erroneous codewords. /// /// Low error correction level. public static readonly Ecc Low = new Ecc(0, 1); /// /// Medium error correction level. The QR code can tolerate about 15% erroneous codewords. /// /// Medium error correction level. public static readonly Ecc Medium = new Ecc(1, 0); /// /// Quartile error correction level. The QR code can tolerate about 25% erroneous codewords. /// /// Quartile error correction level. public static readonly Ecc Quartile = new Ecc(2, 3); /// /// High error correction level. The QR code can tolerate about 30% erroneous codewords. /// /// High error correction level. public static readonly Ecc High = new Ecc(3, 2); internal static readonly Ecc[] AllValues = { Low, Medium, Quartile, High }; /// /// Ordinal number of error correction level (in the range 0 to 3). /// /// /// Higher number represent a higher amount of error tolerance. /// /// Ordinal number. public int Ordinal { get; } // In the range 0 to 3 (unsigned 2-bit integer). internal uint FormatBits { get; } // Constructor. private Ecc(int ordinal, uint fb) { Ordinal = ordinal; FormatBits = fb; } } #endregion }