Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions QrCodeGenerator/PngBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System;
using System.IO;
using System.IO.Compression;

namespace Net.Codecrete.QrCodeGenerator;

internal sealed class PngBuilder : IDisposable
{
private static readonly byte[] Signature = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
private static readonly uint[] CrcTable =
{
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};

private static readonly byte[] IHDR = { 73, 72, 68, 82 };
private static readonly byte[] IDAT = { 73, 68, 65, 84 };
private static readonly byte[] IEND = { 73, 69, 78, 68 };

private MemoryStream stream = new();

public void Dispose()
{
stream.Dispose();
}

public byte[] GetBytes()
{
var bytes = stream.ToArray();
var chunkOffset = Signature.Length;

while (chunkOffset < bytes.Length)
{
var dataLength = (bytes[chunkOffset] << 24) | (bytes[chunkOffset + 1] << 16) | (bytes[chunkOffset + 2] << 8) | bytes[chunkOffset + 3];
var crc = Crc32(bytes, chunkOffset + 4, dataLength + 4);
var crcOffset = chunkOffset + 8 + dataLength;

bytes[crcOffset + 0] = (byte)(crc >> 24);
bytes[crcOffset + 1] = (byte)(crc >> 16);
bytes[crcOffset + 2] = (byte)(crc >> 8);
bytes[crcOffset + 3] = (byte)crc;

chunkOffset = crcOffset + 4;
}

return bytes;
}

public void WriteHeader(int width, int height, byte bitDepth, byte colorType)
{
stream.Write(Signature, 0, Signature.Length);

WriteChunkStart(IHDR, 13);
WriteIntBigEndian((uint)width);
WriteIntBigEndian((uint)height);

stream.WriteByte(bitDepth);
stream.WriteByte(colorType);
stream.WriteByte(0);
stream.WriteByte(0);
stream.WriteByte(0);

WriteChunkEnd();
}

public void WriteData(byte[] data)
{
using (var idatStream = new MemoryStream())
{
Deflate(idatStream, data);
WriteChunkStart(IDAT, (int)(idatStream.Length + 6));

stream.WriteByte(0x78);
stream.WriteByte(0x9C);

idatStream.Position = 0;
idatStream.CopyTo(stream);

var adler = Adler32(data, 0, data.Length);
WriteIntBigEndian(adler);
WriteChunkEnd();
}
}

public void WriteEnd()
{
WriteChunkStart(IEND, 0);
WriteChunkEnd();
}

private void WriteChunkStart(byte[] type, int length)
{
WriteIntBigEndian((uint)length);
stream.Write(type, 0, 4);
}

private void WriteChunkEnd()
{
stream.SetLength(stream.Length + 4);
stream.Position += 4;
}

private void WriteIntBigEndian(uint value)
{
stream.WriteByte((byte)(value >> 24));
stream.WriteByte((byte)(value >> 16));
stream.WriteByte((byte)(value >> 8));
stream.WriteByte((byte)value);
}

private static void Deflate(Stream output, byte[] bytes)
{
using (var stream = new DeflateStream(output, CompressionMode.Compress, true))
stream.Write(bytes, 0, bytes.Length);
}

private static uint Adler32(byte[] data, int index, int length)
{
const uint Base = 65521;
uint s1 = 1, s2 = 0;

var end = index + length;
for (var n = index; n < end; n++)
{
s1 = (s1 + data[n]) % Base;
s2 = (s2 + s1) % Base;
}

return (s2 << 16) + s1;
}

private static uint Crc32(byte[] data, int index, int length)
{
var c = 0xffffffff;

var end = index + length;
for (var n = index; n < end; n++)
c = CrcTable[(c ^ data[n]) & 0xff] ^ (c >> 8);

return c ^ 0xffffffff;
}
}
11 changes: 11 additions & 0 deletions QrCodeGenerator/QrCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,17 @@ public byte[] ToBmpBitmap(int border = 0, int scale = 1)
return ToBmpBitmap(border, scale, 0x000000, 0xffffff);
}

/// <summary>
/// Creates bitmap in the PNG format data using black for dark modules and white for light modules.
/// </summary>
/// <param name="border">The border width, as a factor of the module (QR code pixel) size.</param>
/// <param name="scale">The width and height, in pixels, of each module.</param>
/// <returns>Bitmap data</returns>
public byte[] ToPngBitmap(int border = 0, int scale = 1)
{
return new QrCodePng(this, scale, border).GetBytes();
}

/// <summary>
/// Creates an RGB color value in little endian format.
/// </summary>
Expand Down
65 changes: 65 additions & 0 deletions QrCodeGenerator/QrCodePng.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;

namespace Net.Codecrete.QrCodeGenerator;

internal sealed class QrCodePng
{
private readonly QrCode _qr;
private readonly int _scale;
private readonly int _border;
private readonly int _realSize;
private readonly int _scaledSize;

public QrCodePng(QrCode qr, int scale, int border)
{
_qr = qr;
_scale = scale;
_border = border;
_realSize = qr.Size + border * 2;
_scaledSize = _realSize * scale;
}

public byte[] GetBytes()
{
using var png = new PngBuilder();

var data = Draw();

png.WriteHeader(_scaledSize, _scaledSize, 1, 0);
png.WriteData(data);
png.WriteEnd();

return png.GetBytes();
}

private byte[] Draw()
{
int bytesPerLine = (_scaledSize + 7) / 8 + 1;
var data = new byte[bytesPerLine * _scaledSize];

for (int y = 0; y < _realSize; y++)
{
int offset = y * bytesPerLine * _scale;

for (int x = 0; x < _realSize; x++)
{
if (_qr.GetModule(x - _border, y - _border))
continue;

int pos = x * _scale;
int end = pos + _scale;

for (; pos < end; pos++)
{
int index = offset + pos / 8 + 1;
data[index] |= (byte)(0x80 >> pos % 8);
}
}

for (var i = 1; i < _scale; i++)
Array.Copy(data, offset, data, offset + i * bytesPerLine, bytesPerLine);
}

return data;
}
}