mirror of
https://github.com/abdelkader/vCardEditor
synced 2025-12-12 08:27:19 +07:00
298 lines
16 KiB
C#
298 lines
16 KiB
C#
#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 || NET6_0_WINDOWS
|
|
|
|
using System;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using static QRCoder.ArtQRCode;
|
|
using static QRCoder.QRCodeGenerator;
|
|
|
|
// pull request raised to extend library used.
|
|
namespace QRCoder
|
|
{
|
|
#if NET6_0_WINDOWS
|
|
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
|
#endif
|
|
public class ArtQRCode : AbstractQRCode, IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Constructor without params to be used in COM Objects connections
|
|
/// </summary>
|
|
public ArtQRCode() { }
|
|
|
|
/// <summary>
|
|
/// Creates new ArtQrCode object
|
|
/// </summary>
|
|
/// <param name="data">QRCodeData generated by the QRCodeGenerator</param>
|
|
public ArtQRCode(QRCodeData data) : base(data) { }
|
|
|
|
/// <summary>
|
|
/// Renders an art-style QR code with dots as modules. (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
|
|
/// </summary>
|
|
/// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
|
|
/// <returns>QRCode graphic as bitmap</returns>
|
|
public Bitmap GetGraphic(int pixelsPerModule)
|
|
{
|
|
return this.GetGraphic(pixelsPerModule, Color.Black, Color.White, Color.Transparent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders an art-style QR code with dots as modules and a background image (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
|
|
/// </summary>
|
|
/// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
|
|
/// <returns>QRCode graphic as bitmap</returns>
|
|
public Bitmap GetGraphic(Bitmap backgroundImage = null)
|
|
{
|
|
return this.GetGraphic(10, Color.Black, Color.White, Color.Transparent, backgroundImage: backgroundImage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders an art-style QR code with dots as modules and various user settings
|
|
/// </summary>
|
|
/// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
|
|
/// <param name="darkColor">Color of the dark modules</param>
|
|
/// <param name="lightColor">Color of the light modules</param>
|
|
/// <param name="backgroundColor">Color of the background</param>
|
|
/// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
|
|
/// <param name="pixelSizeFactor">Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be.</param>
|
|
/// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
|
|
/// <param name="quietZoneRenderingStyle">Style of the quiet zones</param>
|
|
/// <param name="backgroundImageStyle">Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone</param>
|
|
/// <param name="finderPatternImage">Optional image that should be used instead of the default finder patterns</param>
|
|
/// <returns>QRCode graphic as bitmap</returns>
|
|
public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, Bitmap backgroundImage = null, double pixelSizeFactor = 0.8,
|
|
bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Dotted,
|
|
BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Bitmap finderPatternImage = null)
|
|
{
|
|
if (pixelSizeFactor > 1)
|
|
throw new Exception("The parameter pixelSize must be between 0 and 1. (0-100%)");
|
|
int pixelSize = (int)Math.Min(pixelsPerModule, Math.Floor(pixelsPerModule / pixelSizeFactor));
|
|
|
|
var numModules = QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8);
|
|
var offset = (drawQuietZones ? 0 : 4);
|
|
var size = numModules * pixelsPerModule;
|
|
|
|
var bitmap = new Bitmap(size, size);
|
|
|
|
using (var graphics = Graphics.FromImage(bitmap))
|
|
{
|
|
using (var lightBrush = new SolidBrush(lightColor))
|
|
{
|
|
using (var darkBrush = new SolidBrush(darkColor))
|
|
{
|
|
// make background transparent
|
|
using (var brush = new SolidBrush(backgroundColor))
|
|
graphics.FillRectangle(brush, new Rectangle(0, 0, size, size));
|
|
//Render background if set
|
|
if (backgroundImage != null)
|
|
{
|
|
if (backgroundImageStyle == BackgroundImageStyle.Fill)
|
|
graphics.DrawImage(Resize(backgroundImage, size), 0, 0);
|
|
else if (backgroundImageStyle == BackgroundImageStyle.DataAreaOnly)
|
|
{
|
|
var bgOffset = 4 - offset;
|
|
graphics.DrawImage(Resize(backgroundImage, size - (2 * bgOffset * pixelsPerModule)), 0 + (bgOffset * pixelsPerModule), (bgOffset * pixelsPerModule));
|
|
}
|
|
}
|
|
|
|
|
|
var darkModulePixel = MakeDotPixel(pixelsPerModule, pixelSize, darkBrush);
|
|
var lightModulePixel = MakeDotPixel(pixelsPerModule, pixelSize, lightBrush);
|
|
|
|
for (var x = 0; x < numModules; x += 1)
|
|
{
|
|
for (var y = 0; y < numModules; y += 1)
|
|
{
|
|
var rectangleF = new Rectangle(x * pixelsPerModule, y * pixelsPerModule, pixelsPerModule, pixelsPerModule);
|
|
|
|
var pixelIsDark = this.QrCodeData.ModuleMatrix[offset + y][offset + x];
|
|
var solidBrush = pixelIsDark ? darkBrush : lightBrush;
|
|
var pixelImage = pixelIsDark ? darkModulePixel : lightModulePixel;
|
|
|
|
if (!IsPartOfFinderPattern(x, y, numModules, offset))
|
|
if (drawQuietZones && quietZoneRenderingStyle == QuietZoneStyle.Flat && IsPartOfQuietZone(x, y, numModules))
|
|
graphics.FillRectangle(solidBrush, rectangleF);
|
|
else
|
|
graphics.DrawImage(pixelImage, rectangleF);
|
|
else if (finderPatternImage == null)
|
|
graphics.FillRectangle(solidBrush, rectangleF);
|
|
}
|
|
}
|
|
if (finderPatternImage != null)
|
|
{
|
|
var finderPatternSize = 7 * pixelsPerModule;
|
|
graphics.DrawImage(finderPatternImage, new Rectangle(0, 0, finderPatternSize, finderPatternSize));
|
|
graphics.DrawImage(finderPatternImage, new Rectangle(size - finderPatternSize, 0, finderPatternSize, finderPatternSize));
|
|
graphics.DrawImage(finderPatternImage, new Rectangle(0, size - finderPatternSize, finderPatternSize, finderPatternSize));
|
|
}
|
|
graphics.Save();
|
|
}
|
|
}
|
|
}
|
|
return bitmap;
|
|
}
|
|
|
|
/// <summary>
|
|
/// If the pixelSize is bigger than the pixelsPerModule or may end up filling the Module making a traditional QR code.
|
|
/// </summary>
|
|
/// <param name="pixelsPerModule">Pixels used per module rendered</param>
|
|
/// <param name="pixelSize">Size of the dots</param>
|
|
/// <param name="brush">Color of the pixels</param>
|
|
/// <returns></returns>
|
|
private Bitmap MakeDotPixel(int pixelsPerModule, int pixelSize, SolidBrush brush)
|
|
{
|
|
// draw a dot
|
|
var bitmap = new Bitmap(pixelSize, pixelSize);
|
|
using (var graphics = Graphics.FromImage(bitmap))
|
|
{
|
|
graphics.FillEllipse(brush, new Rectangle(0, 0, pixelSize, pixelSize));
|
|
graphics.Save();
|
|
}
|
|
|
|
var pixelWidth = Math.Min(pixelsPerModule, pixelSize);
|
|
var margin = Math.Max((pixelsPerModule - pixelWidth) / 2, 0);
|
|
|
|
// center the dot in the module and crop to stay the right size.
|
|
var cropped = new Bitmap(pixelsPerModule, pixelsPerModule);
|
|
using (var graphics = Graphics.FromImage(cropped))
|
|
{
|
|
graphics.DrawImage(bitmap, new Rectangle(margin, margin, pixelWidth, pixelWidth),
|
|
new RectangleF(((float)pixelSize - pixelWidth) / 2, ((float)pixelSize - pixelWidth) / 2, pixelWidth, pixelWidth),
|
|
GraphicsUnit.Pixel);
|
|
graphics.Save();
|
|
}
|
|
|
|
return cropped;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Checks if a given module(-position) is part of the quietzone of a QR code
|
|
/// </summary>
|
|
/// <param name="x">X position</param>
|
|
/// <param name="y">Y position</param>
|
|
/// <param name="numModules">Total number of modules per row</param>
|
|
/// <returns>true, if position is part of quiet zone</returns>
|
|
private bool IsPartOfQuietZone(int x, int y, int numModules)
|
|
{
|
|
return
|
|
x < 4 || //left
|
|
y < 4 || //top
|
|
x > numModules - 5 || //right
|
|
y > numModules - 5; //bottom
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Checks if a given module(-position) is part of one of the three finder patterns of a QR code
|
|
/// </summary>
|
|
/// <param name="x">X position</param>
|
|
/// <param name="y">Y position</param>
|
|
/// <param name="numModules">Total number of modules per row</param>
|
|
/// <param name="offset">Offset in modules (usually depending on drawQuietZones parameter)</param>
|
|
/// <returns>true, if position is part of any finder pattern</returns>
|
|
private bool IsPartOfFinderPattern(int x, int y, int numModules, int offset)
|
|
{
|
|
var cornerSize = 11 - offset;
|
|
var outerLimitLow = (numModules - cornerSize - 1);
|
|
var outerLimitHigh = outerLimitLow + 8;
|
|
var invertedOffset = 4 - offset;
|
|
return
|
|
(x >= invertedOffset && x < cornerSize && y >= invertedOffset && y < cornerSize) || //Top-left finder pattern
|
|
(x > outerLimitLow && x < outerLimitHigh && y >= invertedOffset && y < cornerSize) || //Top-right finder pattern
|
|
(x >= invertedOffset && x < cornerSize && y > outerLimitLow && y < outerLimitHigh); //Bottom-left finder pattern
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resize to a square bitmap, but maintain the aspect ratio by padding transparently.
|
|
/// </summary>
|
|
/// <param name="image"></param>
|
|
/// <param name="newSize"></param>
|
|
/// <returns>Resized image as bitmap</returns>
|
|
private Bitmap Resize(Bitmap image, int newSize)
|
|
{
|
|
if (image == null) return null;
|
|
|
|
float scale = Math.Min((float)newSize / image.Width, (float)newSize / image.Height);
|
|
var scaledWidth = (int)(image.Width * scale);
|
|
var scaledHeight = (int)(image.Height * scale);
|
|
var offsetX = (newSize - scaledWidth) / 2;
|
|
var offsetY = (newSize - scaledHeight) / 2;
|
|
|
|
var scaledImage = new Bitmap(image, new Size(scaledWidth, scaledHeight));
|
|
|
|
var bm = new Bitmap(newSize, newSize);
|
|
|
|
using (Graphics graphics = Graphics.FromImage(bm))
|
|
{
|
|
using (var brush = new SolidBrush(Color.Transparent))
|
|
{
|
|
graphics.FillRectangle(brush, new Rectangle(0, 0, newSize, newSize));
|
|
|
|
graphics.InterpolationMode = InterpolationMode.High;
|
|
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
|
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
|
|
|
graphics.DrawImage(scaledImage, new Rectangle(offsetX, offsetY, scaledWidth, scaledHeight));
|
|
}
|
|
}
|
|
return bm;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines how the quiet zones shall be rendered.
|
|
/// </summary>
|
|
public enum QuietZoneStyle
|
|
{
|
|
Dotted,
|
|
Flat
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines how the background image (if set) shall be rendered.
|
|
/// </summary>
|
|
public enum BackgroundImageStyle
|
|
{
|
|
Fill,
|
|
DataAreaOnly
|
|
}
|
|
}
|
|
|
|
#if NET6_0_WINDOWS
|
|
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
|
#endif
|
|
public static class ArtQRCodeHelper
|
|
{
|
|
/// <summary>
|
|
/// Helper function to create an ArtQRCode graphic with a single function call
|
|
/// </summary>
|
|
/// <param name="plainText">Text/payload to be encoded inside the QR code</param>
|
|
/// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
|
|
/// <param name="darkColor">Color of the dark modules</param>
|
|
/// <param name="lightColor">Color of the light modules</param>
|
|
/// <param name="backgroundColor">Color of the background</param>
|
|
/// <param name="eccLevel">The level of error correction data</param>
|
|
/// <param name="forceUtf8">Shall the generator be forced to work in UTF-8 mode?</param>
|
|
/// <param name="utf8BOM">Should the byte-order-mark be used?</param>
|
|
/// <param name="eciMode">Which ECI mode shall be used?</param>
|
|
/// <param name="requestedVersion">Set fixed QR code target version.</param>
|
|
/// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
|
|
/// <param name="pixelSizeFactor">Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be.</param>
|
|
/// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
|
|
/// <param name="quietZoneRenderingStyle">Style of the quiet zones</param>
|
|
/// <param name="backgroundImageStyle">Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone</param>
|
|
/// <param name="finderPatternImage">Optional image that should be used instead of the default finder patterns</param>
|
|
/// <returns>QRCode graphic as bitmap</returns>
|
|
public static Bitmap GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, ECCLevel eccLevel, bool forceUtf8 = false,
|
|
bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Bitmap backgroundImage = null, double pixelSizeFactor = 0.8,
|
|
bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Flat,
|
|
BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Bitmap finderPatternImage = null)
|
|
{
|
|
using (var qrGenerator = new QRCodeGenerator())
|
|
using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion))
|
|
using (var qrCode = new ArtQRCode(qrCodeData))
|
|
return qrCode.GetGraphic(pixelsPerModule, darkColor, lightColor, backgroundColor, backgroundImage, pixelSizeFactor, drawQuietZones, quietZoneRenderingStyle, backgroundImageStyle, finderPatternImage);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif |