- A+
已知一张二维码图片,怎么生成一张一模一样的图片出来?
最近有个项目,需要用到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