使用Zxing.Net 创建透明背景艺术二维码

Create art transparent background QR Code by Zxing.Net

Posted by 王帅 on August 5, 2020

引言

用过微信的都知道,里面有一个个性化二维码名片的功能;

image-20200805152257211

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

image-20200805154119073

于是参照实现了一个可以融合背景图片的透明背景图功能

解决方案

在visual studio中打开从qrcode.studio下载下来的png二维码文件,放大之后可以看到module的形状

image-20200805155633543

因此,在黑的地方画一个点,周围再自动做颜色渐变就可以了;这里的径向渐变可以使用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;
}

还是使用林克作为背景,生成的二维码如下:

testQR000007

虽然看起来差了点,但是基本可以实现需求

常见问题

  • 关于二维码

初次接触二维码建议看一下qrcode.com

  • 如何debug查看生成的图片

中断的时候调用bmp.Save(“123.png“)方法就行了