Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1f22381
update packages and .net version
TheJoeFin Oct 6, 2025
d942f21
Initial plan
Copilot Nov 1, 2025
a0e4c96
Add logo overlay support for QR codes
Copilot Nov 1, 2025
c5a892d
Add proper disposal of logo images to prevent memory leaks
Copilot Nov 1, 2025
6197c21
Address code review feedback: extract magic numbers and use stream fo…
Copilot Nov 1, 2025
305bd87
Preserve logo aspect ratio to prevent distortion
Copilot Nov 1, 2025
763412d
Add logo size slider with error correction-based limits and optimize …
Copilot Nov 1, 2025
51db551
Improve QR code logo handling and error correction
TheJoeFin Nov 1, 2025
71cea3e
Add logo padding support for QR code generation
TheJoeFin Nov 1, 2025
113c3d2
Refactor QR code logo embedding and UI updates
TheJoeFin Nov 1, 2025
a390cd9
Add support for custom QR code background color
TheJoeFin Nov 1, 2025
a26351e
Refactor QR code generation and UI enhancements
TheJoeFin Nov 2, 2025
7648e5d
Add new IconAndTextContentControl
TheJoeFin Nov 17, 2025
9be34ee
Add new method for LogoImages on Barcodes GetMaxLogoSizePercentage
TheJoeFin Nov 17, 2025
9d73eed
Update packages
TheJoeFin Nov 17, 2025
f7c676d
Adjust min/max of the logo image size and use new IconAndTextContent …
TheJoeFin Nov 17, 2025
9a2e144
Add history support for logo images with local storage persistence
Copilot Nov 18, 2025
9c23fe9
Add logo padding pixels to history persistence
Copilot Nov 18, 2025
93694fe
Move history storage from settings to separate file with migration su…
Copilot Nov 19, 2025
8d460d6
Pass HistoryItem between pages for full state restoration including c…
Copilot Nov 19, 2025
857ebe7
Update SizeTextVisible logic in BarcodeImageItem
TheJoeFin Nov 19, 2025
01fd6ef
Adjust layout and button sizes in QrCodeOptions UI
TheJoeFin Nov 20, 2025
1371b2f
Merge pull request #38 from TheJoeFin/copilot/add-logo-to-qr-code-again
TheJoeFin Nov 20, 2025
fe153ec
Update namespace and add new FAQ items to FaqItem.cs
TheJoeFin Nov 20, 2025
9802a37
update packages
TheJoeFin Dec 1, 2025
0365c13
Change names
TheJoeFin Dec 1, 2025
ccc4e8a
Add a CI/CD build pipeline
TheJoeFin Dec 1, 2025
b4b9e3f
Update version number
TheJoeFin Dec 1, 2025
19507b6
update Actions script
TheJoeFin Dec 1, 2025
d5d2acf
update build yml
TheJoeFin Dec 1, 2025
b7f8dfb
update build yml
TheJoeFin Dec 1, 2025
9de5e06
use build pipeline from Trdo
TheJoeFin Dec 1, 2025
ac4ac3f
Remove Generate Appx Pkg on build to resolve CI issues
TheJoeFin Dec 1, 2025
6bf7013
Try lowering targer framework version for CI
TheJoeFin Dec 1, 2025
d96868d
use Trdo csproj for ci
TheJoeFin Dec 1, 2025
dd2c44e
use msbuild not dotnet build
TheJoeFin Dec 2, 2025
0cc2caa
tweak MSBuild args
TheJoeFin Dec 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Build

on:
push:
branches:
- main
- dev
pull_request:
branches:
- main
- dev
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true

jobs:
build:
runs-on: windows-latest

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: '9.0.x'

- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2

- name: Restore
run: msbuild "Simple QR Code Maker.sln" /t:Restore /p:Configuration=Release /p:Platform=x64

