Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/312 add quietzone flag pngbyteqrcode #350

Merged
merged 2 commits into from
Nov 23, 2021
Merged
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
29 changes: 15 additions & 14 deletions QRCoder/PngByteQRCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public PngByteQRCode(QRCodeData data) : base(data)
/// <summary>
/// Creates a black & white PNG of the QR code, using 1-bit grayscale.
/// </summary>
public byte[] GetGraphic(int pixelsPerModule)
public byte[] GetGraphic(int pixelsPerModule, bool drawQuietZones = true)
{
using (var png = new PngBuilder())
{
var size = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule;
var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule;
png.WriteHeader(size, size, 1, PngBuilder.ColorType.Greyscale);
png.WriteScanlines(this.DrawScanlines(pixelsPerModule));
png.WriteScanlines(this.DrawScanlines(pixelsPerModule, drawQuietZones));
png.WriteEnd();
return png.GetBytes();
}
Expand All @@ -35,14 +35,14 @@ public byte[] GetGraphic(int pixelsPerModule)
/// <summary>
/// Creates 2-color PNG of the QR code, using 1-bit indexed color. Accepts 3-byte RGB colors for normal images and 4-byte RGBA-colors for transparent images.
/// </summary>
public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba)
public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba, bool drawQuietZones = true)
{
using (var png = new PngBuilder())
{
var size = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule;
var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule;
png.WriteHeader(size, size, 1, PngBuilder.ColorType.Indexed);
png.WritePalette(darkColorRgba, lightColorRgba);
png.WriteScanlines(this.DrawScanlines(pixelsPerModule));
png.WriteScanlines(this.DrawScanlines(pixelsPerModule, drawQuietZones));
png.WriteEnd();
return png.GetBytes();
}
Expand All @@ -51,22 +51,23 @@ public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgba, byte[] light
/// <summary>
/// Creates a bitmap where each pixel is represented by a single bit, dark = 0 and light = 1.
/// </summary>
private byte[] DrawScanlines(int pixelsPerModule)
private byte[] DrawScanlines(int pixelsPerModule, bool drawQuietZones)
{
var moduleMatrix = this.QrCodeData.ModuleMatrix;
var matrixSize = moduleMatrix.Count;
var matrixSize = moduleMatrix.Count - (drawQuietZones ? 0 : 8);
var quietZoneOffset = (drawQuietZones ? 0 : 4);
var bytesPerScanline = (matrixSize * pixelsPerModule + 7) / 8 + 1; // A monochrome scanline is one byte for filter type then one bit per pixel.
var scanlines = new byte[bytesPerScanline * matrixSize * pixelsPerModule];

for (var y = 0; y < matrixSize; y++)
{
var modules = moduleMatrix[y];
var modules = moduleMatrix[y+quietZoneOffset];
var scanlineOffset = y * pixelsPerModule * bytesPerScanline;

// Draw a scanline with the modules from the QR code.
for (var x = 0; x < matrixSize; x++)
{
if (modules[x])
if (modules[x + quietZoneOffset])
{
continue;
}
Expand Down Expand Up @@ -319,22 +320,22 @@ private static uint Crc32(byte[] data, int index, int length)

public static class PngByteQRCodeHelper
{
public static byte[] GetQRCode(string plainText, int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1)
public static byte[] GetQRCode(string plainText, int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, bool drawQuietZones = true)
{
using (var qrGenerator = new QRCodeGenerator())
using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion))
using (var qrCode = new PngByteQRCode(qrCodeData))
return qrCode.GetGraphic(pixelsPerModule, darkColorRgba, lightColorRgba);
return qrCode.GetGraphic(pixelsPerModule, darkColorRgba, lightColorRgba, drawQuietZones);
}



public static byte[] GetQRCode(string txt, QRCodeGenerator.ECCLevel eccLevel, int size)
public static byte[] GetQRCode(string txt, QRCodeGenerator.ECCLevel eccLevel, int size, bool drawQuietZones = true)
{
using (var qrGen = new QRCodeGenerator())
using (var qrCode = qrGen.CreateQrCode(txt, eccLevel))
using (var qrPng = new PngByteQRCode(qrCode))
return qrPng.GetGraphic(size);
return qrPng.GetGraphic(size, drawQuietZones);
}
}
}
7 changes: 5 additions & 2 deletions QRCoderTests/Helpers/HelperFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,22 @@ public static string BitmapToHash(Bitmap bmp)
}
return ByteArrayToHash(imgBytes);
}
#endif

