Skip to content

Commit

Permalink
feat: Added QrCodes.System.Drawing library.
Browse files Browse the repository at this point in the history
  • Loading branch information
HavenDV committed Jan 25, 2024
1 parent 3548c8a commit 76ad789
Show file tree
Hide file tree
Showing 12 changed files with 463 additions and 41 deletions.
7 changes: 7 additions & 0 deletions QrCodes.sln
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QrCodes.Benchmarks", "src\b
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QrCodes.SkiaSharp", "src\libs\QrCodes.SkiaSharp\QrCodes.SkiaSharp.csproj", "{DCBD2245-73F4-4644-AD0C-38D8197BABB4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QrCodes.System.Drawing", "src\libs\QrCodes.System.Drawing\QrCodes.System.Drawing.csproj", "{F58F57D4-B95E-42BF-9BA3-16A31C9E9831}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -76,6 +78,10 @@ Global
{DCBD2245-73F4-4644-AD0C-38D8197BABB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCBD2245-73F4-4644-AD0C-38D8197BABB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCBD2245-73F4-4644-AD0C-38D8197BABB4}.Release|Any CPU.Build.0 = Release|Any CPU
{F58F57D4-B95E-42BF-9BA3-16A31C9E9831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F58F57D4-B95E-42BF-9BA3-16A31C9E9831}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F58F57D4-B95E-42BF-9BA3-16A31C9E9831}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F58F57D4-B95E-42BF-9BA3-16A31C9E9831}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -89,6 +95,7 @@ Global
{594FD49C-F7AC-44EC-98D7-788E83D6260D} = {928D4422-05CE-4AC2-89B0-31060889AE6A}
{507C686B-CBB2-4559-8FCC-DBFA90B3E987} = {732F539D-3402-4E31-AD43-F3653C9F2F0C}
{DCBD2245-73F4-4644-AD0C-38D8197BABB4} = {928D4422-05CE-4AC2-89B0-31060889AE6A}
{F58F57D4-B95E-42BF-9BA3-16A31C9E9831} = {928D4422-05CE-4AC2-89B0-31060889AE6A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1493AEE4-9211-46E9-BFE6-8F629EAC5693}
Expand Down
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,31 @@ It contains various payloads and framework helpers for MAUI.

### Usage
```
// Base library with all payloads and some renderers(Ascii, Base64, Bitmap, PNG, SVG, PostScript)
// Base library with all payloads and some renderers(Ascii, Base64, Pdf, FastBitmap, FastPng, SVG, PostScript)
dotnet add package QrCodes
// ImageSharpRenderer(Gif, Jpeg, Png, WebP, Bmp, Pbm, Tga, Tiff), Export to PDF
dotnet add package QrCodes.ImageSharp
// SkiaSharpRenderer(Gif, Jpeg, Png, WebP, Bmp, Ico, Wbmp, Pkm, Ktx, Astc, Dng, Heif, Avif), Export to PDF
// SkiaSharpRenderer(Gif, Jpeg, Png, WebP, Bmp, Ico, Wbmp, Pkm, Ktx, Astc, Dng, Heif, Avif)
dotnet add package QrCodes.SkiaSharp
// ImageSharpRenderer(Gif, Jpeg, Png, WebP, Bmp, Pbm, Tga, Tiff)
dotnet add package QrCodes.ImageSharp
// SystemDrawingRenderer(Gif, Jpeg, Png, Bmp, Tiff)
dotnet add package QrCodes.System.Drawing
// MAUI helpers(QrCodeSource and QrCodeExtension markup extension). Uses SkiaSharpRenderer.
dotnet add package QrCodes.Maui
```

#### Generate QR code with logo image
#### Generate QR code
```csharp
var qrCode = QrCodeGenerator.Generate(
plainText: new Telegram(user: "havendv").ToString(),
eccLevel: ErrorCorrectionLevel.High);
using var image = SkiaSharpRenderer.Render(
data: qrCode,
pixelsPerModule: 5,
darkColor: Color.Black,
lightColor: Color.White,
drawQuietZones: false);
var bytes = image.ToBytes(FileFormat.Png);
using var pngBytes = SkiaSharpRenderer.RenderToBytes(
data,
settings: new RendererSettings
{
DarkColor = Color.Red,
});
```

