using System.IO.Compression; using System.Text; namespace QYZH.InteractiveMagazine.Common.Helpers; /// /// 二维码帮助类 /// public static class QrCodeHelper { private const int Version = 4; private const int Size = Version * 4 + 17; private const int DataCodewordCount = 80; private const int ErrorCorrectionCodewordCount = 20; private const int QuietZone = 4; private const int Scale = 10; private static readonly int[] AlignmentPatternPositions = [6, 26]; /// /// 生成二维码PNG图片字节 /// /// 二维码内容 /// PNG图片字节 public static byte[] GeneratePng(string content) { var dataCodewords = CreateDataCodewords(content); var allCodewords = dataCodewords.Concat(CreateErrorCorrectionCodewords(dataCodewords)).ToArray(); var modules = BuildModules(allCodewords); var imageSize = (Size + QuietZone * 2) * Scale; var pixels = new byte[imageSize * imageSize * 4]; for (var i = 0; i < pixels.Length; i += 4) { pixels[i] = 255; pixels[i + 1] = 255; pixels[i + 2] = 255; pixels[i + 3] = 255; } for (var y = 0; y < Size; y++) { for (var x = 0; x < Size; x++) { if (modules[y, x]) { FillModule(pixels, imageSize, x + QuietZone, y + QuietZone); } } } return EncodePng(imageSize, imageSize, pixels); } private static byte[] CreateDataCodewords(string content) { var bytes = Encoding.UTF8.GetBytes(content); if (bytes.Length > 78) { throw new InvalidOperationException("二维码内容过长"); } var bits = new List(); AppendBits(bits, 0b0100, 4); AppendBits(bits, bytes.Length, 8); foreach (var value in bytes) { AppendBits(bits, value, 8); } var remaining = DataCodewordCount * 8 - bits.Count; AppendBits(bits, 0, Math.Min(4, remaining)); while (bits.Count % 8 != 0) { bits.Add(false); } var data = BitsToBytes(bits); var pad = true; while (data.Count < DataCodewordCount) { data.Add((byte)(pad ? 0xEC : 0x11)); pad = !pad; } return data.ToArray(); } private static bool[,] BuildModules(byte[] codewords) { var modules = new bool[Size, Size]; var reserved = new bool[Size, Size]; AddFinder(modules, reserved, 0, 0); AddFinder(modules, reserved, Size - 7, 0); AddFinder(modules, reserved, 0, Size - 7); AddTimingPatterns(modules, reserved); AddAlignmentPatterns(modules, reserved); ReserveFormatAreas(reserved); modules[Version * 4 + 9, 8] = true; reserved[Version * 4 + 9, 8] = true; AddDataModules(modules, reserved, codewords); AddFormatBits(modules); return modules; } private static void AddFinder(bool[,] modules, bool[,] reserved, int left, int top) { for (var y = -1; y <= 7; y++) { for (var x = -1; x <= 7; x++) { var px = left + x; var py = top + y; if (px < 0 || py < 0 || px >= Size || py >= Size) { continue; } reserved[py, px] = true; modules[py, px] = x >= 0 && x <= 6 && y >= 0 && y <= 6 && (x == 0 || x == 6 || y == 0 || y == 6 || (x >= 2 && x <= 4 && y >= 2 && y <= 4)); } } } private static void AddTimingPatterns(bool[,] modules, bool[,] reserved) { for (var i = 8; i < Size - 8; i++) { var isDark = i % 2 == 0; modules[6, i] = isDark; modules[i, 6] = isDark; reserved[6, i] = true; reserved[i, 6] = true; } } private static void AddAlignmentPatterns(bool[,] modules, bool[,] reserved) { foreach (var centerY in AlignmentPatternPositions) { foreach (var centerX in AlignmentPatternPositions) { if (reserved[centerY, centerX]) { continue; } for (var y = -2; y <= 2; y++) { for (var x = -2; x <= 2; x++) { var px = centerX + x; var py = centerY + y; reserved[py, px] = true; modules[py, px] = Math.Max(Math.Abs(x), Math.Abs(y)) != 1; } } } } } private static void ReserveFormatAreas(bool[,] reserved) { for (var i = 0; i <= 8; i++) { reserved[8, i] = true; reserved[i, 8] = true; } for (var i = 0; i < 8; i++) { reserved[8, Size - 1 - i] = true; } for (var i = 0; i < 7; i++) { reserved[Size - 1 - i, 8] = true; } } private static void AddDataModules(bool[,] modules, bool[,] reserved, byte[] codewords) { var bits = new List(); foreach (var codeword in codewords) { AppendBits(bits, codeword, 8); } var bitIndex = 0; var upward = true; for (var right = Size - 1; right >= 1; right -= 2) { if (right == 6) { right--; } for (var i = 0; i < Size; i++) { var y = upward ? Size - 1 - i : i; for (var j = 0; j < 2; j++) { var x = right - j; if (reserved[y, x]) { continue; } var bit = bitIndex < bits.Count && bits[bitIndex++]; if ((x + y) % 2 == 0) { bit = !bit; } modules[y, x] = bit; } } upward = !upward; } } private static void AddFormatBits(bool[,] modules) { var format = GetFormatBits(); for (var i = 0; i <= 5; i++) { modules[i, 8] = GetBit(format, i); } modules[7, 8] = GetBit(format, 6); modules[8, 8] = GetBit(format, 7); modules[8, 7] = GetBit(format, 8); for (var i = 9; i < 15; i++) { modules[8, 14 - i] = GetBit(format, i); } for (var i = 0; i < 8; i++) { modules[8, Size - 1 - i] = GetBit(format, i); } for (var i = 8; i < 15; i++) { modules[Size - 15 + i, 8] = GetBit(format, i); } modules[Size - 8, 8] = true; } private static int GetFormatBits() { const int errorCorrectionLevelBits = 1; const int mask = 0; var data = (errorCorrectionLevelBits << 3) | mask; var value = data << 10; const int generator = 0x537; for (var i = 14; i >= 10; i--) { if (((value >> i) & 1) != 0) { value ^= generator << (i - 10); } } return ((data << 10) | (value & 0x3FF)) ^ 0x5412; } private static byte[] CreateErrorCorrectionCodewords(byte[] data) { var generator = CreateGeneratorPolynomial(ErrorCorrectionCodewordCount); var result = new byte[ErrorCorrectionCodewordCount]; foreach (var b in data) { var factor = b ^ result[0]; Array.Copy(result, 1, result, 0, result.Length - 1); result[^1] = 0; for (var i = 0; i < result.Length; i++) { result[i] ^= GaloisMultiply(generator[i + 1], factor); } } return result; } private static byte[] CreateGeneratorPolynomial(int degree) { var result = new List { 1 }; for (var i = 0; i < degree; i++) { var next = new byte[result.Count + 1]; for (var j = 0; j < result.Count; j++) { next[j] ^= result[j]; next[j + 1] ^= GaloisMultiply(result[j], GaloisPower(i)); } result = next.ToList(); } return result.ToArray(); } private static byte GaloisPower(int exponent) { var value = 1; for (var i = 0; i < exponent; i++) { value <<= 1; if ((value & 0x100) != 0) { value ^= 0x11D; } } return (byte)value; } private static byte GaloisMultiply(int x, int y) { var result = 0; while (y != 0) { if ((y & 1) != 0) { result ^= x; } x <<= 1; if ((x & 0x100) != 0) { x ^= 0x11D; } y >>= 1; } return (byte)result; } private static void AppendBits(List bits, int value, int count) { for (var i = count - 1; i >= 0; i--) { bits.Add(((value >> i) & 1) != 0); } } private static List BitsToBytes(List bits) { var result = new List(); for (var i = 0; i < bits.Count; i += 8) { var value = 0; for (var j = 0; j < 8; j++) { value = (value << 1) | (bits[i + j] ? 1 : 0); } result.Add((byte)value); } return result; } private static bool GetBit(int value, int index) { return ((value >> index) & 1) != 0; } private static void FillModule(byte[] pixels, int imageSize, int moduleX, int moduleY) { var startX = moduleX * Scale; var startY = moduleY * Scale; for (var y = 0; y < Scale; y++) { for (var x = 0; x < Scale; x++) { var index = ((startY + y) * imageSize + startX + x) * 4; pixels[index] = 0; pixels[index + 1] = 0; pixels[index + 2] = 0; pixels[index + 3] = 255; } } } private static byte[] EncodePng(int width, int height, byte[] rgba) { using var output = new MemoryStream(); WriteUInt(output, 0x89504E47); WriteUInt(output, 0x0D0A1A0A); using (var ihdr = new MemoryStream()) { WriteUInt(ihdr, (uint)width); WriteUInt(ihdr, (uint)height); ihdr.WriteByte(8); ihdr.WriteByte(6); ihdr.WriteByte(0); ihdr.WriteByte(0); ihdr.WriteByte(0); WriteChunk(output, "IHDR", ihdr.ToArray()); } var stride = width * 4; using (var raw = new MemoryStream()) { for (var y = 0; y < height; y++) { raw.WriteByte(0); raw.Write(rgba, y * stride, stride); } using var compressed = new MemoryStream(); using (var zlib = new ZLibStream(compressed, CompressionLevel.SmallestSize, true)) { raw.Position = 0; raw.CopyTo(zlib); } WriteChunk(output, "IDAT", compressed.ToArray()); } WriteChunk(output, "IEND", []); return output.ToArray(); } private static void WriteChunk(Stream stream, string type, byte[] data) { WriteUInt(stream, (uint)data.Length); var typeBytes = Encoding.ASCII.GetBytes(type); stream.Write(typeBytes); stream.Write(data); WriteUInt(stream, Crc32(typeBytes.Concat(data).ToArray())); } private static void WriteUInt(Stream stream, uint value) { stream.WriteByte((byte)(value >> 24)); stream.WriteByte((byte)(value >> 16)); stream.WriteByte((byte)(value >> 8)); stream.WriteByte((byte)value); } private static uint Crc32(byte[] bytes) { var crc = 0xFFFFFFFFu; foreach (var b in bytes) { crc ^= b; for (var i = 0; i < 8; i++) { crc = (crc & 1) == 1 ? (crc >> 1) ^ 0xEDB88320u : crc >> 1; } } return ~crc; } }