public static string ByteArrayToHash(byte[] data)
{
#if !NETCOREAPP1_1
var md5 = new MD5CryptoServiceProvider();
var hash = md5.ComputeHash(data);
#else
var hash = new SshNet.Security.Cryptography.MD5().ComputeHash(data);
#endif
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}

public static string StringToHash(string data)
{
return ByteArrayToHash(Encoding.UTF8.GetBytes(data));
}
#endif

}
}
172 changes: 172 additions & 0 deletions QRCoderTests/PngByteQRCodeRendererTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
using Xunit;
using QRCoder;
using Shouldly;
using QRCoderTests.Helpers.XUnitExtenstions;
using QRCoderTests.Helpers;
#if !NETCOREAPP1_1
using System.Drawing;
using System.IO;
#endif

namespace QRCoderTests
{
/****************************************************************************************************
* Note: Test cases compare the outcome visually even if it's slower than a byte-wise compare.
* This is necessary, because the Deflate implementation differs on the different target
* platforms and thus the outcome, even if visually identical, differs. Thus only a visual
* test method makes sense. In addition bytewise differences shouldn't be important, if the
* visual outcome is identical and thus the qr code is identical/scannable.
****************************************************************************************************/
public class PngByteQRCodeRendererTests
{


[Fact]
[Category("QRRenderer/PngByteQRCode")]
public void can_render_pngbyte_qrcode_blackwhite()
{
//Create QR code
var gen = new QRCodeGenerator();
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L);
var pngCodeGfx = new PngByteQRCode(data).GetGraphic(5);

#if NETCOREAPP1_1
var result = HelperFunctions.ByteArrayToHash(pngCodeGfx);
result.ShouldBe("1fc35c3bea6fad47427143ce716c83b8");
#else
using (var mStream = new MemoryStream(pngCodeGfx))
{
var bmp = (Bitmap)Image.FromStream(mStream);
var result = HelperFunctions.BitmapToHash(bmp);
result.ShouldBe("18b19e6037cff06ae995d8d487b0e46e");
}
#endif
}

[Fact]
[Category("QRRenderer/PngByteQRCode")]
public void can_render_pngbyte_qrcode_color()
{
//Create QR code
var gen = new QRCodeGenerator();
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L);
var pngCodeGfx = new PngByteQRCode(data).GetGraphic(5, new byte[] { 255, 0, 0 }, new byte[] { 0, 0, 255 });

#if NETCOREAPP1_1
var result = HelperFunctions.ByteArrayToHash(pngCodeGfx);
result.ShouldBe("0144b1d40aa6eeb6cb07df42822ea0a7");
#else
using (var mStream = new MemoryStream(pngCodeGfx))
{
var bmp = (Bitmap)Image.FromStream(mStream);
var result = HelperFunctions.BitmapToHash(bmp);
result.ShouldBe("37ae73e90b66beac317b790be3db24cc");
}
#endif
}


[Fact]
[Category("QRRenderer/PngByteQRCode")]
public void can_render_pngbyte_qrcode_color_with_alpha()
{
//Create QR code
var gen = new QRCodeGenerator();
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L);
var pngCodeGfx = new PngByteQRCode(data).GetGraphic(5, new byte[] { 255, 255, 255, 127 }, new byte[] { 0, 0, 255 });

