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

BitmapByteQRCode performance optimization #566

Merged
merged 7 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
68 changes: 41 additions & 27 deletions QRCoder/BitmapByteQRCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,35 @@ public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgb, byte[] lightC
{
var sideLength = QrCodeData.ModuleMatrix.Count * pixelsPerModule;

var moduleDark = darkColorRgb.Reverse();
var moduleLight = lightColorRgb.Reverse();
var moduleDarkSingle = darkColorRgb.Reverse().ToArray();
var moduleLightSingle = lightColorRgb.Reverse().ToArray();
codebude marked this conversation as resolved.
Show resolved Hide resolved

var bmp = new List<byte>();
// Pre-calculate color/module bytes
var moduleDark = new byte[pixelsPerModule * 3];
var moduleLight = new byte[pixelsPerModule * 3];
for (int i = 0; i < pixelsPerModule; i++)
{
Array.Copy(moduleDarkSingle, 0, moduleDark, i * 3, 3);
Array.Copy(moduleLightSingle, 0, moduleLight, i * 3, 3);
codebude marked this conversation as resolved.
Show resolved Hide resolved
}

// Pre-calculate padding bytes
var padding = new byte[sideLength % 4];
codebude marked this conversation as resolved.
Show resolved Hide resolved

// Calculate filesize (header + pixel data + padding)
var fileSize = 54 + (3 * (sideLength * sideLength)) + (sideLength * padding.Length);


var bmp = new List<byte>(fileSize);
codebude marked this conversation as resolved.
Show resolved Hide resolved

//header part 1
bmp.AddRange(new byte[] { 0x42, 0x4D });
codebude marked this conversation as resolved.
Show resolved Hide resolved

//header
bmp.AddRange(new byte[] { 0x42, 0x4D, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00 });
// filesize
bmp.AddRange(IntTo4Byte(fileSize));
codebude marked this conversation as resolved.
Show resolved Hide resolved

// header part 2
bmp.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00 });

//width
bmp.AddRange(IntTo4Byte(sideLength));
Expand All @@ -70,36 +92,28 @@ public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgb, byte[] lightC
bmp.AddRange(new byte[] { 0x01, 0x00, 0x18, 0x00 });
bmp.AddRange(new byte[24]);
codebude marked this conversation as resolved.
Show resolved Hide resolved

//Group of pixels placeholder
var group = new byte[(int)(sideLength / pixelsPerModule) * moduleDark.Length];
codebude marked this conversation as resolved.
Show resolved Hide resolved
codebude marked this conversation as resolved.
Show resolved Hide resolved
//draw qr code
for (var x = sideLength - 1; x >= 0; x -= pixelsPerModule)
{
var i_x = (x + pixelsPerModule) / pixelsPerModule - 1;
// Pre-calculate array
for (var y = 0; y < sideLength; y += pixelsPerModule)
{
var module = QrCodeData.ModuleMatrix[i_x][(y + pixelsPerModule) / pixelsPerModule - 1];
Array.Copy(module ? moduleDark : moduleLight, 0, group, y / pixelsPerModule * moduleDark.Length, moduleDark.Length);
codebude marked this conversation as resolved.
Show resolved Hide resolved
}
for (int pm = 0; pm < pixelsPerModule; pm++)
{
for (var y = 0; y < sideLength; y += pixelsPerModule)
{
var module =
QrCodeData.ModuleMatrix[(x + pixelsPerModule) / pixelsPerModule - 1][(y + pixelsPerModule) / pixelsPerModule - 1];
for (int i = 0; i < pixelsPerModule; i++)
{
bmp.AddRange(module ? moduleDark : moduleLight);
}
}
if (sideLength % 4 != 0)
{
for (int i = 0; i < sideLength % 4; i++)
{
bmp.Add(0x00);
}
}
// Draw pixels
bmp.AddRange(group);

// Add padding (to make line length a multiple of 4)
bmp.AddRange(padding);
}
}

