diff --git a/launcher/Backend/QrCodeRenderer.cs b/launcher/Backend/QrCodeRenderer.cs new file mode 100644 index 0000000..9cb7f59 --- /dev/null +++ b/launcher/Backend/QrCodeRenderer.cs @@ -0,0 +1,60 @@ +using System.Windows.Media; +using System.Windows.Media.Imaging; +using QRCoder; + +namespace DorkNet.Launcher.Backend; + +/// Turns a join-code string into a WPF +/// using QRCoder's raw module matrix. We render the matrix directly to a +/// rather than going through PNG bytes so we +/// avoid pulling in System.Drawing.Common (Windows-only, slated for +/// trimming in cross-platform .NET). +/// +/// Colour choice matches the launcher palette: white (Ink) modules +/// on the deep purple-navy (BgDeeper) background so the QR is legible +/// against the dark panel. +public static class QrCodeRenderer +{ + public static BitmapSource Render(string text, int pixelSize = 220) + { + // QRCodeGenerator -> QRCodeData (matrix of bools) -> WriteableBitmap. + using var gen = new QRCodeGenerator(); + using var data = gen.CreateQrCode(text, QRCodeGenerator.ECCLevel.M); + var modules = data.ModuleMatrix; + var moduleCount = modules.Count; + if (moduleCount == 0) + throw new InvalidOperationException("QR generation produced an empty matrix."); + + // Scale up to the requested pixel size — int division so each + // module is an exact pixel count (avoids subpixel blur). Quiet + // zone (4-module margin) is part of QRCodeData already. + var moduleSize = Math.Max(1, pixelSize / moduleCount); + var dim = moduleCount * moduleSize; + + var bmp = new WriteableBitmap(dim, dim, 96, 96, PixelFormats.Bgra32, null); + var stride = dim * 4; + var pixels = new byte[dim * stride]; + + // Light pixel = Ink (#F7F3FF), dark pixel = BgDeeper (#0E0A1F). + // Order is BGRA in the buffer. + var light = new byte[] { 0xFF, 0xF3, 0xF7, 0xFF }; + var dark = new byte[] { 0x1F, 0x0A, 0x0E, 0xFF }; + + for (int y = 0; y < moduleCount; y++) + for (int x = 0; x < moduleCount; x++) + { + var on = modules[y][x]; + var src = on ? dark : light; + for (int py = 0; py < moduleSize; py++) + for (int px = 0; px < moduleSize; px++) + { + var offset = ((y * moduleSize + py) * stride) + ((x * moduleSize + px) * 4); + Buffer.BlockCopy(src, 0, pixels, offset, 4); + } + } + + bmp.WritePixels(new System.Windows.Int32Rect(0, 0, dim, dim), pixels, stride, 0); + bmp.Freeze(); + return bmp; + } +} diff --git a/launcher/DorkNet.Launcher.csproj b/launcher/DorkNet.Launcher.csproj index 54563f7..2eaf223 100644 --- a/launcher/DorkNet.Launcher.csproj +++ b/launcher/DorkNet.Launcher.csproj @@ -20,4 +20,11 @@ The Backend/* classes (release downloader, server process, etc.) are UI-framework-agnostic and stay unchanged from the PhotinoNET draft. --> + + + + + diff --git a/launcher/MainWindow.xaml b/launcher/MainWindow.xaml index e958a6a..cae9de4 100644 --- a/launcher/MainWindow.xaml +++ b/launcher/MainWindow.xaml @@ -235,28 +235,63 @@ - + - -