#if NETCOREAPP1_1
var result = HelperFunctions.ByteArrayToHash(pngCodeGfx);
result.ShouldBe("627ce564fb5e17be42e4a85e907a17b5");
#else
using (var mStream = new MemoryStream(pngCodeGfx))
{
var bmp = (Bitmap)Image.FromStream(mStream);
var result = HelperFunctions.BitmapToHash(bmp);
result.ShouldBe("c56c2a9535fd8e9a92a6ac9709d21e67");
}
#endif
}

[Fact]
[Category("QRRenderer/PngByteQRCode")]
public void can_render_pngbyte_qrcode_color_without_quietzones()
{
//Create QR code
var gen = new QRCodeGenerator();
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L);
var pngCodeGfx = new PngByteQRCode(data).GetGraphic(5, new byte[] { 255, 255, 255, 127 }, new byte[] { 0, 0, 255 }, false);

#if NETCOREAPP1_1
var result = HelperFunctions.ByteArrayToHash(pngCodeGfx);
result.ShouldBe("07f760b3eb54901840b094d31e299713");
#else
File.WriteAllBytes(@"C:\Temp\pngbyte_35.png", pngCodeGfx);
using (var mStream = new MemoryStream(pngCodeGfx))
{
var bmp = (Bitmap)Image.FromStream(mStream);
bmp.MakeTransparent(Color.Transparent);
var result = HelperFunctions.BitmapToHash(bmp);
#if NET35_OR_GREATER || NET40_OR_GREATER
result.ShouldBe("75be11d582575617d2490c54b69e844e");
#else
result.ShouldBe("fbbc8255ebf3e4f4a1d21f0dd15f76f8");
#endif
}
#endif
}

[Fact]
[Category("QRRenderer/PngByteQRCode")]
public void can_instantate_pngbyte_qrcode_parameterless()
{
var pngCode = new PngByteQRCode();
pngCode.ShouldNotBeNull();
pngCode.ShouldBeOfType<PngByteQRCode>();
}

[Fact]
[Category("QRRenderer/PngByteQRCode")]
public void can_render_pngbyte_qrcode_from_helper()
{
//Create QR code
var pngCodeGfx = PngByteQRCodeHelper.GetQRCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L, 10);

#if NETCOREAPP1_1
var result = HelperFunctions.ByteArrayToHash(pngCodeGfx);
result.ShouldBe("c562388f4f3cf13a299b469a3e3b852f");
#else
using (var mStream = new MemoryStream(pngCodeGfx))
{
var bmp = (Bitmap)Image.FromStream(mStream);
var result = HelperFunctions.BitmapToHash(bmp);
result.ShouldBe("1978fb11ce26acf9b6cb7490b4c44ef2");
}
#endif
}

[Fact]
[Category("QRRenderer/PngByteQRCode")]
public void can_render_pngbyte_qrcode_from_helper_2()
{
//Create QR code
var pngCodeGfx = PngByteQRCodeHelper.GetQRCode("This is a quick test! 123#?", 5, new byte[] { 255, 255, 255, 127 }, new byte[] { 0, 0, 255 }, QRCodeGenerator.ECCLevel.L);

#if NETCOREAPP1_1
var result = HelperFunctions.ByteArrayToHash(pngCodeGfx);
result.ShouldBe("627ce564fb5e17be42e4a85e907a17b5");
#else
using (var mStream = new MemoryStream(pngCodeGfx))
{
var bmp = (Bitmap)Image.FromStream(mStream);
var result = HelperFunctions.BitmapToHash(bmp);
result.ShouldBe("c56c2a9535fd8e9a92a6ac9709d21e67");
}
#endif
}

}
}



1 change: 1 addition & 0 deletions QRCoderTests/QRCoderTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SshNet.Security.Cryptography" Version="1.3.0" />
<ProjectReference Include="..\QRCoder\QRCoder.csproj" />
</ItemGroup>
<ItemGroup>
Expand Down