/*
* 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:
///
///
///
High level: Take the payload data and call
/// or .
///
Mid level: Custom-make a list of instances and call
///
///
Low level: Custom-make an array of data codeword bytes (including segment headers and
/// final padding, excluding error correction codewords), supply the appropriate version number,
/// and call the .
///
///
///
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")
.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
}