C# QRCode二维码的解析与生成

发布时间 2023-08-18 16:13:19作者: MaQaQ

  已知一张二维码图片,怎么生成一张一模一样的图片出来?

  最近有个项目,需要用到QRCode,之前只做过Datamatrix格式的,想着应该也是差不多的,于是就依葫芦画瓢,掏出我的陈年OnBarcode类库,一通修改,生成了个崭新的QRCode,与客户提供的二维码图片一比对,虽然扫出来内容一样,但明显图案并不相同,于是我就意识到,事情并不简单。原图如下:

  首先第一个怀疑的就是参数问题啦,看了一下OnBarcode.Barcode.QRCode的属性,可疑的参数有DataMode、ECL、Version等等,毕竟这几个应该是常见的参数。最大的可能就是ECL,这个是二维码的纠错级别,一般是L/M/Q/H四个等级,对应可遮挡7%/15%/25%/30%,一般如果想在二维码中插入logo的话,就要把纠错级别调高一些。

  于是我尝试了一下,修改了不同的ECL,对应输出的图案都是不同的,但还是没有生成我想要的图案。

  难道是我的陈年OnBarcode类库跟不上时代了?于是我换成了QRCoder,又是一通折腾,还是没对上。再是找了一些在线生成二维码网站(见下方链接),逐个比较,发现还真是五花八门,有些是不提供设置直接生成,有些是可修改版本及ECL的,最后结果都不太一样,幸好有些网站生成的图片是对上了的,总算有条退路。

  既然有了备用方案,那我就可以慢慢研究了。查了下QRCode的生成原理,参考《【来龙去脉系列】QRCode二维码的生成细节和原理》,这一篇讲得不错,不过他在讲Mask那里有点模糊,这也导致我一不留神就掉坑了,后面看了另外两篇才纠正回来。看完大概有了概念,至少明确了,我这个码应该是是version1,Alphanumeric mode 字符编码,同时也知道还有个掩码参数(即Mask),而且从图案中可以看到Format Information,那里面存放着ECL跟Mask。文中将Format Information标注0-14,在左上角从上往下从右往左,并且说15个bits中包括5个数据bits:其中,2个bits用于表示使用什么样的Error Correction Level, 3个bits表示使用什么样的Mask,那我自然就认为0-4就是所谓的数据bits了,看了下是11100,跟10101异或得出01001,ECL是01-L?但我在线生成的时候是选的H,那就肯定不对。

  这时候我发现有个在线生成网站写明了用的是ZXing,于是我又引用了ZXing尝试了一下,按照常规的参数配置了ECL-H,还是不行,一度陷入瓶颈。

  我还突发奇想试着把原图上传到在线网站去解析,说不定有哪个网站能给出点提示,但最终也只是能看到内容。不过这么一来我又打开了思路,我可以自己解析,说不定就能拿到配置参数了,感觉可行性还是有的。

  刚好ZXing就有解码的功能,尝试一下:

 1 static void ParseQRCode(string imagePath, out string data, out IDictionary<ResultMetadataType, object> hints)
 2         {
 3             hints = null;
 4             BarcodeReader reader = new BarcodeReader();
 5             reader.Options.PossibleFormats = new List<BarcodeFormat> { BarcodeFormat.QR_CODE };//可加可不加
 6             Bitmap bitmap = new Bitmap(imagePath);
 7             Result result = reader.Decode(bitmap);
 8             if (result != null)
 9             {
10                 data = result.Text;
11                 hints = result.ResultMetadata;
12             }
13             else
14             {
15                 data = null;
16             }
17         }

  输入图片路径,找了一下result的属性,果然在ResultMetadata里面存放着我要的信息:

   ECL是H,那就没错了,剩下的参数里面,这个QR_MASK_PATTERN不就是掩码参数咯,剩下两个看了下参数介绍,应该不是很重要,于是重点关注QR_MASK_PATTERN。刚好手头的代码引用的是ZXing,这里要注意的是,ZXing的Hints是不能整个赋值的,只能用Add的方式逐个插入参数:

 1 public static Bitmap CreateQRCode(string data)
 2         {
 3             Bitmap bitmap = null;
 4             GC.Collect();
 5             BarcodeWriter barCodeWriter = new BarcodeWriter();
 6             barCodeWriter.Format = BarcodeFormat.QR_CODE; // 生成码的方式(这里设置的是二维码),有条形码\二维码\还有中间嵌入图片的二维码等
 7             //barCodeWriter.Options.Hints.Add(EncodeHintType.CHARACTER_SET, "UTF-8");// 支持中文字符串
 8             barCodeWriter.Options.Hints.Add(EncodeHintType.ERROR_CORRECTION, ZXing.QrCode.Internal.ErrorCorrectionLevel.H);
 9             barCodeWriter.Options.Hints.Add(EncodeHintType.QR_MASK_PATTERN, 2);
10             barCodeWriter.Options.Height = 200;
11             barCodeWriter.Options.Width = 200;
12             barCodeWriter.Options.Margin = 0; //设置的白边大小
13             ZXing.Common.BitMatrix bm = barCodeWriter.Encode(data);
14             bitmap = barCodeWriter.Write(bm);
15             return bitmap;
16         }

  果然,结果跟原图一样(外面的圈是我自己加的):

  问题已经解决了,但我还是有个疑惑,为什么我根据图案的Format Information得出的参数是不对的,于是我继续翻资料,终于在《[译] 为程序员写的Reed-Solomon码解释》这一篇里面看到了,是逆时针读的bit,wxxxx,最开始看的那一篇是顺时针标注的啊,那不就是反过来,仔细一看,读出来是00111,跟10101异或得10010,ECL是10-H,Mask是010-2,对上了。只能说,还是得多方考证吧。

  完结撒花。

 

  • 参考资料:

《【来龙去脉系列】QRCode二维码的生成细节和原理》 https://www.cnblogs.com/tuyile006/p/10916075.html

《二维码生成原理》 https://zhuanlan.zhihu.com/p/543574464

《[译] 为程序员写的Reed-Solomon码解释》 https://www.felix021.com/blog/read.php?2116

《C# 生成二维码方法(QRCoder)》 https://www.cnblogs.com/yakniu/p/16917897.html

《.NET Core(C#)使用ZXing.Net生成条码(Barcode)和二维码(QR code)图片及示例代码》 https://www.cnblogs.com/fireicesion/p/16809637.html

《C# 利用ZXing.Net来生成条形码和二维码》 https://blog.csdn.net/lwf3115841/article/details/128429605

  • 在线工具:

OSCHINA https://tool.oschina.net/qr

互联二维码 https://www.hlcode.cn/decode

草料二维码 https://cli.im/text

二维码工坊 https://www.2weima.com/?text=A0010101