898 字
4 分钟
使用Zxing.Net 创建透明背景艺术二维码
引言
用过微信的都知道,里面有一个个性化二维码名片的功能;

最近接到需求要做一个类似的好看一点的二维码,微信里面不支持手动添加背景图片,估计是做的模板;但是配色不是我擅长的事,要做很多模板出来让用户选是不现实的,我期望有自动合成颜色的方法,一番搜索之后找到一个商用产品qrcode.studio,里面有一个透明背景的功能,可以实现我的要求。

于是参照实现了一个可以融合背景图片的透明背景图功能
解决方案
在visual studio中打开从qrcode.studio下载下来的png二维码文件,放大之后可以看到module的形状

因此,在黑的地方画一个点,周围再自动做颜色渐变就可以了;这里的径向渐变可以使用RadialGradientBrush,但是我是Winform,没有这个画刷;于是我参考c# radial gradient brush effect in GDI and winforms使用了PathGradientBrush
主要的代码在这里,完整demo在最后
public override Bitmap Render(BitMatrix matrix, BarcodeFormat format, string content, EncodingOptions options){ int width = matrix.Width; int height = matrix.Height; Foreground = Color.Black; bool hasBackImage = false; var qrCode = Encoder.encode(content, ErrorCorrectionLevel.L,options.Hints);
var qrCodeMatrix = qrCode.Matrix; Console.WriteLine("-----------------------------"); foreach (var point in qrCodeMatrix.Array) { Console.WriteLine(string.Join(string.Empty, point.Select(s => { if (s == 0) return " "; return s.ToString(); }))); }
var det=new Detector(matrix); var detPoints = det.detect().Points;
var backgroundBrush = new LinearGradientBrush( new Rectangle(0, 0, width, height), BackgroundGradientColor, BackgroundGradientColor, LinearGradientMode.Vertical); var foregroundBrush = new LinearGradientBrush( new Rectangle(0, 0, width, height), ForegroundGradientColor, ForegroundGradientColor, LinearGradientMode.ForwardDia
var bmp = new Bitmap(width,height); var gg = Graphics.FromImage(bmp); if (File.Exists(BackImageFullName)) { var backImageTemp=new Bitmap(BackImageFullName); bmp= new Bitmap(backImageTemp, new Size(width, height)); gg=Graphics.FromImage(bmp); hasBackImage = true; } else { gg.Clear(BackgroundGradientColor); }
Point startPoint=new Point(0,0); Point endPoint=new Point(0,0); bool isStartPoint = false;
//var foreColor = Color.Black; //var backColor = Color.White; var foreColor = ForegroundGradientColor; var backColor = BackgroundGradientColor;
if (hasBackImage && IsMergeBackColor) { var rateX = bmp.Width / qrCodeMatrix.Width; var rateY = bmp.Height / qrCodeMatrix.Height; if (rateY > rateX) { rateY = rateX; } else { rateX = rateY; }
//todo boarder效果 var defaultBorder = 0; var largeImage = new Bitmap(bmp, qrCodeMatrix.Width * rateX+defaultBorder, qrCodeMatrix.Height * rateY+defaultBord var largeG = Graphics.FromImage(largeImage); for (int inputY = 0; inputY < qrCodeMatrix.Height; inputY++) { for (int inputX = 0; inputX < qrCodeMatrix.Width; inputX++) { Rectangle foreRectangle = new Rectangle(inputX * rateX+defaultBorder, inputY * rateY+defaultBorder, rateX,
//定点位使用黑白,其它位置使用PathGradientBrush根据module点的坐标画出图形 if (IsInDetect(qrCodeMatrix, inputX, inputY)) { if (qrCodeMatrix[inputX, inputY] == 1) { largeG.FillRectangle(new SolidBrush(foreColor), foreRectangle); } else { largeG.FillRectangle(new SolidBrush(backColor), foreRectangle); } } else { using (var ellipsePath = new GraphicsPath()) {
ellipsePath.AddEllipse(foreRectangle); var brush = new PathGradientBrush(ellipsePath);
var color = bmp.GetPixel(foreRectangle.X, foreRectangle.Y);
brush.CenterPoint = new PointF(foreRectangle.Width / 2 + foreRectangle.X, foreRectangle.Height / 2 + foreRectangle.Y); brush.SurroundColors = new[] {color}; brush.FocusScales = new PointF(0, 0); if (qrCodeMatrix[inputX, inputY] == 1) {
brush.CenterColor = foreColor; } else { brush.CenterColor = backColor; }
largeG.FillRectangle(brush, foreRectangle); } } } }
return largeImage; }
for (int x = 0; x < width - 1; x++) { for (int y = 0; y < height - 1; y++) { if (matrix[x, y]) { if (!isStartPoint) { isStartPoint = true; startPoint = new Point(x, y); endPoint = new Point(width - x, height - y); }
gg.FillRectangle(foregroundBrush, x, y, 1, 1); } else { if (x < endPoint.X && y < endPoint.Y && x > startPoint.X && y > startPoint.Y) { if (hasBackImage) gg.FillRectangle(backgroundBrush, x, y, 1, 1);
} } } }
if (File.Exists(IconFullName)) { var icon = new Bitmap(IconFullName); Image circleIcon; if (IconShape == IconShape.Round) { circleIcon = CutCircle(IconFullName, 0, 0, icon.Height); } else { circleIcon = icon; } circleIcon = new Bitmap(circleIcon, new Size(33, 33)); gg.DrawImage(circleIcon, new PointF((startPoint.X + endPoint.X) / 2 - circleIcon.Width / 2, (startPoint.Y + endPoint.Y) / 2 - circleIcon.Height / 2)); }
if (hasBackImage) gg.DrawRectangle(new Pen(Color.White, 5), startPoint.X - 3, startPoint.Y - 3, endPoint.X - startPoint.X + 4, endPoint.Y - startPoint.Y + 4);
return bmp;}还是使用林克作为背景,生成的二维码如下:

虽然看起来差了点,但是基本可以实现需求
常见问题
- 关于二维码
初次接触二维码建议看一下qrcode.com
- 如何debug查看生成的图片
中断的时候调用bmp.Save(“123.png“)方法就行了
使用Zxing.Net 创建透明背景艺术二维码
https://blog.wangshuai.app/2020-08-05-使用zxingnet-创建透明背景艺术二维码/