#### Generate ImageSource for MAUI
Expand Down
9 changes: 9 additions & 0 deletions sample/DynamicExamplePage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@
x:Name="BackgroundTypePicker"
ItemsSource="{qr:Enum Type={x:Type renderers:BackgroundType}}"
SelectedItem="{x:Static renderers:BackgroundType.Circle}" />
<Label
HorizontalTextAlignment="Center"
Text="DotStyle:" />
<Picker
x:Name="DotStylePicker"
ItemsSource="{qr:Enum Type={x:Type renderers:BackgroundType}}"
SelectedItem="{x:Static renderers:BackgroundType.Rectangle}" />
<Label
HorizontalTextAlignment="Center"
Text="{Binding Value, Source={x:Reference IconSizePercentSlider}, StringFormat='IconSizePercent: {0}'}" />
Expand Down Expand Up @@ -182,6 +189,8 @@
Source={x:Reference IconBorderWidthSlider}},
BackgroundType={Binding SelectedItem,
Source={x:Reference BackgroundTypePicker}},
DotStyle={Binding SelectedItem,
Source={x:Reference DotStylePicker}},
IconBackgroundColor={Binding SelectedItem,
Source={x:Reference IconBackgroundColorPicker}}}"
WidthRequest="400" />
Expand Down
2 changes: 2 additions & 0 deletions src/libs/QrCodes.Maui/QrCodeSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace QrCodes.Maui;
[DependencyProperty<int>("IconBorderWidth", DefaultValue = 0, OnChanged = nameof(OnChanged))]
[DependencyProperty<Color>("IconBackgroundColor", DefaultValueExpression = "Colors.Transparent", OnChanged = nameof(OnChanged))]
[DependencyProperty<BackgroundType>("BackgroundType", DefaultValue = BackgroundType.Circle, OnChanged = nameof(OnChanged))]
[DependencyProperty<BackgroundType>("DotStyle", DefaultValue = BackgroundType.Rectangle, OnChanged = nameof(OnChanged))]
[DependencyProperty<ImageSource>("LogoSource")]
public partial class QrCodeSource : StreamImageSource
{
Expand Down Expand Up @@ -73,6 +74,7 @@ private Task<Stream> RenderAsync(CancellationToken cancellationToken = default)
IconBackgroundColor = (IconBackgroundColor ?? Colors.Transparent).ToSystemDrawingColor(),
FileFormat = FileFormat,
Quality = Quality,
DotStyle = DotStyle,
});

return Task.FromResult(stream);
Expand Down
3 changes: 0 additions & 3 deletions src/libs/QrCodes.Maui/QrCodes.Maui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<UseMaui>true</UseMaui>
<NoWarn>$(NoWarn);CA1031;CA2000</NoWarn>
</PropertyGroup>
Expand Down
81 changes: 60 additions & 21 deletions src/libs/QrCodes.SkiaSharp/Renderers/SkiaSharpRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ public static SKImage Render(

using var bitmap = new SKBitmap(new SKImageInfo(width: size, height: size));
using var canvas = new SKCanvas(bitmap);

canvas.Clear(settings.LightColor.ToSkiaSharpColor());

DrawQrCode(
data: data,
canvas: canvas,
pixelsPerModule: settings.PixelsPerModule,
moduleOffset: moduleOffset,
darkColor: settings.DarkColor.ToSkiaSharpColor(),
lightColor: settings.LightColor.ToSkiaSharpColor());
settings: settings,
moduleOffset: moduleOffset);