- name: Build
run: msbuild "Simple QR Code Maker.sln" /p:Configuration=Release /p:Platform=x64
4 changes: 2 additions & 2 deletions Simple QR Code Maker.Core/Simple QR Code Maker.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>Simple_QR_Code_Maker.Core</RootNamespace>
<Platforms>x86;x64;arm64;AnyCPU</Platforms>
<ImplicitUsings>enable</ImplicitUsings>
Expand All @@ -11,6 +11,6 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="9.0.4" />
<PackageReference Include="System.Text.Json" Version="10.0.0" />
</ItemGroup>
</Project>
17 changes: 17 additions & 0 deletions Simple QR Code Maker/Controls/IconAndTextContent.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<StackPanel
x:Class="Simple_QR_Code_Maker.Controls.IconAndTextContent"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Simple_QR_Code_Maker.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding}"
Orientation="Horizontal"
Spacing="6"
mc:Ignorable="d">
<Viewbox Height="14">
<SymbolIcon Symbol="{x:Bind Icon}" />
</Viewbox>
<TextBlock Text="{x:Bind ContentText}" />
</StackPanel>
26 changes: 26 additions & 0 deletions Simple QR Code Maker/Controls/IconAndTextContent.xaml.cs
Original file line number Diff line number Diff line change
@@ -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));
}
223 changes: 217 additions & 6 deletions Simple QR Code Maker/Helpers/BarcodeHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,28 @@ 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)
/// <summary>
/// Calculate the maximum safe logo size percentage based on QR code error correction level and version
/// </summary>
/// <param name="text">The text to encode in the QR code</param>
/// <param name="correctionLevel">The error correction level</param>
/// <returns>Maximum safe logo size as a percentage (0-100)</returns>
// 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()
{
Expand All @@ -38,6 +59,16 @@ 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)
{
// 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, background);
}

using MemoryStream ms = new();
bitmap.Save(ms, ImageFormat.Png);
WriteableBitmap bitmapImage = new(encodingOptions.Width, encodingOptions.Height);
Expand All @@ -47,6 +78,81 @@ 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, System.Drawing.Color backgroundColor)
{
// 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 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 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 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;
int logoWidth, logoHeight;

if (aspectRatio > 1) // Wider than tall
{
logoWidth = logoDisplayWidth;
logoHeight = (int)(logoDisplayWidth / aspectRatio);
}
else // Taller than wide or square
{
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);
// 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 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
g.DrawImage(logo, logoX, logoY, logoWidth, logoHeight);
}

/// <summary>
/// Calculate the smallest side of a QR code based on the distance between the camera and the QR code
/// </summary>
Expand Down Expand Up @@ -106,7 +212,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, double logoSizePercentage = 20.0, double logoPaddingPixels = 8.0)
{
SvgRenderer svgRenderer = new()
{
Expand All @@ -131,9 +237,114 @@ 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)
{
// 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, background);
}

return svg;
}

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);
if (punchoutSizeModules < 1) punchoutSizeModules = 1;
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 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;

if (aspectRatio > 1) // Wider than tall
{
logoWidth = logoDisplayWidth;
logoHeight = (int)(logoDisplayWidth / aspectRatio);
}
else // Taller than wide or square
{
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;

// 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);
}

// 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 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 = $@"
<!-- Logo punchout background -->
<rect x=""{punchoutX}"" y=""{punchoutY}"" width=""{punchoutSize}"" height=""{punchoutSize}"" fill=""{backgroundColorHex}""/>
<!-- Logo image -->
<image x=""{logoX}"" y=""{logoY}"" width=""{logoWidth}"" height=""{logoHeight}"" href=""data:image/png;base64,{base64Logo}""/>";

// Find the closing </svg> tag and insert the logo before it
string modifiedContent = svg.Content.Replace("</svg>", logoSvgElement + "\n</svg>");

return new SvgImage(modifiedContent);
}

public static IEnumerable<(string, Result)> GetStringsFromImageFile(StorageFile storageFile)
{
Bitmap bitmap = new(storageFile.Path);
Expand All @@ -148,14 +359,14 @@ public static SvgImage GetSvgQrCodeForText(string text, ErrorCorrectionLevel cor
AutoRotate = true,
Options =
{
TryHarder = true,
TryInverted = true,
}
TryHarder = true,
TryInverted = true,
}
};

Result[] results = barcodeReader.DecodeMultiple(bitmap);

List<(string, Result)> strings = new();
List<(string, Result)> strings = [];

if (results == null || results.Length == 0)
return strings;
Expand Down
Loading
Loading