// write filesize in header
var bmpFileSize = IntTo4Byte(bmp.Count);
for (int i = 0; i < bmpFileSize.Length; i++)
{
bmp[2 + i] = bmpFileSize[i];
}
return bmp.ToArray();
codebude marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down
50 changes: 50 additions & 0 deletions QRCoderBenchmarks/BitmapByteQRCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using BenchmarkDotNet.Attributes;
using QRCoder;

namespace QRCoderBenchmarks;

[MemoryDiagnoser]
public class BitmapByteQRCodeBenchmark
{
private readonly Dictionary<string, QRCodeData> _samples;

public BitmapByteQRCodeBenchmark()
{
var eccLvl = QRCoder.QRCodeGenerator.ECCLevel.L;
_samples = new Dictionary<string, QRCodeData>()
{
{ "small", QRCoder.QRCodeGenerator.GenerateQrCode("ABCD", eccLvl) },
{ "medium", QRCoder.QRCodeGenerator.GenerateQrCode("https://github.com/codebude/QRCoder/blob/f89aa90081f369983a9ba114e49cc6ebf0b2a7b1/QRCoder/Framework4.0Methods/Stream4Methods.cs", eccLvl) },
{ "big", QRCoder.QRCodeGenerator.GenerateQrCode( new string('a', 2600), eccLvl) }
};
}


[Benchmark]
public void RenderBitmapByteQRCodeSmall()
{
var qrCode = new BitmapByteQRCode(_samples["small"]);
_ = qrCode.GetGraphic(10);
}

[Benchmark]
public void RenderBitmapByteQRCodeMedium()
{
var qrCode = new BitmapByteQRCode(_samples["medium"]);
_ = qrCode.GetGraphic(10);
}

[Benchmark]
public void RenderBitmapByteQRCodeBig()
{
var qrCode = new BitmapByteQRCode(_samples["big"]);
_ = qrCode.GetGraphic(10);
}

[Benchmark]
public void RenderBitmapByteQRCodeHuge()
{
var qrCode = new BitmapByteQRCode(_samples["big"]);
_ = qrCode.GetGraphic(50);
}
}
50 changes: 50 additions & 0 deletions QRCoderTests/BitmapByteQRCodeRendererTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using QRCoder;
using QRCoderTests.Helpers;
using QRCoderTests.Helpers.XUnitExtenstions;
using Shouldly;
using Xunit;


namespace QRCoderTests;


public class BitmapByteQRCodeRendererTests
{
[Fact]
[Category("QRRenderer/BitmapByteQRCode")]
public void can_render_bitmapbyte_qrcode()
{
var gen = new QRCodeGenerator();
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
var bmp = new BitmapByteQRCode(data).GetGraphic(10);

var result = HelperFunctions.ByteArrayToHash(bmp);
result.ShouldBe("2d262d074f5c436ad93025150392dd38");
}


[Fact]
[Category("QRRenderer/BitmapByteQRCode")]
public void can_render_bitmapbyte_qrcode_color_bytearray()
{
var gen = new QRCodeGenerator();
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
var bmp = new BitmapByteQRCode(data).GetGraphic(10, new byte[] { 30, 30, 30 }, new byte[] { 255, 0, 0 });

var result = HelperFunctions.ByteArrayToHash(bmp);
result.ShouldBe("1184507c7eb98f9ca76afd04313c41cb");
}

[Fact]
[Category("QRRenderer/BitmapByteQRCode")]
public void can_render_bitmapbyte_qrcode_drawing_color()
{
var gen = new QRCodeGenerator();
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
var bmp = new BitmapByteQRCode(data).GetGraphic(10, "#e3e3e3", "#ffffff");

var result = HelperFunctions.ByteArrayToHash(bmp);
result.ShouldBe("40cd208fc46aa726d6e98a2028ffd2b7");
}

}