if (settings is { IconBytes: not null, IconSizePercent: > 0 and <= 100 })
{
Expand Down Expand Up @@ -67,9 +68,22 @@ public static SKImage Render(
radius: iconDestWidth / 2.0f + settings.IconBorderWidth,
paint: paint);
break;

case BackgroundType.Rectangle:
canvas.DrawRect(centerDest, paint);
canvas.DrawRect(
rect: centerDest,
paint: paint);
break;


case BackgroundType.RoundRectangle:
canvas.DrawRoundRect(
rect: centerDest,
rx: iconDestWidth * 0.25F,
ry: iconDestHeight * 0.25F,
paint: paint);
break;

default:
throw new ArgumentOutOfRangeException(nameof(settings), settings.BackgroundType, null);
}
Expand All @@ -92,30 +106,55 @@ public static SKImage Render(
private static void DrawQrCode(
QrCode data,
SKCanvas canvas,
int pixelsPerModule,
int moduleOffset,
SKColor darkColor,
SKColor lightColor)
RendererSettings settings,
int moduleOffset)
{
using var lightPaint = new SKPaint();
lightPaint.Color = lightColor;
lightPaint.Style = SKPaintStyle.Fill;
using var darkPaint = new SKPaint();
darkPaint.Color = darkColor;
darkPaint.Color = settings.DarkColor.ToSkiaSharpColor();
darkPaint.Style = SKPaintStyle.Fill;

for (var modY = moduleOffset; modY < data.ModuleMatrix.Count - moduleOffset; modY++)
{
for (var modX = moduleOffset; modX < data.ModuleMatrix.Count - moduleOffset; modX++)
{
canvas.DrawRect(
x: (modX - moduleOffset) * pixelsPerModule,
y: (modY - moduleOffset) * pixelsPerModule,
w: pixelsPerModule,
h: pixelsPerModule,
paint: data.ModuleMatrix[modY][modX]
? darkPaint
: lightPaint);
if (!data.ModuleMatrix[modY][modX])
{
continue;
}

switch (settings.DotStyle)
{
case BackgroundType.Circle:
canvas.DrawCircle(
cx: (modX - moduleOffset + 0.5F) * settings.PixelsPerModule,
cy: (modY - moduleOffset + 0.5F) * settings.PixelsPerModule,
radius: settings.PixelsPerModule * 0.5F,
paint: darkPaint);
break;

case BackgroundType.Rectangle:
canvas.DrawRect(
x: (modX - moduleOffset) * settings.PixelsPerModule,
y: (modY - moduleOffset) * settings.PixelsPerModule,
w: settings.PixelsPerModule,
h: settings.PixelsPerModule,
paint: darkPaint);
break;

case BackgroundType.RoundRectangle:
canvas.DrawRoundRect(
x: (modX - moduleOffset) * settings.PixelsPerModule,
y: (modY - moduleOffset) * settings.PixelsPerModule,
w: settings.PixelsPerModule,
h: settings.PixelsPerModule,
paint: darkPaint,
rx: settings.PixelsPerModule * 0.25F,
ry: settings.PixelsPerModule * 0.25F);
break;

default:
throw new ArgumentOutOfRangeException(nameof(settings), settings.BackgroundType, null);
}
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/libs/QrCodes.System.Drawing/QrCodes.System.Drawing.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net4.6.2;netstandard2.0;net6.0-windows;net7.0-windows;net8.0-windows</TargetFrameworks>
<RootNamespace>QrCodes</RootNamespace>
</PropertyGroup>

<PropertyGroup Label="NuGet">
<PackageId>Oscore.$(AssemblyName)</PackageId>
<PackageTags>$(PackageTags);system;drawing</PackageTags>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\QrCodes\QrCodes.csproj" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' != 'net4.6.2' ">
<PackageReference Include="System.Drawing.Common" Version="8.0.1" />
</ItemGroup>

</Project>
80 changes: 80 additions & 0 deletions src/libs/QrCodes.System.Drawing/Renderers/ImageExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System.Drawing;
using System.Drawing.Imaging;

namespace QrCodes.Renderers;

/// <summary>
///
/// </summary>
public static class ImageExtensions
{
private static void Save(
this Image image,
Stream stream,
FileFormat fileFormat,
int quality = 100)
{
image.Save(
stream: stream,
format: fileFormat switch
{
FileFormat.Bmp => ImageFormat.Bmp,
FileFormat.Gif => ImageFormat.Gif,
FileFormat.Ico => throw new NotSupportedException("ICO is not supported by System.Drawing."),
FileFormat.Jpeg => ImageFormat.Jpeg,
FileFormat.Png => ImageFormat.Png,
FileFormat.Wbmp => throw new NotSupportedException("Wbmp is not supported by System.Drawing."),
FileFormat.Webp => throw new NotSupportedException("Webp is not supported by System.Drawing."),
FileFormat.Pkm => throw new NotSupportedException("Pkm is not supported by System.Drawing."),
FileFormat.Ktx => throw new NotSupportedException("Ktx is not supported by System.Drawing."),
FileFormat.Astc => throw new NotSupportedException("Astc is not supported by System.Drawing."),
FileFormat.Dng => throw new NotSupportedException("Dng is not supported by System.Drawing."),
FileFormat.Heif => throw new NotSupportedException("Heif is not supported by System.Drawing."),
FileFormat.Avif => throw new NotSupportedException("Avif is not supported by System.Drawing."),
FileFormat.Pbm => throw new NotSupportedException("PBM is not supported by System.Drawing."),
FileFormat.Tga => throw new NotSupportedException("TGA is not supported by System.Drawing."),
FileFormat.Tiff => ImageFormat.Tiff,
_ => throw new ArgumentOutOfRangeException(nameof(fileFormat), fileFormat, null)
});
}

/// <summary>
///
/// </summary>
/// <param name="image"></param>
/// <param name="fileFormat"></param>
/// <param name="quality"></param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static Stream ToStream(
this Image image,
FileFormat fileFormat,
int quality = 100)
{
image = image ?? throw new ArgumentNullException(nameof(image));

var stream = new MemoryStream();
image.Save(stream, fileFormat, quality);

return stream;
}

/// <summary>
///
/// </summary>
/// <param name="image"></param>
/// <param name="fileFormat"></param>
/// <param name="quality"></param>
/// <returns></returns>
public static byte[] ToBytes(
this Image image,
FileFormat fileFormat,
int quality = 100)
{
image = image ?? throw new ArgumentNullException(nameof(image));

using var stream = new MemoryStream();
image.Save(stream, fileFormat, quality);

return stream.ToArray();
}
}
Loading

0 comments on commit 76ad789

Please sign in to comment.