From 1f22381944712c7f902149b3ce8d90e7b10173b4 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Mon, 6 Oct 2025 17:50:49 -0500 Subject: [PATCH 01/39] update packages and .net version --- .../Simple QR Code Maker.Core.csproj | 2 +- Simple QR Code Maker/Simple QR Code Maker.csproj | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Simple QR Code Maker.Core/Simple QR Code Maker.Core.csproj b/Simple QR Code Maker.Core/Simple QR Code Maker.Core.csproj index 4af9d37..d2f96c3 100644 --- a/Simple QR Code Maker.Core/Simple QR Code Maker.Core.csproj +++ b/Simple QR Code Maker.Core/Simple QR Code Maker.Core.csproj @@ -1,6 +1,6 @@  - net8.0 + net9.0 Simple_QR_Code_Maker.Core x86;x64;arm64;AnyCPU enable diff --git a/Simple QR Code Maker/Simple QR Code Maker.csproj b/Simple QR Code Maker/Simple QR Code Maker.csproj index 80bba17..fa262d1 100644 --- a/Simple QR Code Maker/Simple QR Code Maker.csproj +++ b/Simple QR Code Maker/Simple QR Code Maker.csproj @@ -1,7 +1,7 @@  WinExe - net8.0-windows10.0.22621.0 + net9.0-windows10.0.22621.0 10.0.19041.0 Simple_QR_Code_Maker Assets/WindowIcon.ico @@ -47,12 +47,13 @@ - - - + + + + - - + + From d942f21014d541c009d6b0893d415ecff7a5d3d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 03:59:28 +0000 Subject: [PATCH 02/39] Initial plan From a0e4c962cb93a9ee8471f332fb888b7facebcaa5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 04:04:53 +0000 Subject: [PATCH 03/39] Add logo overlay support for QR codes Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com> --- .../Helpers/BarcodeHelpers.cs | 72 ++++++++++++++++++- .../Models/BarcodeImageItem.cs | 6 +- .../ViewModels/MainViewModel.cs | 60 +++++++++++++++- Simple QR Code Maker/Views/MainPage.xaml | 17 +++++ 4 files changed, 150 insertions(+), 5 deletions(-) diff --git a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs index 51ff1eb..ba45f12 100644 --- a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs +++ b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs @@ -14,7 +14,7 @@ namespace Simple_QR_Code_Maker.Helpers; public static class BarcodeHelpers { - public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background) + public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null) { BitmapRenderer bitmapRenderer = new() { @@ -38,6 +38,13 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti barcodeWriter.Options = encodingOptions; using Bitmap bitmap = barcodeWriter.Write(text); + + // If a logo is provided, overlay it on the center of the QR code + if (logoImage != null) + { + OverlayLogoOnQrCode(bitmap, logoImage); + } + using MemoryStream ms = new(); bitmap.Save(ms, ImageFormat.Png); WriteableBitmap bitmapImage = new(encodingOptions.Width, encodingOptions.Height); @@ -47,6 +54,31 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti return bitmapImage; } + private static void OverlayLogoOnQrCode(Bitmap qrCode, Bitmap logo) + { + // Calculate the size of the logo (typically 20-30% of QR code size to maintain scannability) + int logoSize = Math.Min(qrCode.Width, qrCode.Height) / 5; // 20% of QR code size + + // Calculate the position to center the logo + int x = (qrCode.Width - logoSize) / 2; + int y = (qrCode.Height - logoSize) / 2; + + using Graphics g = Graphics.FromImage(qrCode); + // Set high quality rendering + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + + // Draw a white background circle/rectangle behind the logo for better visibility + int padding = 8; + using SolidBrush whiteBrush = new(System.Drawing.Color.White); + g.FillRectangle(whiteBrush, x - padding, y - padding, logoSize + (padding * 2), logoSize + (padding * 2)); + + // Draw the logo + g.DrawImage(logo, x, y, logoSize, logoSize); + } + /// /// Calculate the smallest side of a QR code based on the distance between the camera and the QR code /// @@ -106,7 +138,7 @@ public static string SmallestSideWithUnits(double distance, int numberOfBlocks, return $"{smallestSideCm:F2} x {smallestSideCm:F2} cm"; } - public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background) + public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null) { SvgRenderer svgRenderer = new() { @@ -131,9 +163,45 @@ public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel cor SvgImage svg = barcodeWriter.Write(text); + // If a logo is provided, embed it in the SVG + if (logoImage != null) + { + svg = EmbedLogoInSvg(svg, logoImage); + } + return svg; } + private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo) + { + // Convert logo to base64 for embedding + string base64Logo; + using (MemoryStream ms = new()) + { + logo.Save(ms, ImageFormat.Png); + byte[] imageBytes = ms.ToArray(); + base64Logo = Convert.ToBase64String(imageBytes); + } + + // Calculate logo dimensions (20% of SVG size) + int logoSize = 1024 / 5; // 20% of 1024 + int x = (1024 - logoSize) / 2; + int y = (1024 - logoSize) / 2; + int padding = 8; + + // Insert the logo into the SVG content + string logoSvgElement = $@" + + + + "; + + // Find the closing tag and insert the logo before it + string modifiedContent = svg.Content.Replace("", logoSvgElement + "\n"); + + return new SvgImage(modifiedContent); + } + public static IEnumerable<(string, Result)> GetStringsFromImageFile(StorageFile storageFile) { Bitmap bitmap = new(storageFile.Path); diff --git a/Simple QR Code Maker/Models/BarcodeImageItem.cs b/Simple QR Code Maker/Models/BarcodeImageItem.cs index b943b94..f75d873 100644 --- a/Simple QR Code Maker/Models/BarcodeImageItem.cs +++ b/Simple QR Code Maker/Models/BarcodeImageItem.cs @@ -34,6 +34,8 @@ public partial class BarcodeImageItem : ObservableRecipient public Windows.UI.Color BackgroundColor { get; set; } + public System.Drawing.Bitmap? LogoImage { get; set; } + public QRCode QRCodeDetails => Encoder.encode(CodeAsText, ErrorCorrection); public string ToolTipText => $"Smallest recommended size {SmallestSide}, {CodeAsText}"; @@ -63,7 +65,7 @@ public async Task SaveCodeAsSvgFile(StorageFile file, System.Drawing.Color { try { - SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background); + SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background, LogoImage); using IRandomAccessStream randomAccessStream = await file.OpenAsync(FileAccessMode.ReadWrite); DataWriter dataWriter = new(randomAccessStream); dataWriter.WriteString(svgImage.Content); @@ -81,7 +83,7 @@ public string GetCodeAsSvgText(System.Drawing.Color foreground, System.Drawing.C { try { - SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background); + SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background, LogoImage); return svgImage.Content; } catch diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index dc55f19..aba40d5 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -86,6 +86,12 @@ public partial class MainViewModel : ObservableRecipient, INavigationAware [ObservableProperty] private bool copySharePopupOpen = false; + [ObservableProperty] + private System.Drawing.Bitmap? logoImage = null; + + [ObservableProperty] + private bool hasLogo = false; + private double MinSizeScanDistanceScaleFactor = 1; private readonly DispatcherTimer copyInfoBarTimer = new(); @@ -130,6 +136,13 @@ partial void OnForegroundColorChanged(Windows.UI.Color value) debounceTimer.Start(); } + partial void OnLogoImageChanged(System.Drawing.Bitmap? value) + { + HasLogo = value != null; + debounceTimer.Stop(); + debounceTimer.Start(); + } + public bool CanSaveImage { get => !string.IsNullOrWhiteSpace(UrlText); } partial void OnUrlTextChanged(string value) @@ -289,7 +302,8 @@ private void GenerateQrCodeFromOneLine(string text) textToUse, SelectedOption.ErrorCorrectionLevel, ForegroundColor.ToSystemDrawingColor(), - BackgroundColor.ToSystemDrawingColor()); + BackgroundColor.ToSystemDrawingColor(), + LogoImage); BarcodeImageItem barcodeImageItem = new() { CodeAsBitmap = bitmap, @@ -300,6 +314,7 @@ private void GenerateQrCodeFromOneLine(string text) ForegroundColor = ForegroundColor, BackgroundColor = BackgroundColor, MaxSizeScaleFactor = MinSizeScanDistanceScaleFactor, + LogoImage = LogoImage, }; double ratio = barcodeImageItem.ColorContrastRatio; @@ -574,6 +589,49 @@ private void AddNewLine() UrlText += $"\r{stringToAdd}"; } + [RelayCommand] + private async Task SelectLogo() + { + FileOpenPicker openPicker = new() + { + SuggestedStartLocation = PickerLocationId.PicturesLibrary, + }; + openPicker.FileTypeFilter.Add(".png"); + openPicker.FileTypeFilter.Add(".jpg"); + openPicker.FileTypeFilter.Add(".jpeg"); + openPicker.FileTypeFilter.Add(".bmp"); + openPicker.FileTypeFilter.Add(".gif"); + + Window window = new(); + IntPtr windowHandle = WindowNative.GetWindowHandle(window); + InitializeWithWindow.Initialize(openPicker, windowHandle); + + StorageFile file = await openPicker.PickSingleFileAsync(); + + if (file == null) + return; + + try + { + LogoImage = new System.Drawing.Bitmap(file.Path); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to load logo image: {ex.Message}"); + CodeInfoBarMessage = "Failed to load the selected image"; + ShowCodeInfoBar = true; + CodeInfoBarSeverity = InfoBarSeverity.Error; + CodeInfoBarTitle = "Error loading logo"; + } + } + + [RelayCommand] + private void RemoveLogo() + { + LogoImage?.Dispose(); + LogoImage = null; + } + private async Task WriteImageToFile(BarcodeImageItem imageItem, StorageFile file, FileKind kindOfFile) { switch (kindOfFile) diff --git a/Simple QR Code Maker/Views/MainPage.xaml b/Simple QR Code Maker/Views/MainPage.xaml index d950138..de1fc03 100644 --- a/Simple QR Code Maker/Views/MainPage.xaml +++ b/Simple QR Code Maker/Views/MainPage.xaml @@ -164,6 +164,23 @@ + + Date: Sat, 1 Nov 2025 04:05:56 +0000 Subject: [PATCH 04/39] Add proper disposal of logo images to prevent memory leaks Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com> --- Simple QR Code Maker/ViewModels/MainViewModel.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index aba40d5..a8184f9 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -262,6 +262,9 @@ private void CheckCanPasteText() debounceTimer.Tick -= DebounceTimer_Tick; Clipboard.ContentChanged -= Clipboard_ContentChanged; + + // Dispose of the logo image + LogoImage?.Dispose(); } private void PlaceholderTextTimer_Tick(object? sender, object e) @@ -613,6 +616,8 @@ private async Task SelectLogo() try { + // Dispose of the old logo if it exists + LogoImage?.Dispose(); LogoImage = new System.Drawing.Bitmap(file.Path); } catch (Exception ex) From 6197c21a8df84565e2fa5b959788e49ec89d5753 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 04:07:58 +0000 Subject: [PATCH 05/39] Address code review feedback: extract magic numbers and use stream for file access Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com> --- Simple QR Code Maker/Helpers/BarcodeHelpers.cs | 15 ++++++++------- Simple QR Code Maker/ViewModels/MainViewModel.cs | 5 ++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs index ba45f12..9c9af56 100644 --- a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs +++ b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs @@ -14,6 +14,8 @@ namespace Simple_QR_Code_Maker.Helpers; public static class BarcodeHelpers { + private const int LOGO_PADDING = 8; + public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null) { BitmapRenderer bitmapRenderer = new() @@ -71,9 +73,8 @@ private static void OverlayLogoOnQrCode(Bitmap qrCode, Bitmap logo) g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; // Draw a white background circle/rectangle behind the logo for better visibility - int padding = 8; using SolidBrush whiteBrush = new(System.Drawing.Color.White); - g.FillRectangle(whiteBrush, x - padding, y - padding, logoSize + (padding * 2), logoSize + (padding * 2)); + g.FillRectangle(whiteBrush, x - LOGO_PADDING, y - LOGO_PADDING, logoSize + (LOGO_PADDING * 2), logoSize + (LOGO_PADDING * 2)); // Draw the logo g.DrawImage(logo, x, y, logoSize, logoSize); @@ -184,15 +185,15 @@ private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo) } // Calculate logo dimensions (20% of SVG size) - int logoSize = 1024 / 5; // 20% of 1024 - int x = (1024 - logoSize) / 2; - int y = (1024 - logoSize) / 2; - int padding = 8; + const int svgSize = 1024; // Should match the encoding options Width/Height + int logoSize = svgSize / 5; // 20% of SVG size + int x = (svgSize - logoSize) / 2; + int y = (svgSize - logoSize) / 2; // Insert the logo into the SVG content string logoSvgElement = $@" - + "; diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index a8184f9..04c636e 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -618,7 +618,10 @@ private async Task SelectLogo() { // Dispose of the old logo if it exists LogoImage?.Dispose(); - LogoImage = new System.Drawing.Bitmap(file.Path); + + // Use stream to access file instead of direct path for better compatibility + using var stream = await file.OpenReadAsync(); + LogoImage = new System.Drawing.Bitmap(stream.AsStreamForRead()); } catch (Exception ex) { From 305bd878acebb502a9f3aba034ff1efbb5268c8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:04:06 +0000 Subject: [PATCH 06/39] Preserve logo aspect ratio to prevent distortion Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com> --- .../Helpers/BarcodeHelpers.cs | 74 +++++++++++++++---- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs index 9c9af56..707c9e8 100644 --- a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs +++ b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs @@ -58,12 +58,33 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti private static void OverlayLogoOnQrCode(Bitmap qrCode, Bitmap logo) { - // Calculate the size of the logo (typically 20-30% of QR code size to maintain scannability) - int logoSize = Math.Min(qrCode.Width, qrCode.Height) / 5; // 20% of QR code size + // Calculate the maximum size of the logo (20% of QR code size to maintain scannability) + int maxLogoSize = Math.Min(qrCode.Width, qrCode.Height) / 5; + + // Calculate logo dimensions preserving aspect ratio + float aspectRatio = (float)logo.Width / logo.Height; + int logoWidth, logoHeight; + + if (aspectRatio > 1) // Wider than tall + { + logoWidth = maxLogoSize; + logoHeight = (int)(maxLogoSize / aspectRatio); + } + else // Taller than wide or square + { + logoHeight = maxLogoSize; + logoWidth = (int)(maxLogoSize * aspectRatio); + } // Calculate the position to center the logo - int x = (qrCode.Width - logoSize) / 2; - int y = (qrCode.Height - logoSize) / 2; + int x = (qrCode.Width - logoWidth) / 2; + int y = (qrCode.Height - logoHeight) / 2; + + // Calculate background rectangle size (fits the actual logo dimensions) + int bgWidth = logoWidth + (LOGO_PADDING * 2); + int bgHeight = logoHeight + (LOGO_PADDING * 2); + int bgX = x - LOGO_PADDING; + int bgY = y - LOGO_PADDING; using Graphics g = Graphics.FromImage(qrCode); // Set high quality rendering @@ -72,12 +93,12 @@ private static void OverlayLogoOnQrCode(Bitmap qrCode, Bitmap logo) g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - // Draw a white background circle/rectangle behind the logo for better visibility + // Draw a white background rectangle behind the logo for better visibility using SolidBrush whiteBrush = new(System.Drawing.Color.White); - g.FillRectangle(whiteBrush, x - LOGO_PADDING, y - LOGO_PADDING, logoSize + (LOGO_PADDING * 2), logoSize + (LOGO_PADDING * 2)); + g.FillRectangle(whiteBrush, bgX, bgY, bgWidth, bgHeight); - // Draw the logo - g.DrawImage(logo, x, y, logoSize, logoSize); + // Draw the logo with preserved aspect ratio + g.DrawImage(logo, x, y, logoWidth, logoHeight); } /// @@ -184,18 +205,41 @@ private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo) base64Logo = Convert.ToBase64String(imageBytes); } - // Calculate logo dimensions (20% of SVG size) + // Calculate logo dimensions preserving aspect ratio (20% of SVG size) const int svgSize = 1024; // Should match the encoding options Width/Height - int logoSize = svgSize / 5; // 20% of SVG size - int x = (svgSize - logoSize) / 2; - int y = (svgSize - logoSize) / 2; + int maxLogoSize = svgSize / 5; // 20% of SVG size + + // Calculate logo dimensions preserving aspect ratio + float aspectRatio = (float)logo.Width / logo.Height; + int logoWidth, logoHeight; + + if (aspectRatio > 1) // Wider than tall + { + logoWidth = maxLogoSize; + logoHeight = (int)(maxLogoSize / aspectRatio); + } + else // Taller than wide or square + { + logoHeight = maxLogoSize; + logoWidth = (int)(maxLogoSize * aspectRatio); + } + + // Calculate centered position + int x = (svgSize - logoWidth) / 2; + int y = (svgSize - logoHeight) / 2; + + // Calculate background rectangle size + int bgWidth = logoWidth + (LOGO_PADDING * 2); + int bgHeight = logoHeight + (LOGO_PADDING * 2); + int bgX = x - LOGO_PADDING; + int bgY = y - LOGO_PADDING; // Insert the logo into the SVG content string logoSvgElement = $@" - - - "; + + + "; // Find the closing tag and insert the logo before it string modifiedContent = svg.Content.Replace("", logoSvgElement + "\n"); From 763412dbf5481d2003c1106dd38e22139637a9aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:19:46 +0000 Subject: [PATCH 07/39] Add logo size slider with error correction-based limits and optimize SVG encoding Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com> --- .../Helpers/BarcodeHelpers.cs | 50 ++++++++++++------- .../Models/BarcodeImageItem.cs | 6 ++- .../ViewModels/MainViewModel.cs | 37 +++++++++++++- Simple QR Code Maker/Views/MainPage.xaml | 26 ++++++++++ 4 files changed, 97 insertions(+), 22 deletions(-) diff --git a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs index 707c9e8..72e69cb 100644 --- a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs +++ b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs @@ -16,7 +16,7 @@ public static class BarcodeHelpers { private const int LOGO_PADDING = 8; - public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null) + public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null, double logoSizePercentage = 20.0) { BitmapRenderer bitmapRenderer = new() { @@ -44,7 +44,7 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti // If a logo is provided, overlay it on the center of the QR code if (logoImage != null) { - OverlayLogoOnQrCode(bitmap, logoImage); + OverlayLogoOnQrCode(bitmap, logoImage, logoSizePercentage); } using MemoryStream ms = new(); @@ -56,10 +56,10 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti return bitmapImage; } - private static void OverlayLogoOnQrCode(Bitmap qrCode, Bitmap logo) + private static void OverlayLogoOnQrCode(Bitmap qrCode, Bitmap logo, double sizePercentage = 20.0) { - // Calculate the maximum size of the logo (20% of QR code size to maintain scannability) - int maxLogoSize = Math.Min(qrCode.Width, qrCode.Height) / 5; + // Calculate the maximum size of the logo based on the size percentage + int maxLogoSize = (int)(Math.Min(qrCode.Width, qrCode.Height) * (sizePercentage / 100.0)); // Calculate logo dimensions preserving aspect ratio float aspectRatio = (float)logo.Width / logo.Height; @@ -160,7 +160,7 @@ public static string SmallestSideWithUnits(double distance, int numberOfBlocks, return $"{smallestSideCm:F2} x {smallestSideCm:F2} cm"; } - public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null) + public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null, double logoSizePercentage = 20.0) { SvgRenderer svgRenderer = new() { @@ -188,26 +188,17 @@ public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel cor // If a logo is provided, embed it in the SVG if (logoImage != null) { - svg = EmbedLogoInSvg(svg, logoImage); + svg = EmbedLogoInSvg(svg, logoImage, logoSizePercentage); } return svg; } - private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo) + private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePercentage = 20.0) { - // Convert logo to base64 for embedding - string base64Logo; - using (MemoryStream ms = new()) - { - logo.Save(ms, ImageFormat.Png); - byte[] imageBytes = ms.ToArray(); - base64Logo = Convert.ToBase64String(imageBytes); - } - - // Calculate logo dimensions preserving aspect ratio (20% of SVG size) + // Calculate logo dimensions preserving aspect ratio const int svgSize = 1024; // Should match the encoding options Width/Height - int maxLogoSize = svgSize / 5; // 20% of SVG size + int maxLogoSize = (int)(svgSize * (sizePercentage / 100.0)); // Calculate logo dimensions preserving aspect ratio float aspectRatio = (float)logo.Width / logo.Height; @@ -224,6 +215,27 @@ private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo) logoWidth = (int)(maxLogoSize * aspectRatio); } + // Resize the logo before encoding to reduce SVG file size + Bitmap resizedLogo = new(logoWidth, logoHeight); + using (Graphics g = Graphics.FromImage(resizedLogo)) + { + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + g.DrawImage(logo, 0, 0, logoWidth, logoHeight); + } + + // Convert resized logo to base64 for embedding + string base64Logo; + using (MemoryStream ms = new()) + { + resizedLogo.Save(ms, ImageFormat.Png); + byte[] imageBytes = ms.ToArray(); + base64Logo = Convert.ToBase64String(imageBytes); + } + resizedLogo.Dispose(); + // Calculate centered position int x = (svgSize - logoWidth) / 2; int y = (svgSize - logoHeight) / 2; diff --git a/Simple QR Code Maker/Models/BarcodeImageItem.cs b/Simple QR Code Maker/Models/BarcodeImageItem.cs index f75d873..cdeefe0 100644 --- a/Simple QR Code Maker/Models/BarcodeImageItem.cs +++ b/Simple QR Code Maker/Models/BarcodeImageItem.cs @@ -36,6 +36,8 @@ public partial class BarcodeImageItem : ObservableRecipient public System.Drawing.Bitmap? LogoImage { get; set; } + public double LogoSizePercentage { get; set; } = 20.0; + public QRCode QRCodeDetails => Encoder.encode(CodeAsText, ErrorCorrection); public string ToolTipText => $"Smallest recommended size {SmallestSide}, {CodeAsText}"; @@ -65,7 +67,7 @@ public async Task SaveCodeAsSvgFile(StorageFile file, System.Drawing.Color { try { - SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background, LogoImage); + SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background, LogoImage, LogoSizePercentage); using IRandomAccessStream randomAccessStream = await file.OpenAsync(FileAccessMode.ReadWrite); DataWriter dataWriter = new(randomAccessStream); dataWriter.WriteString(svgImage.Content); @@ -83,7 +85,7 @@ public string GetCodeAsSvgText(System.Drawing.Color foreground, System.Drawing.C { try { - SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background, LogoImage); + SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background, LogoImage, LogoSizePercentage); return svgImage.Content; } catch diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index 04c636e..456d20f 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -92,6 +92,11 @@ public partial class MainViewModel : ObservableRecipient, INavigationAware [ObservableProperty] private bool hasLogo = false; + [ObservableProperty] + private double logoSizePercentage = 20.0; // Default 20% of QR code size + + public double MaxLogoSizePercentage => GetMaxLogoSizeForErrorCorrection(); + private double MinSizeScanDistanceScaleFactor = 1; private readonly DispatcherTimer copyInfoBarTimer = new(); @@ -120,6 +125,13 @@ partial void OnSelectedHistoryItemChanged(HistoryItem? value) partial void OnSelectedOptionChanged(ErrorCorrectionOptions value) { + // Ensure logo size doesn't exceed the new error correction level's maximum + if (LogoSizePercentage > MaxLogoSizePercentage) + { + LogoSizePercentage = MaxLogoSizePercentage; + } + OnPropertyChanged(nameof(MaxLogoSizePercentage)); + debounceTimer.Stop(); debounceTimer.Start(); } @@ -143,6 +155,27 @@ partial void OnLogoImageChanged(System.Drawing.Bitmap? value) debounceTimer.Start(); } + partial void OnLogoSizePercentageChanged(double value) + { + debounceTimer.Stop(); + debounceTimer.Start(); + } + + private double GetMaxLogoSizeForErrorCorrection() + { + // Error correction allows us to obscure a percentage of the QR code + // We use a conservative estimate (80% of the theoretical maximum) + // to ensure reliable scanning + return SelectedOption.ErrorCorrectionLevel switch + { + ErrorCorrectionLevel.L => 7.0 * 0.8, // ~5.6% max + ErrorCorrectionLevel.M => 15.0 * 0.8, // ~12% max + ErrorCorrectionLevel.Q => 25.0 * 0.8, // ~20% max + ErrorCorrectionLevel.H => 30.0 * 0.8, // ~24% max + _ => 20.0 + }; + } + public bool CanSaveImage { get => !string.IsNullOrWhiteSpace(UrlText); } partial void OnUrlTextChanged(string value) @@ -306,7 +339,8 @@ private void GenerateQrCodeFromOneLine(string text) SelectedOption.ErrorCorrectionLevel, ForegroundColor.ToSystemDrawingColor(), BackgroundColor.ToSystemDrawingColor(), - LogoImage); + LogoImage, + LogoSizePercentage); BarcodeImageItem barcodeImageItem = new() { CodeAsBitmap = bitmap, @@ -318,6 +352,7 @@ private void GenerateQrCodeFromOneLine(string text) BackgroundColor = BackgroundColor, MaxSizeScaleFactor = MinSizeScanDistanceScaleFactor, LogoImage = LogoImage, + LogoSizePercentage = LogoSizePercentage, }; double ratio = barcodeImageItem.ColorContrastRatio; diff --git a/Simple QR Code Maker/Views/MainPage.xaml b/Simple QR Code Maker/Views/MainPage.xaml index de1fc03..512c44c 100644 --- a/Simple QR Code Maker/Views/MainPage.xaml +++ b/Simple QR Code Maker/Views/MainPage.xaml @@ -183,6 +183,32 @@ + + + + + + + + + Date: Sat, 1 Nov 2025 11:37:42 -0500 Subject: [PATCH 08/39] Improve QR code logo handling and error correction Enhanced logo overlay precision by aligning dimensions and padding to QR code module boundaries. Updated SVG logo embedding for consistency and accuracy. Increased allowable logo size for error correction levels. Added null checks for clipboard operations to improve robustness. Refactored code for better readability and maintainability. --- .../Helpers/BarcodeHelpers.cs | 150 ++++++++++++------ .../Models/BarcodeImageItem.cs | 6 + .../ViewModels/MainViewModel.cs | 22 +-- 3 files changed, 123 insertions(+), 55 deletions(-) diff --git a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs index 72e69cb..704cc97 100644 --- a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs +++ b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs @@ -44,7 +44,10 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti // If a logo is provided, overlay it on the center of the QR code if (logoImage != null) { - OverlayLogoOnQrCode(bitmap, logoImage, logoSizePercentage); + // Get the QR code details to calculate module size + QRCode qrCode = ZXing.QrCode.Internal.Encoder.encode(text, correctionLevel); + int moduleCount = qrCode.Version.DimensionForVersion; + OverlayLogoOnQrCode(bitmap, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin); } using MemoryStream ms = new(); @@ -56,10 +59,24 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti return bitmapImage; } - private static void OverlayLogoOnQrCode(Bitmap qrCode, Bitmap logo, double sizePercentage = 20.0) + private static void OverlayLogoOnQrCode(Bitmap qrCodeBitmap, Bitmap logo, double sizePercentage, int moduleCount, int margin) { + // Calculate the pixel size of each QR code module + // The total size includes the margin on both sides + int totalModules = moduleCount + (margin * 2); + double modulePixelSize = (double)qrCodeBitmap.Width / totalModules; + // Calculate the maximum size of the logo based on the size percentage - int maxLogoSize = (int)(Math.Min(qrCode.Width, qrCode.Height) * (sizePercentage / 100.0)); + int maxLogoSizePixels = (int)(Math.Min(qrCodeBitmap.Width, qrCodeBitmap.Height) * (sizePercentage / 100.0)); + + // Round the max logo size to the nearest module boundary + int maxLogoSizeModules = (int)Math.Round(maxLogoSizePixels / modulePixelSize); + // Ensure it's at least 1 module and odd number for better centering + if (maxLogoSizeModules < 1) maxLogoSizeModules = 1; + if (maxLogoSizeModules % 2 == 0) maxLogoSizeModules++; // Make it odd for symmetry + + // Convert back to pixels, aligned to module boundaries + int maxLogoSize = (int)(maxLogoSizeModules * modulePixelSize); // Calculate logo dimensions preserving aspect ratio float aspectRatio = (float)logo.Width / logo.Height; @@ -69,24 +86,39 @@ private static void OverlayLogoOnQrCode(Bitmap qrCode, Bitmap logo, double sizeP { logoWidth = maxLogoSize; logoHeight = (int)(maxLogoSize / aspectRatio); + // Round height to nearest module + int heightModules = (int)Math.Round(logoHeight / modulePixelSize); + if (heightModules < 1) heightModules = 1; + if (heightModules % 2 == 0) heightModules++; + logoHeight = (int)(heightModules * modulePixelSize); } else // Taller than wide or square { logoHeight = maxLogoSize; logoWidth = (int)(maxLogoSize * aspectRatio); + // Round width to nearest module + int widthModules = (int)Math.Round(logoWidth / modulePixelSize); + if (widthModules < 1) widthModules = 1; + if (widthModules % 2 == 0) widthModules++; + logoWidth = (int)(widthModules * modulePixelSize); } // Calculate the position to center the logo - int x = (qrCode.Width - logoWidth) / 2; - int y = (qrCode.Height - logoHeight) / 2; + int x = (qrCodeBitmap.Width - logoWidth) / 2; + int y = (qrCodeBitmap.Height - logoHeight) / 2; + + // Round padding to module boundaries + int paddingModules = (int)Math.Round(LOGO_PADDING / modulePixelSize); + if (paddingModules < 1) paddingModules = 1; + int modulePadding = (int)(paddingModules * modulePixelSize); // Calculate background rectangle size (fits the actual logo dimensions) - int bgWidth = logoWidth + (LOGO_PADDING * 2); - int bgHeight = logoHeight + (LOGO_PADDING * 2); - int bgX = x - LOGO_PADDING; - int bgY = y - LOGO_PADDING; + int bgWidth = logoWidth + (modulePadding * 2); + int bgHeight = logoHeight + (modulePadding * 2); + int bgX = x - modulePadding; + int bgY = y - modulePadding; - using Graphics g = Graphics.FromImage(qrCode); + using Graphics g = Graphics.FromImage(qrCodeBitmap); // Set high quality rendering g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; @@ -188,49 +220,72 @@ public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel cor // If a logo is provided, embed it in the SVG if (logoImage != null) { - svg = EmbedLogoInSvg(svg, logoImage, logoSizePercentage); + // Get the QR code details to calculate module size + QRCode qrCode = ZXing.QrCode.Internal.Encoder.encode(text, correctionLevel); + int moduleCount = qrCode.Version.DimensionForVersion; + svg = EmbedLogoInSvg(svg, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin); } return svg; } - private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePercentage = 20.0) + private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePercentage, int moduleCount, int margin) { - // Calculate logo dimensions preserving aspect ratio const int svgSize = 1024; // Should match the encoding options Width/Height - int maxLogoSize = (int)(svgSize * (sizePercentage / 100.0)); - // Calculate logo dimensions preserving aspect ratio + // Calculate the pixel size of each QR code module + int totalModules = moduleCount + (margin * 2); + double modulePixelSize = (double)svgSize / totalModules; + + // Calculate the maximum size of the logo based on the size percentage + int maxLogoSizePixels = (int)(svgSize * (sizePercentage / 100.0)); + + // Round the max logo size to the nearest module boundary + int maxLogoSizeModules = (int)Math.Round(maxLogoSizePixels / modulePixelSize); + if (maxLogoSizeModules < 1) maxLogoSizeModules = 1; + if (maxLogoSizeModules % 2 == 0) maxLogoSizeModules++; + + int maxLogoSize = (int)(maxLogoSizeModules * modulePixelSize); + + // Calculate logo dimensions preserving aspect ratio float aspectRatio = (float)logo.Width / logo.Height; - int logoWidth, logoHeight; + int logoWidth, logoHeight; - if (aspectRatio > 1) // Wider than tall - { + if (aspectRatio > 1) // Wider than tall + { logoWidth = maxLogoSize; - logoHeight = (int)(maxLogoSize / aspectRatio); - } + logoHeight = (int)(maxLogoSize / aspectRatio); + int heightModules = (int)Math.Round(logoHeight / modulePixelSize); + if (heightModules < 1) heightModules = 1; + if (heightModules % 2 == 0) heightModules++; + logoHeight = (int)(heightModules * modulePixelSize); + } else // Taller than wide or square - { + { logoHeight = maxLogoSize; logoWidth = (int)(maxLogoSize * aspectRatio); + int widthModules = (int)Math.Round(logoWidth / modulePixelSize); + if (widthModules < 1) widthModules = 1; + if (widthModules % 2 == 0) widthModules++; + logoWidth = (int)(widthModules * modulePixelSize); } - - // Resize the logo before encoding to reduce SVG file size - Bitmap resizedLogo = new(logoWidth, logoHeight); + + // Resize the logo before encoding to reduce SVG file size + Bitmap resizedLogo = new(logoWidth, logoHeight); using (Graphics g = Graphics.FromImage(resizedLogo)) - { - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + { + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; g.DrawImage(logo, 0, 0, logoWidth, logoHeight); - } + } // Convert resized logo to base64 for embedding - string base64Logo; - using (MemoryStream ms = new()) - { - resizedLogo.Save(ms, ImageFormat.Png); + string base64Logo; + using (MemoryStream ms = new()) + { + resizedLogo.Save(ms, ImageFormat.Png); byte[] imageBytes = ms.ToArray(); base64Logo = Convert.ToBase64String(imageBytes); } @@ -239,24 +294,29 @@ private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePer // Calculate centered position int x = (svgSize - logoWidth) / 2; int y = (svgSize - logoHeight) / 2; - - // Calculate background rectangle size - int bgWidth = logoWidth + (LOGO_PADDING * 2); - int bgHeight = logoHeight + (LOGO_PADDING * 2); - int bgX = x - LOGO_PADDING; - int bgY = y - LOGO_PADDING; - - // Insert the logo into the SVG content + + // Round padding to module boundaries + int paddingModules = (int)Math.Round(LOGO_PADDING / modulePixelSize); + if (paddingModules < 1) paddingModules = 1; + int modulePadding = (int)(paddingModules * modulePixelSize); + + // Calculate background rectangle size + int bgWidth = logoWidth + (modulePadding * 2); + int bgHeight = logoHeight + (modulePadding * 2); + int bgX = x - modulePadding; + int bgY = y - modulePadding; + + // Insert the logo into the SVG content string logoSvgElement = $@" "; - // Find the closing tag and insert the logo before it - string modifiedContent = svg.Content.Replace("", logoSvgElement + "\n"); - - return new SvgImage(modifiedContent); + // Find the closing tag and insert the logo before it + string modifiedContent = svg.Content.Replace("", logoSvgElement + "\n"); + + return new SvgImage(modifiedContent); } public static IEnumerable<(string, Result)> GetStringsFromImageFile(StorageFile storageFile) diff --git a/Simple QR Code Maker/Models/BarcodeImageItem.cs b/Simple QR Code Maker/Models/BarcodeImageItem.cs index cdeefe0..e2b64f7 100644 --- a/Simple QR Code Maker/Models/BarcodeImageItem.cs +++ b/Simple QR Code Maker/Models/BarcodeImageItem.cs @@ -130,6 +130,12 @@ private async Task SaveCodeSvgContext() [RelayCommand] private async Task CopyCodePngContext() { + if (CodeAsBitmap is null) + { + WeakReferenceMessenger.Default.Send(new RequestShowMessage("Failed to copy QR Code to the clipboard", "No QR Code to copy to the clipboard", InfoBarSeverity.Error)); + return; + } + StorageFolder folder = ApplicationData.Current.LocalCacheFolder; List files = []; diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index 456d20f..c2b69c1 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -164,16 +164,18 @@ partial void OnLogoSizePercentageChanged(double value) private double GetMaxLogoSizeForErrorCorrection() { // Error correction allows us to obscure a percentage of the QR code - // We use a conservative estimate (80% of the theoretical maximum) - // to ensure reliable scanning - return SelectedOption.ErrorCorrectionLevel switch - { - ErrorCorrectionLevel.L => 7.0 * 0.8, // ~5.6% max - ErrorCorrectionLevel.M => 15.0 * 0.8, // ~12% max - ErrorCorrectionLevel.Q => 25.0 * 0.8, // ~20% max - ErrorCorrectionLevel.H => 30.0 * 0.8, // ~24% max - _ => 20.0 - }; + // We use 95% of the theoretical maximum to allow larger logos + // while still maintaining reliable scanning + if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.L) + return 7.0 * 0.95; // ~6.65% max (increased from ~5.6%) + else if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.M) + return 15.0 * 0.95; // ~14.25% max (increased from ~12%) + else if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.Q) + return 25.0 * 0.95; // ~23.75% max (increased from ~20%) + else if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.H) + return 30.0 * 0.95; // ~28.5% max (increased from ~24%) + else + return 20.0; } public bool CanSaveImage { get => !string.IsNullOrWhiteSpace(UrlText); } From 71cea3e940fcf91e4b240f3f7b2458fcb9f0f9f2 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Sat, 1 Nov 2025 11:54:38 -0500 Subject: [PATCH 09/39] Add logo padding support for QR code generation Introduced `logoPaddingPixels` parameter to control padding or cropping around logos in QR codes. Updated `BarcodeHelpers`, `BarcodeImageItem`, and `MainViewModel` to support this feature. Enhanced SVG and bitmap generation logic, added a UI slider for dynamic padding adjustment, and improved code consistency and rendering quality. --- .../Helpers/BarcodeHelpers.cs | 185 ++++++++++++------ .../Models/BarcodeImageItem.cs | 6 +- .../ViewModels/MainViewModel.cs | 13 +- Simple QR Code Maker/Views/MainPage.xaml | 25 +++ 4 files changed, 162 insertions(+), 67 deletions(-) diff --git a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs index 704cc97..8f4d42a 100644 --- a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs +++ b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs @@ -14,9 +14,7 @@ namespace Simple_QR_Code_Maker.Helpers; public static class BarcodeHelpers { - private const int LOGO_PADDING = 8; - - public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null, double logoSizePercentage = 20.0) + public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null, double logoSizePercentage = 20.0, double logoPaddingPixels = 8.0) { BitmapRenderer bitmapRenderer = new() { @@ -47,7 +45,7 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti // Get the QR code details to calculate module size QRCode qrCode = ZXing.QrCode.Internal.Encoder.encode(text, correctionLevel); int moduleCount = qrCode.Version.DimensionForVersion; - OverlayLogoOnQrCode(bitmap, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin); + OverlayLogoOnQrCode(bitmap, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin, logoPaddingPixels); } using MemoryStream ms = new(); @@ -59,7 +57,7 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti return bitmapImage; } - private static void OverlayLogoOnQrCode(Bitmap qrCodeBitmap, Bitmap logo, double sizePercentage, int moduleCount, int margin) + private static void OverlayLogoOnQrCode(Bitmap qrCodeBitmap, Bitmap logo, double sizePercentage, int moduleCount, int margin, double logoPaddingPixels) { // Calculate the pixel size of each QR code module // The total size includes the margin on both sides @@ -106,31 +104,45 @@ private static void OverlayLogoOnQrCode(Bitmap qrCodeBitmap, Bitmap logo, double // Calculate the position to center the logo int x = (qrCodeBitmap.Width - logoWidth) / 2; int y = (qrCodeBitmap.Height - logoHeight) / 2; - - // Round padding to module boundaries - int paddingModules = (int)Math.Round(LOGO_PADDING / modulePixelSize); - if (paddingModules < 1) paddingModules = 1; - int modulePadding = (int)(paddingModules * modulePixelSize); - + + // Convert padding pixels to actual pixels (can be negative for cropping) + int modulePadding = (int)Math.Round(logoPaddingPixels); + // Calculate background rectangle size (fits the actual logo dimensions) int bgWidth = logoWidth + (modulePadding * 2); - int bgHeight = logoHeight + (modulePadding * 2); - int bgX = x - modulePadding; + int bgHeight = logoHeight + (modulePadding * 2); + int bgX = x - modulePadding; int bgY = y - modulePadding; - using Graphics g = Graphics.FromImage(qrCodeBitmap); + using Graphics g = Graphics.FromImage(qrCodeBitmap); // Set high quality rendering g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - // Draw a white background rectangle behind the logo for better visibility - using SolidBrush whiteBrush = new(System.Drawing.Color.White); - g.FillRectangle(whiteBrush, bgX, bgY, bgWidth, bgHeight); + // Draw a white background rectangle behind the logo for better visibility + // Only draw if padding is positive (adding space around logo) + if (modulePadding > 0) + { + using SolidBrush whiteBrush = new(System.Drawing.Color.White); + g.FillRectangle(whiteBrush, bgX, bgY, bgWidth, bgHeight); + } - // Draw the logo with preserved aspect ratio - g.DrawImage(logo, x, y, logoWidth, logoHeight); + // If padding is negative, we need to crop the logo + if (modulePadding < 0) + { + // Draw the logo cropped + int cropAmount = -modulePadding; + Rectangle srcRect = new(cropAmount, cropAmount, logoWidth - (cropAmount * 2), logoHeight - (cropAmount * 2)); + Rectangle destRect = new(x + cropAmount, y + cropAmount, logoWidth - (cropAmount * 2), logoHeight - (cropAmount * 2)); + g.DrawImage(logo, destRect, srcRect, GraphicsUnit.Pixel); + } + else + { + // Draw the logo with preserved aspect ratio at normal or expanded size + g.DrawImage(logo, x, y, logoWidth, logoHeight); + } } /// @@ -192,7 +204,7 @@ public static string SmallestSideWithUnits(double distance, int numberOfBlocks, return $"{smallestSideCm:F2} x {smallestSideCm:F2} cm"; } - public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null, double logoSizePercentage = 20.0) + public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null, double logoSizePercentage = 20.0, double logoPaddingPixels = 8.0) { SvgRenderer svgRenderer = new() { @@ -223,13 +235,13 @@ public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel cor // Get the QR code details to calculate module size QRCode qrCode = ZXing.QrCode.Internal.Encoder.encode(text, correctionLevel); int moduleCount = qrCode.Version.DimensionForVersion; - svg = EmbedLogoInSvg(svg, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin); + svg = EmbedLogoInSvg(svg, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin, logoPaddingPixels); } return svg; } - private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePercentage, int moduleCount, int margin) + private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePercentage, int moduleCount, int margin, double logoPaddingPixels) { const int svgSize = 1024; // Should match the encoding options Width/Height @@ -253,70 +265,115 @@ private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePer if (aspectRatio > 1) // Wider than tall { - logoWidth = maxLogoSize; + logoWidth = maxLogoSize; logoHeight = (int)(maxLogoSize / aspectRatio); - int heightModules = (int)Math.Round(logoHeight / modulePixelSize); - if (heightModules < 1) heightModules = 1; - if (heightModules % 2 == 0) heightModules++; - logoHeight = (int)(heightModules * modulePixelSize); + int heightModules = (int)Math.Round(logoHeight / modulePixelSize); + if (heightModules < 1) heightModules = 1; + if (heightModules % 2 == 0) heightModules++; + logoHeight = (int)(heightModules * modulePixelSize); } - else // Taller than wide or square +else // Taller than wide or square { logoHeight = maxLogoSize; - logoWidth = (int)(maxLogoSize * aspectRatio); + logoWidth = (int)(maxLogoSize * aspectRatio); int widthModules = (int)Math.Round(logoWidth / modulePixelSize); if (widthModules < 1) widthModules = 1; - if (widthModules % 2 == 0) widthModules++; - logoWidth = (int)(widthModules * modulePixelSize); + if (widthModules % 2 == 0) widthModules++; + logoWidth = (int)(widthModules * modulePixelSize); } - // Resize the logo before encoding to reduce SVG file size - Bitmap resizedLogo = new(logoWidth, logoHeight); - using (Graphics g = Graphics.FromImage(resizedLogo)) + // Calculate centered position + int x = (svgSize - logoWidth) / 2; + int y = (svgSize - logoHeight) / 2; + + // Convert padding pixels to actual pixels (can be negative for cropping) + int modulePadding = (int)Math.Round(logoPaddingPixels); + + // Calculate background rectangle size + int bgWidth = logoWidth + (modulePadding * 2); + int bgHeight = logoHeight + (modulePadding * 2); + int bgX = x - modulePadding; + int bgY = y - modulePadding; + + // If padding is negative, we need to crop the logo before embedding + Bitmap logoToEmbed = logo; + int logoX = x; + int logoY = y; + int displayWidth = logoWidth; + int displayHeight = logoHeight; + + if (modulePadding < 0) + { + // Crop the logo + int cropAmount = -modulePadding; + int croppedWidth = Math.Max(1, logoWidth - (cropAmount * 2)); + int croppedHeight = Math.Max(1, logoHeight - (cropAmount * 2)); + + logoToEmbed = new Bitmap(croppedWidth, croppedHeight); + using (Graphics g = Graphics.FromImage(logoToEmbed)) + { + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + + Rectangle srcRect = new(cropAmount, cropAmount, croppedWidth, croppedHeight); + Rectangle destRect = new(0, 0, croppedWidth, croppedHeight); + g.DrawImage(logo, destRect, srcRect, GraphicsUnit.Pixel); + } + + logoX = x + cropAmount; + logoY = y + cropAmount; + displayWidth = croppedWidth; + displayHeight = croppedHeight; + } + + // Resize the logo before encoding to reduce SVG file size + Bitmap resizedLogo = new(displayWidth, displayHeight); + using (Graphics g = Graphics.FromImage(resizedLogo)) { g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - g.DrawImage(logo, 0, 0, logoWidth, logoHeight); +g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + g.DrawImage(logoToEmbed, 0, 0, displayWidth, displayHeight); } - // Convert resized logo to base64 for embedding + // Clean up cropped logo if it was created + if (modulePadding < 0 && logoToEmbed != logo) + { + logoToEmbed.Dispose(); + } + + // Convert resized logo to base64 for embedding string base64Logo; using (MemoryStream ms = new()) { - resizedLogo.Save(ms, ImageFormat.Png); - byte[] imageBytes = ms.ToArray(); - base64Logo = Convert.ToBase64String(imageBytes); - } - resizedLogo.Dispose(); + resizedLogo.Save(ms, ImageFormat.Png); + byte[] imageBytes = ms.ToArray(); + base64Logo = Convert.ToBase64String(imageBytes); + } + resizedLogo.Dispose(); + + // Build the SVG logo element + string logoSvgElement = ""; - // Calculate centered position - int x = (svgSize - logoWidth) / 2; - int y = (svgSize - logoHeight) / 2; - - // Round padding to module boundaries - int paddingModules = (int)Math.Round(LOGO_PADDING / modulePixelSize); - if (paddingModules < 1) paddingModules = 1; - int modulePadding = (int)(paddingModules * modulePixelSize); - - // Calculate background rectangle size - int bgWidth = logoWidth + (modulePadding * 2); - int bgHeight = logoHeight + (modulePadding * 2); - int bgX = x - modulePadding; - int bgY = y - modulePadding; - - // Insert the logo into the SVG content - string logoSvgElement = $@" + // Only add background if padding is positive + if (modulePadding > 0) + { + logoSvgElement += $@" - + "; + } + + logoSvgElement += $@" - "; + "; - // Find the closing tag and insert the logo before it + // Find the closing tag and insert the logo before it string modifiedContent = svg.Content.Replace("", logoSvgElement + "\n"); - return new SvgImage(modifiedContent); +return new SvgImage(modifiedContent); } public static IEnumerable<(string, Result)> GetStringsFromImageFile(StorageFile storageFile) diff --git a/Simple QR Code Maker/Models/BarcodeImageItem.cs b/Simple QR Code Maker/Models/BarcodeImageItem.cs index e2b64f7..f0dc4c0 100644 --- a/Simple QR Code Maker/Models/BarcodeImageItem.cs +++ b/Simple QR Code Maker/Models/BarcodeImageItem.cs @@ -38,6 +38,8 @@ public partial class BarcodeImageItem : ObservableRecipient public double LogoSizePercentage { get; set; } = 20.0; + public double LogoPaddingPixels { get; set; } = 8.0; + public QRCode QRCodeDetails => Encoder.encode(CodeAsText, ErrorCorrection); public string ToolTipText => $"Smallest recommended size {SmallestSide}, {CodeAsText}"; @@ -67,7 +69,7 @@ public async Task SaveCodeAsSvgFile(StorageFile file, System.Drawing.Color { try { - SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background, LogoImage, LogoSizePercentage); + SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background, LogoImage, LogoSizePercentage, LogoPaddingPixels); using IRandomAccessStream randomAccessStream = await file.OpenAsync(FileAccessMode.ReadWrite); DataWriter dataWriter = new(randomAccessStream); dataWriter.WriteString(svgImage.Content); @@ -85,7 +87,7 @@ public string GetCodeAsSvgText(System.Drawing.Color foreground, System.Drawing.C { try { - SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background, LogoImage, LogoSizePercentage); + SvgImage svgImage = BarcodeHelpers.GetSvgQrCodeForText(CodeAsText, correctionLevel, foreground, background, LogoImage, LogoSizePercentage, LogoPaddingPixels); return svgImage.Content; } catch diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index c2b69c1..1d140af 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -95,6 +95,9 @@ public partial class MainViewModel : ObservableRecipient, INavigationAware [ObservableProperty] private double logoSizePercentage = 20.0; // Default 20% of QR code size + [ObservableProperty] + private double logoPaddingPixels = 8.0; // Default 8 pixels padding around logo + public double MaxLogoSizePercentage => GetMaxLogoSizeForErrorCorrection(); private double MinSizeScanDistanceScaleFactor = 1; @@ -161,6 +164,12 @@ partial void OnLogoSizePercentageChanged(double value) debounceTimer.Start(); } + partial void OnLogoPaddingPixelsChanged(double value) + { + debounceTimer.Stop(); + debounceTimer.Start(); + } + private double GetMaxLogoSizeForErrorCorrection() { // Error correction allows us to obscure a percentage of the QR code @@ -342,7 +351,8 @@ private void GenerateQrCodeFromOneLine(string text) ForegroundColor.ToSystemDrawingColor(), BackgroundColor.ToSystemDrawingColor(), LogoImage, - LogoSizePercentage); + LogoSizePercentage, + LogoPaddingPixels); BarcodeImageItem barcodeImageItem = new() { CodeAsBitmap = bitmap, @@ -355,6 +365,7 @@ private void GenerateQrCodeFromOneLine(string text) MaxSizeScaleFactor = MinSizeScanDistanceScaleFactor, LogoImage = LogoImage, LogoSizePercentage = LogoSizePercentage, + LogoPaddingPixels = LogoPaddingPixels, }; double ratio = barcodeImageItem.ColorContrastRatio; diff --git a/Simple QR Code Maker/Views/MainPage.xaml b/Simple QR Code Maker/Views/MainPage.xaml index 512c44c..7221fa5 100644 --- a/Simple QR Code Maker/Views/MainPage.xaml +++ b/Simple QR Code Maker/Views/MainPage.xaml @@ -209,6 +209,31 @@ + + + + + + + + + Date: Sat, 1 Nov 2025 12:21:43 -0500 Subject: [PATCH 10/39] Refactor QR code logo embedding and UI updates Refactored QR code logo embedding logic to use "punchout size" for better clarity and flexibility. Simplified calculations for punchout area, logo dimensions, and padding. Updated SVG embedding to reduce file size and align with new logic. Revised `MainPage.xaml` labels and tooltips to reflect updated terminology and functionality. --- .../Helpers/BarcodeHelpers.cs | 299 +++++++----------- Simple QR Code Maker/Views/MainPage.xaml | 8 +- 2 files changed, 126 insertions(+), 181 deletions(-) diff --git a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs index 8f4d42a..959da04 100644 --- a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs +++ b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs @@ -64,85 +64,72 @@ private static void OverlayLogoOnQrCode(Bitmap qrCodeBitmap, Bitmap logo, double int totalModules = moduleCount + (margin * 2); double modulePixelSize = (double)qrCodeBitmap.Width / totalModules; - // Calculate the maximum size of the logo based on the size percentage - int maxLogoSizePixels = (int)(Math.Min(qrCodeBitmap.Width, qrCodeBitmap.Height) * (sizePercentage / 100.0)); + // Calculate the punchout space size based on the size percentage + // This is the area that will be covered (blocking QR code modules) + int punchoutSizePixels = (int)(Math.Min(qrCodeBitmap.Width, qrCodeBitmap.Height) * (sizePercentage / 100.0)); - // Round the max logo size to the nearest module boundary - int maxLogoSizeModules = (int)Math.Round(maxLogoSizePixels / modulePixelSize); - // Ensure it's at least 1 module and odd number for better centering - if (maxLogoSizeModules < 1) maxLogoSizeModules = 1; - if (maxLogoSizeModules % 2 == 0) maxLogoSizeModules++; // Make it odd for symmetry + // Round the punchout size to the nearest module boundary + int punchoutSizeModules = (int)Math.Round(punchoutSizePixels / modulePixelSize); + // Ensure it's at least 1 module and odd number for better centering + if (punchoutSizeModules < 1) punchoutSizeModules = 1; + if (punchoutSizeModules % 2 == 0) punchoutSizeModules++; // Make it odd for symmetry // Convert back to pixels, aligned to module boundaries - int maxLogoSize = (int)(maxLogoSizeModules * modulePixelSize); + int punchoutSize = (int)(punchoutSizeModules * modulePixelSize); + + // Calculate the position to center the punchout area + int punchoutX = (qrCodeBitmap.Width - punchoutSize) / 2; + int punchoutY = (qrCodeBitmap.Height - punchoutSize) / 2; + + // Convert padding to actual pixels + // Positive padding = logo smaller than punchout (adds white space) + // Negative padding = logo larger than punchout (logo extends beyond white background) + int paddingPixels = (int)Math.Round(logoPaddingPixels); + + // Calculate the actual logo display size + // Logo fills the punchout area minus the padding on all sides + int logoDisplayWidth = Math.Max(1, punchoutSize - (Math.Abs(paddingPixels) * 2)); + int logoDisplayHeight = Math.Max(1, punchoutSize - (Math.Abs(paddingPixels) * 2)); + + // If padding is negative, logo is larger than punchout + if (paddingPixels < 0) + { + logoDisplayWidth = punchoutSize + (Math.Abs(paddingPixels) * 2); + logoDisplayHeight = punchoutSize + (Math.Abs(paddingPixels) * 2); + } // Calculate logo dimensions preserving aspect ratio - float aspectRatio = (float)logo.Width / logo.Height; + float aspectRatio = (float)logo.Width / logo.Height; int logoWidth, logoHeight; - + if (aspectRatio > 1) // Wider than tall - { - logoWidth = maxLogoSize; - logoHeight = (int)(maxLogoSize / aspectRatio); - // Round height to nearest module - int heightModules = (int)Math.Round(logoHeight / modulePixelSize); - if (heightModules < 1) heightModules = 1; - if (heightModules % 2 == 0) heightModules++; - logoHeight = (int)(heightModules * modulePixelSize); + { + logoWidth = logoDisplayWidth; + logoHeight = (int)(logoDisplayWidth / aspectRatio); } else // Taller than wide or square { - logoHeight = maxLogoSize; - logoWidth = (int)(maxLogoSize * aspectRatio); - // Round width to nearest module - int widthModules = (int)Math.Round(logoWidth / modulePixelSize); - if (widthModules < 1) widthModules = 1; - if (widthModules % 2 == 0) widthModules++; - logoWidth = (int)(widthModules * modulePixelSize); - } - - // Calculate the position to center the logo - int x = (qrCodeBitmap.Width - logoWidth) / 2; - int y = (qrCodeBitmap.Height - logoHeight) / 2; - - // Convert padding pixels to actual pixels (can be negative for cropping) - int modulePadding = (int)Math.Round(logoPaddingPixels); - - // Calculate background rectangle size (fits the actual logo dimensions) - int bgWidth = logoWidth + (modulePadding * 2); - int bgHeight = logoHeight + (modulePadding * 2); - int bgX = x - modulePadding; - int bgY = y - modulePadding; + logoHeight = logoDisplayHeight; + logoWidth = (int)(logoDisplayHeight * aspectRatio); + } + + // Center the logo within the punchout area (or offset if larger) + int logoX = punchoutX + (punchoutSize - logoWidth) / 2; + int logoY = punchoutY + (punchoutSize - logoHeight) / 2; - using Graphics g = Graphics.FromImage(qrCodeBitmap); + using Graphics g = Graphics.FromImage(qrCodeBitmap); // Set high quality rendering g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - // Draw a white background rectangle behind the logo for better visibility - // Only draw if padding is positive (adding space around logo) - if (modulePadding > 0) - { - using SolidBrush whiteBrush = new(System.Drawing.Color.White); - g.FillRectangle(whiteBrush, bgX, bgY, bgWidth, bgHeight); - } + // Always draw the white punchout background (covers QR code modules) +using SolidBrush whiteBrush = new(System.Drawing.Color.White); + g.FillRectangle(whiteBrush, punchoutX, punchoutY, punchoutSize, punchoutSize); - // If padding is negative, we need to crop the logo - if (modulePadding < 0) - { - // Draw the logo cropped - int cropAmount = -modulePadding; - Rectangle srcRect = new(cropAmount, cropAmount, logoWidth - (cropAmount * 2), logoHeight - (cropAmount * 2)); - Rectangle destRect = new(x + cropAmount, y + cropAmount, logoWidth - (cropAmount * 2), logoHeight - (cropAmount * 2)); - g.DrawImage(logo, destRect, srcRect, GraphicsUnit.Pixel); - } - else - { - // Draw the logo with preserved aspect ratio at normal or expanded size - g.DrawImage(logo, x, y, logoWidth, logoHeight); - } + // Draw the logo scaled to fit within or extend beyond the punchout + g.DrawImage(logo, logoX, logoY, logoWidth, logoHeight); } /// @@ -244,136 +231,94 @@ public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel cor private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePercentage, int moduleCount, int margin, double logoPaddingPixels) { const int svgSize = 1024; // Should match the encoding options Width/Height - + // Calculate the pixel size of each QR code module int totalModules = moduleCount + (margin * 2); double modulePixelSize = (double)svgSize / totalModules; - - // Calculate the maximum size of the logo based on the size percentage - int maxLogoSizePixels = (int)(svgSize * (sizePercentage / 100.0)); + + // Calculate the punchout space size based on the size percentage + // This is the area that will be covered (blocking QR code modules) +int punchoutSizePixels = (int)(svgSize * (sizePercentage / 100.0)); - // Round the max logo size to the nearest module boundary - int maxLogoSizeModules = (int)Math.Round(maxLogoSizePixels / modulePixelSize); - if (maxLogoSizeModules < 1) maxLogoSizeModules = 1; - if (maxLogoSizeModules % 2 == 0) maxLogoSizeModules++; + // Round the punchout size to the nearest module boundary + int punchoutSizeModules = (int)Math.Round(punchoutSizePixels / modulePixelSize); + if (punchoutSizeModules < 1) punchoutSizeModules = 1; + if (punchoutSizeModules % 2 == 0) punchoutSizeModules++; - int maxLogoSize = (int)(maxLogoSizeModules * modulePixelSize); + int punchoutSize = (int)(punchoutSizeModules * modulePixelSize); + + // Calculate the position to center the punchout area + int punchoutX = (svgSize - punchoutSize) / 2; + int punchoutY = (svgSize - punchoutSize) / 2; - // Calculate logo dimensions preserving aspect ratio + // Convert padding to actual pixels + // Positive padding = logo smaller than punchout (adds white space) + // Negative padding = logo larger than punchout (logo extends beyond white background) + int paddingPixels = (int)Math.Round(logoPaddingPixels); + + // Calculate the actual logo display size + // Logo fills the punchout area minus the padding on all sides + int logoDisplayWidth = Math.Max(1, punchoutSize - (Math.Abs(paddingPixels) * 2)); + int logoDisplayHeight = Math.Max(1, punchoutSize - (Math.Abs(paddingPixels) * 2)); + + // If padding is negative, logo is larger than punchout + if (paddingPixels < 0) + { + logoDisplayWidth = punchoutSize + (Math.Abs(paddingPixels) * 2); + logoDisplayHeight = punchoutSize + (Math.Abs(paddingPixels) * 2); + } + + // Calculate logo dimensions preserving aspect ratio float aspectRatio = (float)logo.Width / logo.Height; - int logoWidth, logoHeight; + int logoWidth, logoHeight; - if (aspectRatio > 1) // Wider than tall + if (aspectRatio > 1) // Wider than tall + { + logoWidth = logoDisplayWidth; + logoHeight = (int)(logoDisplayWidth / aspectRatio); + } + else // Taller than wide or square { - logoWidth = maxLogoSize; - logoHeight = (int)(maxLogoSize / aspectRatio); - int heightModules = (int)Math.Round(logoHeight / modulePixelSize); - if (heightModules < 1) heightModules = 1; - if (heightModules % 2 == 0) heightModules++; - logoHeight = (int)(heightModules * modulePixelSize); - } -else // Taller than wide or square - { - logoHeight = maxLogoSize; - logoWidth = (int)(maxLogoSize * aspectRatio); - int widthModules = (int)Math.Round(logoWidth / modulePixelSize); - if (widthModules < 1) widthModules = 1; - if (widthModules % 2 == 0) widthModules++; - logoWidth = (int)(widthModules * modulePixelSize); + logoHeight = logoDisplayHeight; + logoWidth = (int)(logoDisplayHeight * aspectRatio); } - - // Calculate centered position - int x = (svgSize - logoWidth) / 2; - int y = (svgSize - logoHeight) / 2; - - // Convert padding pixels to actual pixels (can be negative for cropping) - int modulePadding = (int)Math.Round(logoPaddingPixels); - - // Calculate background rectangle size - int bgWidth = logoWidth + (modulePadding * 2); - int bgHeight = logoHeight + (modulePadding * 2); - int bgX = x - modulePadding; - int bgY = y - modulePadding; - - // If padding is negative, we need to crop the logo before embedding - Bitmap logoToEmbed = logo; - int logoX = x; - int logoY = y; - int displayWidth = logoWidth; - int displayHeight = logoHeight; - - if (modulePadding < 0) - { - // Crop the logo - int cropAmount = -modulePadding; - int croppedWidth = Math.Max(1, logoWidth - (cropAmount * 2)); - int croppedHeight = Math.Max(1, logoHeight - (cropAmount * 2)); - - logoToEmbed = new Bitmap(croppedWidth, croppedHeight); - using (Graphics g = Graphics.FromImage(logoToEmbed)) + + // Center the logo within the punchout area (or offset if larger) + int logoX = punchoutX + (punchoutSize - logoWidth) / 2; + int logoY = punchoutY + (punchoutSize - logoHeight) / 2; + + // Resize the logo to the display size before encoding to reduce SVG file size + Bitmap resizedLogo = new(logoWidth, logoHeight); + using (Graphics g = Graphics.FromImage(resizedLogo)) { - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - - Rectangle srcRect = new(cropAmount, cropAmount, croppedWidth, croppedHeight); - Rectangle destRect = new(0, 0, croppedWidth, croppedHeight); - g.DrawImage(logo, destRect, srcRect, GraphicsUnit.Pixel); - } - - logoX = x + cropAmount; - logoY = y + cropAmount; - displayWidth = croppedWidth; - displayHeight = croppedHeight; - } - - // Resize the logo before encoding to reduce SVG file size - Bitmap resizedLogo = new(displayWidth, displayHeight); - using (Graphics g = Graphics.FromImage(resizedLogo)) - { - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; -g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - g.DrawImage(logoToEmbed, 0, 0, displayWidth, displayHeight); - } - - // Clean up cropped logo if it was created - if (modulePadding < 0 && logoToEmbed != logo) - { - logoToEmbed.Dispose(); - } - - // Convert resized logo to base64 for embedding - string base64Logo; - using (MemoryStream ms = new()) - { - resizedLogo.Save(ms, ImageFormat.Png); - byte[] imageBytes = ms.ToArray(); - base64Logo = Convert.ToBase64String(imageBytes); - } - resizedLogo.Dispose(); + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + g.DrawImage(logo, 0, 0, logoWidth, logoHeight); + } + + // Convert resized logo to base64 for embedding + string base64Logo; + using (MemoryStream ms = new()) + { + resizedLogo.Save(ms, ImageFormat.Png); + byte[] imageBytes = ms.ToArray(); + base64Logo = Convert.ToBase64String(imageBytes); + } + resizedLogo.Dispose(); - // Build the SVG logo element - string logoSvgElement = ""; - - // Only add background if padding is positive - if (modulePadding > 0) - { - logoSvgElement += $@" - - "; - } - - logoSvgElement += $@" - - "; + // Build the SVG logo element with punchout background and logo + string logoSvgElement = $@" + + + + "; - // Find the closing tag and insert the logo before it - string modifiedContent = svg.Content.Replace("", logoSvgElement + "\n"); + // Find the closing tag and insert the logo before it + string modifiedContent = svg.Content.Replace("", logoSvgElement + "\n"); -return new SvgImage(modifiedContent); + return new SvgImage(modifiedContent); } public static IEnumerable<(string, Result)> GetStringsFromImageFile(StorageFile storageFile) diff --git a/Simple QR Code Maker/Views/MainPage.xaml b/Simple QR Code Maker/Views/MainPage.xaml index 7221fa5..b8da2e9 100644 --- a/Simple QR Code Maker/Views/MainPage.xaml +++ b/Simple QR Code Maker/Views/MainPage.xaml @@ -190,7 +190,7 @@ Visibility="{x:Bind ViewModel.HasLogo, Mode=OneWay, Converter={StaticResource BoolToVisibility}}"> - + @@ -216,7 +216,7 @@ Visibility="{x:Bind ViewModel.HasLogo, Mode=OneWay, Converter={StaticResource BoolToVisibility}}"> - + From a390cd9d593614e1a2a962fd3c11df7eaacae6e8 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Sat, 1 Nov 2025 16:21:35 -0500 Subject: [PATCH 11/39] Add support for custom QR code background color Updated `OverlayLogoOnQrCode` and `EmbedLogoInSvg` methods to accept a `backgroundColor` parameter, enabling customization of the punchout background color in both bitmap and SVG formats. Adjusted related logic and comments to reflect this enhancement. --- .../Helpers/BarcodeHelpers.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs index 959da04..0fd27a1 100644 --- a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs +++ b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs @@ -45,7 +45,7 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti // Get the QR code details to calculate module size QRCode qrCode = ZXing.QrCode.Internal.Encoder.encode(text, correctionLevel); int moduleCount = qrCode.Version.DimensionForVersion; - OverlayLogoOnQrCode(bitmap, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin, logoPaddingPixels); + OverlayLogoOnQrCode(bitmap, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin, logoPaddingPixels, background); } using MemoryStream ms = new(); @@ -57,7 +57,7 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti return bitmapImage; } - private static void OverlayLogoOnQrCode(Bitmap qrCodeBitmap, Bitmap logo, double sizePercentage, int moduleCount, int margin, double logoPaddingPixels) + private static void OverlayLogoOnQrCode(Bitmap qrCodeBitmap, Bitmap logo, double sizePercentage, int moduleCount, int margin, double logoPaddingPixels, System.Drawing.Color backgroundColor) { // Calculate the pixel size of each QR code module // The total size includes the margin on both sides @@ -124,11 +124,11 @@ private static void OverlayLogoOnQrCode(Bitmap qrCodeBitmap, Bitmap logo, double g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - // Always draw the white punchout background (covers QR code modules) -using SolidBrush whiteBrush = new(System.Drawing.Color.White); - g.FillRectangle(whiteBrush, punchoutX, punchoutY, punchoutSize, punchoutSize); + // Draw the punchout background with the same color as the QR code background + using SolidBrush backgroundBrush = new(backgroundColor); + g.FillRectangle(backgroundBrush, punchoutX, punchoutY, punchoutSize, punchoutSize); - // Draw the logo scaled to fit within or extend beyond the punchout + // Draw the logo scaled to fit within or extend beyond the punchout g.DrawImage(logo, logoX, logoY, logoWidth, logoHeight); } @@ -222,13 +222,13 @@ public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel cor // Get the QR code details to calculate module size QRCode qrCode = ZXing.QrCode.Internal.Encoder.encode(text, correctionLevel); int moduleCount = qrCode.Version.DimensionForVersion; - svg = EmbedLogoInSvg(svg, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin, logoPaddingPixels); + svg = EmbedLogoInSvg(svg, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin, logoPaddingPixels, background); } return svg; } - private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePercentage, int moduleCount, int margin, double logoPaddingPixels) + private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePercentage, int moduleCount, int margin, double logoPaddingPixels, System.Drawing.Color backgroundColor) { const int svgSize = 1024; // Should match the encoding options Width/Height @@ -309,13 +309,16 @@ private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePer resizedLogo.Dispose(); // Build the SVG logo element with punchout background and logo + // Convert the background color to RGB format for SVG + string backgroundColorHex = $"rgb({backgroundColor.R},{backgroundColor.G},{backgroundColor.B})"; + string logoSvgElement = $@" - + "; - // Find the closing tag and insert the logo before it + // Find the closing tag and insert the logo before it string modifiedContent = svg.Content.Replace("", logoSvgElement + "\n"); return new SvgImage(modifiedContent); From a26351ec397a65b839e7a5f6f499f48508abc19f Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Sat, 1 Nov 2025 19:27:11 -0500 Subject: [PATCH 12/39] Refactor QR code generation and UI enhancements - Adjusted QR code margin for tighter generation. - Enhanced logo overlay logic with improved scaling and alignment. - Refined padding and logo size calculations for better accuracy. - Updated default logo settings for size and padding. - Simplified error correction level handling with fixed values. - Improved stream handling with explicit type usage. - Enhanced UI with `SplitButton` for logo options and sliders. - Consolidated bindings and improved code readability in XAML. - Standardized formatting and improved code consistency. - Fixed logo resizing issues and minor binding inconsistencies. --- .../Helpers/BarcodeHelpers.cs | 156 +++++++------- .../ViewModels/MainViewModel.cs | 60 +++--- Simple QR Code Maker/Views/MainPage.xaml | 192 +++++++----------- 3 files changed, 176 insertions(+), 232 deletions(-) diff --git a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs index 0fd27a1..fb25e86 100644 --- a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs +++ b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs @@ -32,13 +32,13 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti { Width = 1024, Height = 1024, - Margin = 2, + Margin = 0, }; encodingOptions.Hints.Add(EncodeHintType.ERROR_CORRECTION, correctionLevel); barcodeWriter.Options = encodingOptions; using Bitmap bitmap = barcodeWriter.Write(text); - + // If a logo is provided, overlay it on the center of the QR code if (logoImage != null) { @@ -47,7 +47,7 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti int moduleCount = qrCode.Version.DimensionForVersion; OverlayLogoOnQrCode(bitmap, logoImage, logoSizePercentage, moduleCount, encodingOptions.Margin, logoPaddingPixels, background); } - + using MemoryStream ms = new(); bitmap.Save(ms, ImageFormat.Png); WriteableBitmap bitmapImage = new(encodingOptions.Width, encodingOptions.Height); @@ -63,72 +63,72 @@ private static void OverlayLogoOnQrCode(Bitmap qrCodeBitmap, Bitmap logo, double // The total size includes the margin on both sides int totalModules = moduleCount + (margin * 2); double modulePixelSize = (double)qrCodeBitmap.Width / totalModules; - + // Calculate the punchout space size based on the size percentage - // This is the area that will be covered (blocking QR code modules) + // This is the area that will be covered (blocking QR code modules) int punchoutSizePixels = (int)(Math.Min(qrCodeBitmap.Width, qrCodeBitmap.Height) * (sizePercentage / 100.0)); - - // Round the punchout size to the nearest module boundary + + // Round the punchout size to the nearest module boundary int punchoutSizeModules = (int)Math.Round(punchoutSizePixels / modulePixelSize); - // Ensure it's at least 1 module and odd number for better centering - if (punchoutSizeModules < 1) punchoutSizeModules = 1; + // Ensure it's at least 1 module and odd number for better centering + if (punchoutSizeModules < 1) punchoutSizeModules = 1; if (punchoutSizeModules % 2 == 0) punchoutSizeModules++; // Make it odd for symmetry - + // Convert back to pixels, aligned to module boundaries int punchoutSize = (int)(punchoutSizeModules * modulePixelSize); - - // Calculate the position to center the punchout area + + // Calculate the position to center the punchout area int punchoutX = (qrCodeBitmap.Width - punchoutSize) / 2; - int punchoutY = (qrCodeBitmap.Height - punchoutSize) / 2; - + int punchoutY = (qrCodeBitmap.Height - punchoutSize) / 2; + // Convert padding to actual pixels // Positive padding = logo smaller than punchout (adds white space) - // Negative padding = logo larger than punchout (logo extends beyond white background) + // Negative padding = logo larger than punchout (logo extends beyond white background) int paddingPixels = (int)Math.Round(logoPaddingPixels); - - // Calculate the actual logo display size + + // Calculate the actual logo display size // Logo fills the punchout area minus the padding on all sides int logoDisplayWidth = Math.Max(1, punchoutSize - (Math.Abs(paddingPixels) * 2)); int logoDisplayHeight = Math.Max(1, punchoutSize - (Math.Abs(paddingPixels) * 2)); - - // If padding is negative, logo is larger than punchout + + // If padding is negative, logo is larger than punchout if (paddingPixels < 0) { - logoDisplayWidth = punchoutSize + (Math.Abs(paddingPixels) * 2); + logoDisplayWidth = punchoutSize + (Math.Abs(paddingPixels) * 2); logoDisplayHeight = punchoutSize + (Math.Abs(paddingPixels) * 2); } - + // Calculate logo dimensions preserving aspect ratio - float aspectRatio = (float)logo.Width / logo.Height; + float aspectRatio = (float)logo.Width / logo.Height; int logoWidth, logoHeight; - + if (aspectRatio > 1) // Wider than tall - { + { logoWidth = logoDisplayWidth; - logoHeight = (int)(logoDisplayWidth / aspectRatio); + logoHeight = (int)(logoDisplayWidth / aspectRatio); } else // Taller than wide or square { logoHeight = logoDisplayHeight; - logoWidth = (int)(logoDisplayHeight * aspectRatio); - } + logoWidth = (int)(logoDisplayHeight * aspectRatio); + } // Center the logo within the punchout area (or offset if larger) int logoX = punchoutX + (punchoutSize - logoWidth) / 2; - int logoY = punchoutY + (punchoutSize - logoHeight) / 2; + int logoY = punchoutY + (punchoutSize - logoHeight) / 2; using Graphics g = Graphics.FromImage(qrCodeBitmap); // Set high quality rendering g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; // Draw the punchout background with the same color as the QR code background using SolidBrush backgroundBrush = new(backgroundColor); - g.FillRectangle(backgroundBrush, punchoutX, punchoutY, punchoutSize, punchoutSize); + g.FillRectangle(backgroundBrush, punchoutX, punchoutY, punchoutSize, punchoutSize); - // Draw the logo scaled to fit within or extend beyond the punchout + // Draw the logo scaled to fit within or extend beyond the punchout g.DrawImage(logo, logoX, logoY, logoWidth, logoHeight); } @@ -231,88 +231,88 @@ public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel cor private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePercentage, int moduleCount, int margin, double logoPaddingPixels, System.Drawing.Color backgroundColor) { const int svgSize = 1024; // Should match the encoding options Width/Height - + // Calculate the pixel size of each QR code module int totalModules = moduleCount + (margin * 2); double modulePixelSize = (double)svgSize / totalModules; - + // Calculate the punchout space size based on the size percentage // This is the area that will be covered (blocking QR code modules) -int punchoutSizePixels = (int)(svgSize * (sizePercentage / 100.0)); - - // Round the punchout size to the nearest module boundary - int punchoutSizeModules = (int)Math.Round(punchoutSizePixels / modulePixelSize); + int punchoutSizePixels = (int)(svgSize * (sizePercentage / 100.0)); + + // Round the punchout size to the nearest module boundary + int punchoutSizeModules = (int)Math.Round(punchoutSizePixels / modulePixelSize); if (punchoutSizeModules < 1) punchoutSizeModules = 1; - if (punchoutSizeModules % 2 == 0) punchoutSizeModules++; - - int punchoutSize = (int)(punchoutSizeModules * modulePixelSize); - + if (punchoutSizeModules % 2 == 0) punchoutSizeModules++; + + int punchoutSize = (int)(punchoutSizeModules * modulePixelSize); + // Calculate the position to center the punchout area int punchoutX = (svgSize - punchoutSize) / 2; int punchoutY = (svgSize - punchoutSize) / 2; - + // Convert padding to actual pixels // Positive padding = logo smaller than punchout (adds white space) // Negative padding = logo larger than punchout (logo extends beyond white background) - int paddingPixels = (int)Math.Round(logoPaddingPixels); - - // Calculate the actual logo display size - // Logo fills the punchout area minus the padding on all sides - int logoDisplayWidth = Math.Max(1, punchoutSize - (Math.Abs(paddingPixels) * 2)); + int paddingPixels = (int)Math.Round(logoPaddingPixels); + + // Calculate the actual logo display size + // Logo fills the punchout area minus the padding on all sides + int logoDisplayWidth = Math.Max(1, punchoutSize - (Math.Abs(paddingPixels) * 2)); int logoDisplayHeight = Math.Max(1, punchoutSize - (Math.Abs(paddingPixels) * 2)); - + // If padding is negative, logo is larger than punchout - if (paddingPixels < 0) - { - logoDisplayWidth = punchoutSize + (Math.Abs(paddingPixels) * 2); - logoDisplayHeight = punchoutSize + (Math.Abs(paddingPixels) * 2); + if (paddingPixels < 0) + { + logoDisplayWidth = punchoutSize + (Math.Abs(paddingPixels) * 2); + logoDisplayHeight = punchoutSize + (Math.Abs(paddingPixels) * 2); } - - // Calculate logo dimensions preserving aspect ratio + + // Calculate logo dimensions preserving aspect ratio float aspectRatio = (float)logo.Width / logo.Height; int logoWidth, logoHeight; - - if (aspectRatio > 1) // Wider than tall + + if (aspectRatio > 1) // Wider than tall { - logoWidth = logoDisplayWidth; + logoWidth = logoDisplayWidth; logoHeight = (int)(logoDisplayWidth / aspectRatio); - } + } else // Taller than wide or square - { - logoHeight = logoDisplayHeight; + { + logoHeight = logoDisplayHeight; logoWidth = (int)(logoDisplayHeight * aspectRatio); } // Center the logo within the punchout area (or offset if larger) int logoX = punchoutX + (punchoutSize - logoWidth) / 2; - int logoY = punchoutY + (punchoutSize - logoHeight) / 2; + int logoY = punchoutY + (punchoutSize - logoHeight) / 2; - // Resize the logo to the display size before encoding to reduce SVG file size + // Resize the logo to the display size before encoding to reduce SVG file size Bitmap resizedLogo = new(logoWidth, logoHeight); using (Graphics g = Graphics.FromImage(resizedLogo)) { - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - g.DrawImage(logo, 0, 0, logoWidth, logoHeight); + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + g.DrawImage(logo, 0, 0, logoWidth, logoHeight); } - + // Convert resized logo to base64 for embedding string base64Logo; using (MemoryStream ms = new()) - { - resizedLogo.Save(ms, ImageFormat.Png); - byte[] imageBytes = ms.ToArray(); + { + resizedLogo.Save(ms, ImageFormat.Png); + byte[] imageBytes = ms.ToArray(); base64Logo = Convert.ToBase64String(imageBytes); } resizedLogo.Dispose(); - // Build the SVG logo element with punchout background and logo + // Build the SVG logo element with punchout background and logo // Convert the background color to RGB format for SVG - string backgroundColorHex = $"rgb({backgroundColor.R},{backgroundColor.G},{backgroundColor.B})"; - - string logoSvgElement = $@" + string backgroundColorHex = $"rgb({backgroundColor.R},{backgroundColor.G},{backgroundColor.B})"; + + string logoSvgElement = $@" @@ -320,7 +320,7 @@ private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePer // Find the closing tag and insert the logo before it string modifiedContent = svg.Content.Replace("", logoSvgElement + "\n"); - + return new SvgImage(modifiedContent); } @@ -345,7 +345,7 @@ private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePer Result[] results = barcodeReader.DecodeMultiple(bitmap); - List<(string, Result)> strings = new(); + List<(string, Result)> strings = []; if (results == null || results.Length == 0) return strings; diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index 1d140af..812f4d8 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -13,6 +13,7 @@ using Windows.ApplicationModel.DataTransfer; using Windows.Storage; using Windows.Storage.Pickers; +using Windows.Storage.Streams; using WinRT.Interop; using ZXing.QrCode.Internal; @@ -93,10 +94,10 @@ public partial class MainViewModel : ObservableRecipient, INavigationAware private bool hasLogo = false; [ObservableProperty] - private double logoSizePercentage = 20.0; // Default 20% of QR code size + private double logoSizePercentage = 14.0; [ObservableProperty] - private double logoPaddingPixels = 8.0; // Default 8 pixels padding around logo + private double logoPaddingPixels = 4.0; public double MaxLogoSizePercentage => GetMaxLogoSizeForErrorCorrection(); @@ -134,7 +135,7 @@ partial void OnSelectedOptionChanged(ErrorCorrectionOptions value) LogoSizePercentage = MaxLogoSizePercentage; } OnPropertyChanged(nameof(MaxLogoSizePercentage)); - + debounceTimer.Stop(); debounceTimer.Start(); } @@ -166,7 +167,7 @@ partial void OnLogoSizePercentageChanged(double value) partial void OnLogoPaddingPixelsChanged(double value) { - debounceTimer.Stop(); + debounceTimer.Stop(); debounceTimer.Start(); } @@ -175,19 +176,21 @@ private double GetMaxLogoSizeForErrorCorrection() // Error correction allows us to obscure a percentage of the QR code // We use 95% of the theoretical maximum to allow larger logos // while still maintaining reliable scanning + return 50.0; + if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.L) - return 7.0 * 0.95; // ~6.65% max (increased from ~5.6%) + return 6.0; // ~6.65% max (increased from ~5.6%) else if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.M) - return 15.0 * 0.95; // ~14.25% max (increased from ~12%) + return 14.0; // ~14.25% max (increased from ~12%) else if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.Q) - return 25.0 * 0.95; // ~23.75% max (increased from ~20%) - else if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.H) - return 30.0 * 0.95; // ~28.5% max (increased from ~24%) + return 23.0; // ~23.75% max (increased from ~20%) + else if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.H) + return 27.0; // ~28.5% max (increased from ~24%) else - return 20.0; + return 20.0; } - public bool CanSaveImage { get => !string.IsNullOrWhiteSpace(UrlText); } + public bool CanSaveImage => !string.IsNullOrWhiteSpace(UrlText); partial void OnUrlTextChanged(string value) { @@ -264,10 +267,7 @@ private void OnRequestPaneChange(object recipient, RequestPaneChange message) } } - private void OnSaveHistoryMessage(object recipient, SaveHistoryMessage message) - { - SaveCurrentStateToHistory(); - } + private void OnSaveHistoryMessage(object recipient, SaveHistoryMessage message) => SaveCurrentStateToHistory(); private void OnRequestShowMessage(object recipient, RequestShowMessage rsm) { @@ -306,7 +306,7 @@ private void CheckCanPasteText() debounceTimer.Tick -= DebounceTimer_Tick; Clipboard.ContentChanged -= Clipboard_ContentChanged; - + // Dispose of the logo image LogoImage?.Dispose(); } @@ -558,36 +558,22 @@ private void CopyInfoBarTimer_Tick(object? sender, object e) } [RelayCommand] - private void ToggleFaqPaneOpen() - { - IsFaqPaneOpen = !IsFaqPaneOpen; - } + private void ToggleFaqPaneOpen() => IsFaqPaneOpen = !IsFaqPaneOpen; [RelayCommand] - private void ToggleHistoryPaneOpen() - { - IsHistoryPaneOpen = !IsHistoryPaneOpen; - } + private void ToggleHistoryPaneOpen() => IsHistoryPaneOpen = !IsHistoryPaneOpen; [RelayCommand] - private void ShareApp() - { - CopySharePopupOpen = !CopySharePopupOpen; - } + private void ShareApp() => CopySharePopupOpen = !CopySharePopupOpen; [RelayCommand] - private void OpenFile() - { - NavigationService.NavigateTo(typeof(DecodingViewModel).FullName!, UrlText); - } + private void OpenFile() => NavigationService.NavigateTo(typeof(DecodingViewModel).FullName!, UrlText); [RelayCommand] - private void GoToSettings() - { + private void GoToSettings() => // pass the contents of the UrlText to the settings page // so when coming back it can be rehydrated NavigationService.NavigateTo(typeof(SettingsViewModel).FullName!, UrlText); - } [RelayCommand] private async Task SavePng() @@ -666,9 +652,9 @@ private async Task SelectLogo() { // Dispose of the old logo if it exists LogoImage?.Dispose(); - + // Use stream to access file instead of direct path for better compatibility - using var stream = await file.OpenReadAsync(); + using IRandomAccessStreamWithContentType stream = await file.OpenReadAsync(); LogoImage = new System.Drawing.Bitmap(stream.AsStreamForRead()); } catch (Exception ex) diff --git a/Simple QR Code Maker/Views/MainPage.xaml b/Simple QR Code Maker/Views/MainPage.xaml index b8da2e9..f16b3ee 100644 --- a/Simple QR Code Maker/Views/MainPage.xaml +++ b/Simple QR Code Maker/Views/MainPage.xaml @@ -24,9 +24,7 @@ @@ -35,9 +33,7 @@ @@ -58,12 +54,9 @@ MinWidth="258" MaxWidth="800" AcceptsReturn="True" - PlaceholderText="{x:Bind ViewModel.PlaceholderText, - Mode=OneWay}" + PlaceholderText="{x:Bind ViewModel.PlaceholderText, Mode=OneWay}" ScrollViewer.HorizontalScrollBarVisibility="Auto" - Text="{x:Bind ViewModel.UrlText, - Mode=TwoWay, - UpdateSourceTrigger=PropertyChanged}" + Text="{x:Bind ViewModel.UrlText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextChanged="UrlTextBox_TextChanged" TextWrapping="NoWrap" /> @@ -75,9 +68,7 @@ VerticalAlignment="Bottom" Command="{x:Bind ViewModel.AddNewLineCommand}" ToolTipService.ToolTip="Add a return and start making another QR Code" - Visibility="{x:Bind ViewModel.CanSaveImage, - Converter={StaticResource BoolToVisibility}, - Mode=OneWay}"> + Visibility="{x:Bind ViewModel.CanSaveImage, Converter={StaticResource BoolToVisibility}, Mode=OneWay}"> @@ -89,13 +80,9 @@ Padding="0" VerticalAlignment="Bottom" Command="{x:Bind ViewModel.PasteTextIntoUrlTextCommand}" - IsEnabled="{x:Bind ViewModel.CanPasteText, - Mode=OneWay}" + IsEnabled="{x:Bind ViewModel.CanPasteText, Mode=OneWay}" ToolTipService.ToolTip="Paste text from clipboard" - Visibility="{x:Bind ViewModel.CanSaveImage, - Converter={StaticResource BoolToVisibility}, - ConverterParameter=True, - Mode=OneWay}"> + Visibility="{x:Bind ViewModel.CanSaveImage, Converter={StaticResource BoolToVisibility}, ConverterParameter=True, Mode=OneWay}"> @@ -108,9 +95,7 @@ HorizontalAlignment="Center" Orientation="Horizontal" Spacing="12" - Visibility="{x:Bind ViewModel.CanSaveImage, - Mode=OneWay, - Converter={StaticResource BoolToVisibility}}"> + Visibility="{x:Bind ViewModel.CanSaveImage, Mode=OneWay, Converter={StaticResource BoolToVisibility}}"> @@ -164,74 +143,63 @@ - - - + + + + + - - - - - - - - + + + + + + + + - - - - - - - + + + + + + + + + + + + @@ -265,8 +232,7 @@ x:Name="SavePngButton" Command="{x:Bind ViewModel.SavePngCommand}" Content="PNG" - IsEnabled="{x:Bind ViewModel.CanSaveImage, - Mode=OneWay}" + IsEnabled="{x:Bind ViewModel.CanSaveImage, Mode=OneWay}" ToolTipService.ToolTip="Save code(s) as PNG files"> @@ -285,8 +251,7 @@ x:Name="SaveSvgButton" Command="{x:Bind ViewModel.SaveSvgCommand}" Content="SVG" - IsEnabled="{x:Bind ViewModel.CanSaveImage, - Mode=OneWay}" + IsEnabled="{x:Bind ViewModel.CanSaveImage, Mode=OneWay}" ToolTipService.ToolTip="Save code(s) as SVG files"> @@ -369,8 +334,7 @@ HorizontalAlignment="Right" VerticalAlignment="Top" Background="{ThemeResource SolidBackgroundFillColorBaseBrush}" - IsOpen="{x:Bind ViewModel.CopySharePopupOpen, - Mode=TwoWay}"> + IsOpen="{x:Bind ViewModel.CopySharePopupOpen, Mode=TwoWay}"> + IsOpen="{x:Bind ViewModel.ShowCodeInfoBar, Mode=TwoWay}" + Message="{x:Bind ViewModel.CodeInfoBarMessage, Mode=OneWay}" + Severity="{x:Bind ViewModel.CodeInfoBarSeverity, Mode=OneWay}" /> @@ -422,9 +382,7 @@ + SelectedItem="{x:Bind ViewModel.SelectedHistoryItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> From 7648e5d36ee6113c9ef72b7fe0d7b0c56fd3c0a6 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Mon, 17 Nov 2025 13:08:21 -0800 Subject: [PATCH 13/39] Add new IconAndTextContentControl --- .../Controls/IconAndTextContent.xaml | 17 ++++++++++++ .../Controls/IconAndTextContent.xaml.cs | 26 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 Simple QR Code Maker/Controls/IconAndTextContent.xaml create mode 100644 Simple QR Code Maker/Controls/IconAndTextContent.xaml.cs diff --git a/Simple QR Code Maker/Controls/IconAndTextContent.xaml b/Simple QR Code Maker/Controls/IconAndTextContent.xaml new file mode 100644 index 0000000..4583fe8 --- /dev/null +++ b/Simple QR Code Maker/Controls/IconAndTextContent.xaml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/Simple QR Code Maker/Controls/IconAndTextContent.xaml.cs b/Simple QR Code Maker/Controls/IconAndTextContent.xaml.cs new file mode 100644 index 0000000..3f6f92e --- /dev/null +++ b/Simple QR Code Maker/Controls/IconAndTextContent.xaml.cs @@ -0,0 +1,26 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Simple_QR_Code_Maker.Controls; + +public sealed partial class IconAndTextContent : StackPanel +{ + public IconAndTextContent() + { + InitializeComponent(); + } + + public Symbol Icon + { get => (Symbol)GetValue(IconProperty); set => SetValue(IconProperty, value); + } + + public static readonly DependencyProperty IconProperty = + DependencyProperty.Register(nameof(Icon), typeof(Symbol), typeof(IconAndTextContent), new PropertyMetadata(Symbol.Placeholder)); + + public string ContentText + { get => (string)GetValue(ContentTextProperty); set => SetValue(ContentTextProperty, value); + } + + public static readonly DependencyProperty ContentTextProperty = + DependencyProperty.Register(nameof(ContentText), typeof(string), typeof(IconAndTextContent), new PropertyMetadata(string.Empty)); +} From 9be34ee94b295ec0211723525af806b70d005cfd Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Mon, 17 Nov 2025 13:10:04 -0800 Subject: [PATCH 14/39] Add new method for LogoImages on Barcodes GetMaxLogoSizePercentage --- .../Helpers/BarcodeHelpers.cs | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs index fb25e86..53f4bf6 100644 --- a/Simple QR Code Maker/Helpers/BarcodeHelpers.cs +++ b/Simple QR Code Maker/Helpers/BarcodeHelpers.cs @@ -14,6 +14,27 @@ namespace Simple_QR_Code_Maker.Helpers; public static class BarcodeHelpers { + /// + /// Calculate the maximum safe logo size percentage based on QR code error correction level and version + /// + /// The text to encode in the QR code + /// The error correction level + /// Maximum safe logo size as a percentage (0-100) + // public static int GetMaxLogoSizePercentage(string text, ErrorCorrectionLevel correctionLevel) + public static int GetMaxLogoSizePercentage(ErrorCorrectionLevel correctionLevel) + { + // Error correction capacity by level (percentage of code that can be damaged and still readable) + // These are the theoretical maximum recovery percentages + return correctionLevel.ToString() switch + { + "L" => 20, // Low: ~7% + "M" => 23, // Medium: ~15% + "Q" => 35, // Quartile: ~25% + "H" => 40, // High: ~30% + _ => 20 + }; + } + public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrectionLevel correctionLevel, System.Drawing.Color foreground, System.Drawing.Color background, Bitmap? logoImage = null, double logoSizePercentage = 20.0, double logoPaddingPixels = 8.0) { BitmapRenderer bitmapRenderer = new() @@ -32,7 +53,7 @@ public static WriteableBitmap GetQrCodeBitmapFromText(string text, ErrorCorrecti { Width = 1024, Height = 1024, - Margin = 0, + Margin = 2, }; encodingOptions.Hints.Add(EncodeHintType.ERROR_CORRECTION, correctionLevel); barcodeWriter.Options = encodingOptions; @@ -338,9 +359,9 @@ private static SvgImage EmbedLogoInSvg(SvgImage svg, Bitmap logo, double sizePer AutoRotate = true, Options = { - TryHarder = true, - TryInverted = true, - } + TryHarder = true, + TryInverted = true, +} }; Result[] results = barcodeReader.DecodeMultiple(bitmap); From 9d73eed13562f817091b72b71d4da8d60cd74e51 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Mon, 17 Nov 2025 13:10:37 -0800 Subject: [PATCH 15/39] Update packages --- .../Simple QR Code Maker.Core.csproj | 2 +- .../Simple QR Code Maker.csproj | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Simple QR Code Maker.Core/Simple QR Code Maker.Core.csproj b/Simple QR Code Maker.Core/Simple QR Code Maker.Core.csproj index d2f96c3..8c1cb13 100644 --- a/Simple QR Code Maker.Core/Simple QR Code Maker.Core.csproj +++ b/Simple QR Code Maker.Core/Simple QR Code Maker.Core.csproj @@ -11,6 +11,6 @@ - + diff --git a/Simple QR Code Maker/Simple QR Code Maker.csproj b/Simple QR Code Maker/Simple QR Code Maker.csproj index fa262d1..b4bec46 100644 --- a/Simple QR Code Maker/Simple QR Code Maker.csproj +++ b/Simple QR Code Maker/Simple QR Code Maker.csproj @@ -33,6 +33,7 @@ + @@ -47,15 +48,15 @@ - + - - + + - - - - + + + + @@ -65,6 +66,9 @@ Always + + MSBuild:Compile + MSBuild:Compile From f7c676d78014e76f0eb93efd0c64eca434a966d9 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Mon, 17 Nov 2025 13:11:09 -0800 Subject: [PATCH 16/39] Adjust min/max of the logo image size and use new IconAndTextContent control --- .../ViewModels/MainViewModel.cs | 70 ++++++++++++------- Simple QR Code Maker/Views/MainPage.xaml | 38 +++++----- 2 files changed, 65 insertions(+), 43 deletions(-) diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index 812f4d8..c66d93f 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -94,12 +94,13 @@ public partial class MainViewModel : ObservableRecipient, INavigationAware private bool hasLogo = false; [ObservableProperty] - private double logoSizePercentage = 14.0; + private double logoPaddingPixels = 4.0; [ObservableProperty] - private double logoPaddingPixels = 4.0; + private double logoSizePercentage = 15; - public double MaxLogoSizePercentage => GetMaxLogoSizeForErrorCorrection(); + [ObservableProperty] + private double logoSizeMaxPercentage = 20; private double MinSizeScanDistanceScaleFactor = 1; @@ -119,7 +120,9 @@ partial void OnSelectedHistoryItemChanged(HistoryItem? value) SelectedHistoryItem = null; } - public List ErrorCorrectionLevels { get; } = + public ObservableCollection ErrorCorrectionLevels { get; set; } = new(allCorrectionLevels); + + private static readonly List allCorrectionLevels = [ new("Low 7%", ErrorCorrectionLevel.L), new("Medium 15%", ErrorCorrectionLevel.M), @@ -130,11 +133,13 @@ partial void OnSelectedHistoryItemChanged(HistoryItem? value) partial void OnSelectedOptionChanged(ErrorCorrectionOptions value) { // Ensure logo size doesn't exceed the new error correction level's maximum - if (LogoSizePercentage > MaxLogoSizePercentage) + if (logoSizePercentage > logoSizeMaxPercentage) { - LogoSizePercentage = MaxLogoSizePercentage; + logoSizePercentage = logoSizeMaxPercentage; } - OnPropertyChanged(nameof(MaxLogoSizePercentage)); + OnPropertyChanged(nameof(logoSizeMaxPercentage)); + + LogoSizeMaxPercentage = BarcodeHelpers.GetMaxLogoSizePercentage(value.ErrorCorrectionLevel); debounceTimer.Stop(); debounceTimer.Start(); @@ -155,6 +160,26 @@ partial void OnForegroundColorChanged(Windows.UI.Color value) partial void OnLogoImageChanged(System.Drawing.Bitmap? value) { HasLogo = value != null; + + + if (HasLogo) + { + if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.L && ErrorCorrectionLevels.Count > 1) + { + SelectedOption = ErrorCorrectionLevels[1]; + } + + if (ErrorCorrectionLevels.Count > 0 + && ErrorCorrectionLevels[0].ErrorCorrectionLevel == ErrorCorrectionLevel.L) + ErrorCorrectionLevels.RemoveAt(0); + } + else + { + if (ErrorCorrectionLevels.Count == 3) + ErrorCorrectionLevels.Insert(0,new("Low 7%", ErrorCorrectionLevel.L)); + } + + debounceTimer.Stop(); debounceTimer.Start(); } @@ -171,29 +196,19 @@ partial void OnLogoPaddingPixelsChanged(double value) debounceTimer.Start(); } - private double GetMaxLogoSizeForErrorCorrection() - { - // Error correction allows us to obscure a percentage of the QR code - // We use 95% of the theoretical maximum to allow larger logos - // while still maintaining reliable scanning - return 50.0; - - if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.L) - return 6.0; // ~6.65% max (increased from ~5.6%) - else if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.M) - return 14.0; // ~14.25% max (increased from ~12%) - else if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.Q) - return 23.0; // ~23.75% max (increased from ~20%) - else if (SelectedOption.ErrorCorrectionLevel == ErrorCorrectionLevel.H) - return 27.0; // ~28.5% max (increased from ~24%) - else - return 20.0; - } - public bool CanSaveImage => !string.IsNullOrWhiteSpace(UrlText); partial void OnUrlTextChanged(string value) { + // Update max logo size when text changes (affects QR version/density) + OnPropertyChanged(nameof(logoSizeMaxPercentage)); + + // Ensure current logo size doesn't exceed the new maximum + if (logoSizePercentage > logoSizeMaxPercentage) + { + logoSizePercentage = logoSizeMaxPercentage; + } + debounceTimer.Stop(); debounceTimer.Start(); } @@ -645,7 +660,7 @@ private async Task SelectLogo() StorageFile file = await openPicker.PickSingleFileAsync(); - if (file == null) + if (file is null) return; try @@ -732,6 +747,7 @@ public async Task SaveAllFiles(FileKind kindOfFile) public async void OnNavigatedTo(object parameter) { await LoadHistory(); + CheckCanPasteText(); MultiLineCodeMode = await LocalSettingsService.ReadSettingAsync(nameof(MultiLineCodeMode)); BaseText = await LocalSettingsService.ReadSettingAsync(nameof(BaseText)) ?? string.Empty; UrlText = BaseText; diff --git a/Simple QR Code Maker/Views/MainPage.xaml b/Simple QR Code Maker/Views/MainPage.xaml index f16b3ee..036b9d4 100644 --- a/Simple QR Code Maker/Views/MainPage.xaml +++ b/Simple QR Code Maker/Views/MainPage.xaml @@ -134,7 +134,7 @@ @@ -145,7 +145,9 @@ - + + + @@ -161,19 +163,19 @@ + Text="Image Size (blocks)" /> - + @@ -181,15 +183,15 @@ + Text="Scale image" /> @@ -231,14 +233,16 @@ + + + + + + Date: Tue, 18 Nov 2025 19:58:18 +0000 Subject: [PATCH 17/39] Add history support for logo images with local storage persistence Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com> --- Simple QR Code Maker/Models/HistoryItem.cs | 4 + .../ViewModels/MainViewModel.cs | 95 +++++++++++++++++-- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/Simple QR Code Maker/Models/HistoryItem.cs b/Simple QR Code Maker/Models/HistoryItem.cs index f413320..79f2973 100644 --- a/Simple QR Code Maker/Models/HistoryItem.cs +++ b/Simple QR Code Maker/Models/HistoryItem.cs @@ -39,6 +39,10 @@ public ErrorCorrectionLevel ErrorCorrection [JsonConverter(typeof(JsonStringEnumConverter))] public BarcodeFormat Format { get; set; } = BarcodeFormat.QR_CODE; + public string? LogoImagePath { get; set; } + + public double LogoSizePercentage { get; set; } = 15; + public HistoryItem() { diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index c66d93f..018d1c1 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -10,6 +10,7 @@ using Simple_QR_Code_Maker.Helpers; using Simple_QR_Code_Maker.Models; using System.Collections.ObjectModel; +using System.IO; using Windows.ApplicationModel.DataTransfer; using Windows.Storage; using Windows.Storage.Pickers; @@ -116,9 +117,45 @@ partial void OnSelectedHistoryItemChanged(HistoryItem? value) ForegroundColor = value.Foreground; BackgroundColor = value.Background; SelectedOption = ErrorCorrectionLevels.First(x => x.ErrorCorrectionLevel == value.ErrorCorrection); + + // Restore logo image and size if available + if (!string.IsNullOrEmpty(value.LogoImagePath)) + { + _ = LoadLogoFromHistory(value.LogoImagePath); + } + else + { + // Clear logo if history item has no logo + LogoImage?.Dispose(); + LogoImage = null; + } + + LogoSizePercentage = value.LogoSizePercentage; SelectedHistoryItem = null; } + + private async Task LoadLogoFromHistory(string logoPath) + { + try + { + if (File.Exists(logoPath)) + { + // Dispose old logo first + LogoImage?.Dispose(); + + StorageFile file = await StorageFile.GetFileFromPathAsync(logoPath); + using var stream = await file.OpenReadAsync(); + LogoImage = new System.Drawing.Bitmap(stream.AsStreamForRead()); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to load logo from history: {ex.Message}"); + LogoImage?.Dispose(); + LogoImage = null; + } + } public ObservableCollection ErrorCorrectionLevels { get; set; } = new(allCorrectionLevels); @@ -282,7 +319,7 @@ private void OnRequestPaneChange(object recipient, RequestPaneChange message) } } - private void OnSaveHistoryMessage(object recipient, SaveHistoryMessage message) => SaveCurrentStateToHistory(); + private async void OnSaveHistoryMessage(object recipient, SaveHistoryMessage message) => await SaveCurrentStateToHistory(); private void OnRequestShowMessage(object recipient, RequestShowMessage rsm) { @@ -426,7 +463,7 @@ private async Task CopyPngToClipboard() if (QrCodeBitmaps.Count == 0) return; - SaveCurrentStateToHistory(); + await SaveCurrentStateToHistory(); StorageFolder folder = ApplicationData.Current.LocalCacheFolder; List files = []; @@ -475,7 +512,7 @@ private async Task CopySvgToClipboard() if (QrCodeBitmaps.Count == 0) return; - SaveCurrentStateToHistory(); + await SaveCurrentStateToHistory(); StorageFolder folder = ApplicationData.Current.LocalCacheFolder; List files = []; @@ -519,12 +556,12 @@ private async Task CopySvgToClipboard() } [RelayCommand] - private void CopySvgTextToClipboard() + private async Task CopySvgTextToClipboard() { if (QrCodeBitmaps.Count == 0) return; - SaveCurrentStateToHistory(); + await SaveCurrentStateToHistory(); List textStrings = []; foreach (BarcodeImageItem qrCodeItem in QrCodeBitmaps) @@ -596,7 +633,7 @@ private async Task SavePng() if (QrCodeBitmaps.Count == 0) return; - SaveCurrentStateToHistory(); + await SaveCurrentStateToHistory(); await SaveAllFiles(FileKind.PNG); @@ -615,7 +652,7 @@ private async Task SaveSvg() if (QrCodeBitmaps.Count == 0) return; - SaveCurrentStateToHistory(); + await SaveCurrentStateToHistory(); await SaveAllFiles(FileKind.SVG); @@ -769,23 +806,61 @@ public async void OnNavigatedTo(object parameter) public void OnNavigatedFrom() { if (!string.IsNullOrWhiteSpace(UrlText)) - SaveCurrentStateToHistory(); + _ = SaveCurrentStateToHistory(); } - public void SaveCurrentStateToHistory() + public async Task SaveCurrentStateToHistory() { + string? logoImagePath = null; + + // Save logo image to local app storage if present + if (LogoImage != null) + { + try + { + StorageFolder logoFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("LogoImages", CreationCollisionOption.OpenIfExists); + string fileName = $"logo_{DateTime.Now:yyyyMMddHHmmss}_{Guid.NewGuid():N}.png"; + StorageFile logoFile = await logoFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting); + + using (var stream = await logoFile.OpenAsync(FileAccessMode.ReadWrite)) + { + using (var outputStream = stream.GetOutputStreamAt(0)) + { + using (var dataWriter = new DataWriter(outputStream)) + { + using (MemoryStream ms = new()) + { + LogoImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + byte[] bytes = ms.ToArray(); + dataWriter.WriteBytes(bytes); + await dataWriter.StoreAsync(); + } + } + } + } + + logoImagePath = logoFile.Path; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to save logo image: {ex.Message}"); + } + } + HistoryItem historyItem = new() { CodesContent = UrlText, Foreground = ForegroundColor, Background = BackgroundColor, ErrorCorrection = SelectedOption.ErrorCorrectionLevel, + LogoImagePath = logoImagePath, + LogoSizePercentage = LogoSizePercentage, }; HistoryItems.Remove(historyItem); HistoryItems.Insert(0, historyItem); - LocalSettingsService.SaveSettingAsync(nameof(HistoryItems), HistoryItems); + await LocalSettingsService.SaveSettingAsync(nameof(HistoryItems), HistoryItems); } private async Task LoadHistory() From 9c23fe9a4e4caa337503df91945880002553d03c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:05:20 +0000 Subject: [PATCH 18/39] Add logo padding pixels to history persistence Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com> --- Simple QR Code Maker/Models/HistoryItem.cs | 2 ++ Simple QR Code Maker/ViewModels/MainViewModel.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Simple QR Code Maker/Models/HistoryItem.cs b/Simple QR Code Maker/Models/HistoryItem.cs index 79f2973..729c77f 100644 --- a/Simple QR Code Maker/Models/HistoryItem.cs +++ b/Simple QR Code Maker/Models/HistoryItem.cs @@ -42,6 +42,8 @@ public ErrorCorrectionLevel ErrorCorrection public string? LogoImagePath { get; set; } public double LogoSizePercentage { get; set; } = 15; + + public double LogoPaddingPixels { get; set; } = 4.0; public HistoryItem() { diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index 018d1c1..4ca1362 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -131,6 +131,7 @@ partial void OnSelectedHistoryItemChanged(HistoryItem? value) } LogoSizePercentage = value.LogoSizePercentage; + LogoPaddingPixels = value.LogoPaddingPixels; SelectedHistoryItem = null; } @@ -855,6 +856,7 @@ public async Task SaveCurrentStateToHistory() ErrorCorrection = SelectedOption.ErrorCorrectionLevel, LogoImagePath = logoImagePath, LogoSizePercentage = LogoSizePercentage, + LogoPaddingPixels = LogoPaddingPixels, }; HistoryItems.Remove(historyItem); From 93694fe58a79d77e6cfbde66ee23fc669240f2e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 00:57:49 +0000 Subject: [PATCH 19/39] Move history storage from settings to separate file with migration support Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com> --- .../ViewModels/MainViewModel.cs | 73 +++++++++++++++++-- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index 4ca1362..f88c46f 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -862,23 +862,80 @@ public async Task SaveCurrentStateToHistory() HistoryItems.Remove(historyItem); HistoryItems.Insert(0, historyItem); - await LocalSettingsService.SaveSettingAsync(nameof(HistoryItems), HistoryItems); + await SaveHistoryToFile(); } - private async Task LoadHistory() + private async Task SaveHistoryToFile() { - ObservableCollection? prevHistory = null; + try + { + StorageFolder localFolder = ApplicationData.Current.LocalFolder; + StorageFile historyFile = await localFolder.CreateFileAsync("History.json", CreationCollisionOption.ReplaceExisting); + + string json = System.Text.Json.JsonSerializer.Serialize(HistoryItems, new System.Text.Json.JsonSerializerOptions + { + WriteIndented = true + }); + + await FileIO.WriteTextAsync(historyFile, json); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to save history to file: {ex.Message}"); + } + } + private async Task LoadHistory() + { + ObservableCollection? historyFromFile = null; + + // First, try to load from file (new location) try { - prevHistory = await LocalSettingsService.ReadSettingAsync>(nameof(HistoryItems)); + StorageFolder localFolder = ApplicationData.Current.LocalFolder; + StorageFile historyFile = await localFolder.GetFileAsync("History.json"); + string json = await FileIO.ReadTextAsync(historyFile); + historyFromFile = System.Text.Json.JsonSerializer.Deserialize>(json); + } + catch + { + // File doesn't exist yet, that's okay } - catch { } - if (prevHistory is null || prevHistory.Count == 0) + // If we found history in the file, use it + if (historyFromFile != null && historyFromFile.Count > 0) + { + foreach (HistoryItem hisItem in historyFromFile) + HistoryItems.Add(hisItem); return; + } - foreach (HistoryItem hisItem in prevHistory) - HistoryItems.Add(hisItem); + // Otherwise, try to migrate from old settings location + ObservableCollection? historyFromSettings = null; + try + { + historyFromSettings = await LocalSettingsService.ReadSettingAsync>(nameof(HistoryItems)); + } + catch { } + + if (historyFromSettings != null && historyFromSettings.Count > 0) + { + // Migrate: add to collection and save to file + foreach (HistoryItem hisItem in historyFromSettings) + HistoryItems.Add(hisItem); + + // Save to new file location + await SaveHistoryToFile(); + + // Clear from old settings location to free up space + try + { + await LocalSettingsService.SaveSettingAsync(nameof(HistoryItems), new ObservableCollection()); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to clear old history from settings: {ex.Message}"); + } + } } } From 8d460d6220a60c1c87cbdcd1c75d8038cbb62df4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:16:16 +0000 Subject: [PATCH 20/39] Pass HistoryItem between pages for full state restoration including colors and logos Co-authored-by: TheJoeFin <7809853+TheJoeFin@users.noreply.github.com> --- .../ViewModels/DecodingViewModel.cs | 32 +++++++-- .../ViewModels/MainViewModel.cs | 70 +++++++++++++++++-- .../ViewModels/SettingsViewModel.cs | 15 ++-- 3 files changed, 101 insertions(+), 16 deletions(-) diff --git a/Simple QR Code Maker/ViewModels/DecodingViewModel.cs b/Simple QR Code Maker/ViewModels/DecodingViewModel.cs index 2058538..50ba22f 100644 --- a/Simple QR Code Maker/ViewModels/DecodingViewModel.cs +++ b/Simple QR Code Maker/ViewModels/DecodingViewModel.cs @@ -36,7 +36,7 @@ public partial class DecodingViewModel : ObservableRecipient, INavigationAware [ObservableProperty] private ObservableCollection decodingImageItems = []; - private string passedUrl = string.Empty; + private HistoryItem? navigationHistoryItem = null; private INavigationService NavigationService { get; } @@ -78,8 +78,16 @@ private void CheckIfCanPaste() public void OnNavigatedTo(object parameter) { - if (parameter is string url) - passedUrl = url; + // Store the HistoryItem to pass back when returning to main page + if (parameter is HistoryItem historyItem) + { + navigationHistoryItem = historyItem; + } + // For backward compatibility, also handle string parameter + else if (parameter is string url) + { + navigationHistoryItem = new HistoryItem { CodesContent = url }; + } } [RelayCommand] @@ -127,7 +135,7 @@ private async Task TryLaunchLink() [RelayCommand] private void GoBack() { - NavigationService.NavigateTo(typeof(MainViewModel).FullName!, passedUrl); + NavigationService.NavigateTo(typeof(MainViewModel).FullName!, navigationHistoryItem); } [RelayCommand] @@ -174,7 +182,21 @@ private void EditQrCode(object parameter) if (string.IsNullOrWhiteSpace(InfoBarMessage)) return; - NavigationService.NavigateTo(typeof(MainViewModel).FullName!, InfoBarMessage); + // Create a new HistoryItem with the decoded text, preserving other state if available + var editHistoryItem = navigationHistoryItem != null + ? new HistoryItem + { + CodesContent = InfoBarMessage, + Foreground = navigationHistoryItem.Foreground, + Background = navigationHistoryItem.Background, + ErrorCorrection = navigationHistoryItem.ErrorCorrection, + LogoImagePath = navigationHistoryItem.LogoImagePath, + LogoSizePercentage = navigationHistoryItem.LogoSizePercentage, + LogoPaddingPixels = navigationHistoryItem.LogoPaddingPixels, + } + : new HistoryItem { CodesContent = InfoBarMessage }; + + NavigationService.NavigateTo(typeof(MainViewModel).FullName!, editHistoryItem); } private void OpenAndDecodeBitmap(IRandomAccessStreamWithContentType streamWithContentType) diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index f88c46f..2677f83 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -102,6 +102,8 @@ public partial class MainViewModel : ObservableRecipient, INavigationAware [ObservableProperty] private double logoSizeMaxPercentage = 20; + + private string? currentLogoPath = null; private double MinSizeScanDistanceScaleFactor = 1; @@ -148,6 +150,7 @@ private async Task LoadLogoFromHistory(string logoPath) StorageFile file = await StorageFile.GetFileFromPathAsync(logoPath); using var stream = await file.OpenReadAsync(); LogoImage = new System.Drawing.Bitmap(stream.AsStreamForRead()); + currentLogoPath = logoPath; } } catch (Exception ex) @@ -155,6 +158,7 @@ private async Task LoadLogoFromHistory(string logoPath) System.Diagnostics.Debug.WriteLine($"Failed to load logo from history: {ex.Message}"); LogoImage?.Dispose(); LogoImage = null; + currentLogoPath = null; } } @@ -620,13 +624,32 @@ private void CopyInfoBarTimer_Tick(object? sender, object e) private void ShareApp() => CopySharePopupOpen = !CopySharePopupOpen; [RelayCommand] - private void OpenFile() => NavigationService.NavigateTo(typeof(DecodingViewModel).FullName!, UrlText); + private void OpenFile() => NavigationService.NavigateTo(typeof(DecodingViewModel).FullName!, CreateCurrentStateHistoryItem()); [RelayCommand] private void GoToSettings() => - // pass the contents of the UrlText to the settings page - // so when coming back it can be rehydrated - NavigationService.NavigateTo(typeof(SettingsViewModel).FullName!, UrlText); + // pass the current state as a HistoryItem to the settings page + // so when coming back it can be fully restored + NavigationService.NavigateTo(typeof(SettingsViewModel).FullName!, CreateCurrentStateHistoryItem()); + + private HistoryItem CreateCurrentStateHistoryItem() + { + return new HistoryItem + { + CodesContent = UrlText, + Foreground = ForegroundColor, + Background = BackgroundColor, + ErrorCorrection = SelectedOption.ErrorCorrectionLevel, + LogoImagePath = LogoImage != null ? GetCurrentLogoPath() : null, + LogoSizePercentage = LogoSizePercentage, + LogoPaddingPixels = LogoPaddingPixels, + }; + } + + private string? GetCurrentLogoPath() + { + return currentLogoPath; + } [RelayCommand] private async Task SavePng() @@ -725,6 +748,7 @@ private void RemoveLogo() { LogoImage?.Dispose(); LogoImage = null; + currentLogoPath = null; } private async Task WriteImageToFile(BarcodeImageItem imageItem, StorageFile file, FileKind kindOfFile) @@ -799,9 +823,40 @@ public async void OnNavigatedTo(object parameter) await LocalSettingsService.SaveSettingAsync(nameof(MinSizeScanDistanceScaleFactor), MinSizeScanDistanceScaleFactor); } - // check on text rehydration, could be coming from Reading or Settings - if (parameter is string textParam && !string.IsNullOrWhiteSpace(textParam)) + // Check if parameter is a HistoryItem with full state restoration + if (parameter is HistoryItem historyItem) + { + RestoreFromHistoryItem(historyItem); + } + // Otherwise check for text rehydration from other pages + else if (parameter is string textParam && !string.IsNullOrWhiteSpace(textParam)) + { UrlText = textParam; + } + } + + private void RestoreFromHistoryItem(HistoryItem historyItem) + { + UrlText = historyItem.CodesContent; + ForegroundColor = historyItem.Foreground; + BackgroundColor = historyItem.Background; + SelectedOption = ErrorCorrectionLevels.First(x => x.ErrorCorrectionLevel == historyItem.ErrorCorrection); + + // Restore logo image and settings if available + if (!string.IsNullOrEmpty(historyItem.LogoImagePath)) + { + _ = LoadLogoFromHistory(historyItem.LogoImagePath); + } + else + { + // Clear logo if history item has no logo + LogoImage?.Dispose(); + LogoImage = null; + currentLogoPath = null; + } + + LogoSizePercentage = historyItem.LogoSizePercentage; + LogoPaddingPixels = historyItem.LogoPaddingPixels; } public void OnNavigatedFrom() @@ -841,6 +896,7 @@ public async Task SaveCurrentStateToHistory() } logoImagePath = logoFile.Path; + currentLogoPath = logoImagePath; // Update current logo path } catch (Exception ex) { @@ -854,7 +910,7 @@ public async Task SaveCurrentStateToHistory() Foreground = ForegroundColor, Background = BackgroundColor, ErrorCorrection = SelectedOption.ErrorCorrectionLevel, - LogoImagePath = logoImagePath, + LogoImagePath = logoImagePath ?? currentLogoPath, // Use saved path or current path LogoSizePercentage = LogoSizePercentage, LogoPaddingPixels = LogoPaddingPixels, }; diff --git a/Simple QR Code Maker/ViewModels/SettingsViewModel.cs b/Simple QR Code Maker/ViewModels/SettingsViewModel.cs index 8f67bc4..1e6b8c3 100644 --- a/Simple QR Code Maker/ViewModels/SettingsViewModel.cs +++ b/Simple QR Code Maker/ViewModels/SettingsViewModel.cs @@ -4,6 +4,7 @@ using Simple_QR_Code_Maker.Contracts.Services; using Simple_QR_Code_Maker.Contracts.ViewModels; using Simple_QR_Code_Maker.Helpers; +using Simple_QR_Code_Maker.Models; using System.Globalization; using System.Reflection; using System.Windows.Input; @@ -41,7 +42,7 @@ public partial class SettingsViewModel : ObservableRecipient, INavigationAware private readonly DispatcherTimer settingChangedDebounceTimer = new(); - private string navigationText = string.Empty; + private HistoryItem? navigationHistoryItem = null; private INavigationService NavigationService { get; } public ILocalSettingsService LocalSettingsService { get; } @@ -142,7 +143,7 @@ partial void OnMinSizeScanDistanceScaleFactorChanged(double value) [RelayCommand] private void GoHome() { - NavigationService.NavigateTo(typeof(MainViewModel).FullName!, navigationText); + NavigationService.NavigateTo(typeof(MainViewModel).FullName!, navigationHistoryItem); } [RelayCommand] @@ -183,9 +184,15 @@ public async void OnNavigatedTo(object parameter) HideMinimumSizeText = await LocalSettingsService.ReadSettingAsync(nameof(HideMinimumSizeText)); MinSizeScanDistanceScaleFactor = await LocalSettingsService.ReadSettingAsync(nameof(MinSizeScanDistanceScaleFactor)); - if (parameter is string urlText && !string.IsNullOrWhiteSpace(urlText)) + // Store the HistoryItem to pass back when returning to main page + if (parameter is HistoryItem historyItem) { - navigationText = urlText; + navigationHistoryItem = historyItem; + } + // For backward compatibility, also handle string parameter + else if (parameter is string urlText && !string.IsNullOrWhiteSpace(urlText)) + { + navigationHistoryItem = new HistoryItem { CodesContent = urlText }; } } From 857ebe785a0427af635365f53602ca94737a05d7 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 19 Nov 2025 16:29:21 -0600 Subject: [PATCH 21/39] Update SizeTextVisible logic in BarcodeImageItem Refine the logic for setting the SizeTextVisible property to also hide the size text when a logo image is present, in addition to the existing HideMinimumSizeText condition. --- Simple QR Code Maker/ViewModels/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simple QR Code Maker/ViewModels/MainViewModel.cs b/Simple QR Code Maker/ViewModels/MainViewModel.cs index 2677f83..8f1c6bf 100644 --- a/Simple QR Code Maker/ViewModels/MainViewModel.cs +++ b/Simple QR Code Maker/ViewModels/MainViewModel.cs @@ -415,7 +415,7 @@ private void GenerateQrCodeFromOneLine(string text) CodeAsBitmap = bitmap, CodeAsText = textToUse, IsAppShowingUrlWarnings = WarnWhenNotUrl, - SizeTextVisible = HideMinimumSizeText ? Visibility.Collapsed : Visibility.Visible, + SizeTextVisible = (HideMinimumSizeText || LogoImage != null) ? Visibility.Collapsed : Visibility.Visible, ErrorCorrection = SelectedOption.ErrorCorrectionLevel, ForegroundColor = ForegroundColor, BackgroundColor = BackgroundColor, From 01fd6ef5b84204c1f243d5313fc04f92e90b0e6b Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 19 Nov 2025 21:11:18 -0600 Subject: [PATCH 22/39] Adjust layout and button sizes in QrCodeOptions UI Reduced the `Spacing` property of the `StackPanel` named `QrCodeOptions` from `12` to `6` to optimize spacing. Decreased the `Width` of the "Code Color" and "Background Color" buttons from `68` to `48` to improve the visual design and save space in the user interface. --- Simple QR Code Maker/Views/MainPage.xaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Simple QR Code Maker/Views/MainPage.xaml b/Simple QR Code Maker/Views/MainPage.xaml index 036b9d4..2c239db 100644 --- a/Simple QR Code Maker/Views/MainPage.xaml +++ b/Simple QR Code Maker/Views/MainPage.xaml @@ -94,9 +94,9 @@ x:Name="QrCodeOptions" HorizontalAlignment="Center" Orientation="Horizontal" - Spacing="12" + Spacing="6" Visibility="{x:Bind ViewModel.CanSaveImage, Mode=OneWay, Converter={StaticResource BoolToVisibility}}"> - -