美化二维码
- 二维码内容长度不作控制
大多数互联网技术使用到二维码生成,都是使用开源的包生成,输入字符信息,即可生成一张包含此字符信息的黑白二维码图片:
上面有两个二维码图片,扫码结果都是访问到同个页面,但是大多数使用二维码的地方,都是生成第一种样式,WC里的广告就是直接的例子:
很明显,第一种二维码点阵非常复杂,在某种复杂的环境下,如扫码角度大、屏幕光线反光、光线明暗对比变化大、打印粘贴出去后污点影响,这时候扫码的图像识别会变得很困难,甚至根本扫不出来,直接原因是点阵太密,摄像头对点阵的误判超过码本身的容错率。
第二种二维码点阵则相对简单很多,在同样环境复杂条件下,扫码几乎都没有难度,同样优等环境下,扫码读取的速度也会 快很多。
这两种码本质上有什么区别呢?如果使用扫码解析出内容,你会发现内容不一样,扫出来结果分别是:
https://work.alibaba-inc.com/work/search?type=person&tabIndex=1&offset=0&isSingleTabSearch=1&filterParameters=%7B%22fieldindexes%22%3A%7B%22province%22%3A%22%E6%B5%99%E6%B1%9F%E7%9C%81%22%2C%22city%22%3A%22%E8%A1%A2%E5%B7%9E%E5%B8%82%22%7D%7D
http://ma.taobao.com/ZSk4YV
现在可以很清楚看到,第二个码是使用了短地址跳转,短地址的URL字符长度特别短,使得码的占阵变得很简单,究其原因,要了解下二维码的版本,最早的版本1只有17×17的黑白点阵,到现在最大的版本40有177×177点阵,每种版本的信息容量有限,版本越高容量越大。二维码编码过程中先是对数据进行编码,然后对照数据需要的容量来选择版本,字符长度越短,当然需要版本就越低。
另外,参照《QR Code Specification》里数据编码章节,你会发现数字编码(Numeric mode )和大写字母编码(Alphanumeric mode)所占的比特位特别少,在某些条件允许的情况下,可以尽量使用数字或大写字母来作来码信息内容载体,这样的二维码生成会更加简单一些。
- 二维码大小随意设置
由于交互设计师的需要,经常听到运营、交互设计同学让开发在产品中生成的二维码尺寸固定为多少长宽,比如171×171相素(不带边框)的二维码,这个需求是否能实现呢?事实上,如果按照标准二维码规范,这样大小的二维码无法生成,即使不按标准尺寸生产出来码能被扫码识别,也是不推荐的(只是利用了扫码的一些容错能力达到扫码结果,但是会影响扫码响应、容错等,我们希望是越优越好),具体原因如下:
二维码最早版本是17×17大小点阵,后面每增加一版本,点阵都是以4点阵增加,某一版本的点阵计算公式是:(V-1)*4 + 21(V是版本号),按照上面170×170大小计算:
版本3 29点阵x5放大= 145相素
版本3 29点阵x6放大= 174相素
版本4 33点阵x5放大= 165相素
版本4 33点阵x6放大= 198相素
…
你会发现,怎么也算不出有171相素大小的二维码,除非你加上边框拼凑。
- 二维码logo随添加
你会很多品牌、产品为了推广自己,往往会在二维码中间或边角加上logo:
当然,为了突出logo,你可能会这样加:
这时你会发现,二维码扫不出来了。二维码规范里根本没有logo这个概念,加logo是利用二维码容错率这个功能,生成二维码时,可以设置二维码的容错率(某些地方也叫容错级别),即对二维码点阵破坏纠错能力,分为:L、M、Q、H级别,级别越高,容错能力越大,当然点阵也会更加复杂,而加logo,对二维码来说,其实是对数据的一种污染,当染污破坏数据数超过当前设置的容错级别时,数据就不能再被纠错读取出来了。
在设计视觉效果前提下,建议logo越小越好,这样对点阵数据破坏小,能更好的被读取。如果对二维码解码了解的话,你可以兼顾效果和纠错,设计出如下的二维码视觉(图像和点阵复用):
- 二维码不设置边框
二维码扫码器读取二维码有一步是读取定位符(即三个角上的矩形),定位符的识别,是按照黑白点阵比例(1:1:3:1:1)来确定定位符:
要确定此比例,得先找到比例起止点(即上图中的A和B),标准黑白二维码就是二维码最边上的黑白交界处,实际二维码可能不是纯黑白,但是灰阶处理后还是按黑白来判断,假如你生成的二维码没有边框,贴在浅色背景上,扫码没问题,背景本身是浅色,灰阶等处理后,还是一样找到边界,假如背景是深色的,扫码识别就很难定位到边界了:
对扫码器来说,图像处理二值化后,背景颜色会被认为黑色,二维码定位符起点也是黑色,没有边界了,就无支法确定定位符位置。
目前主流的一些APP扫码器,如手淘、微信,都修复增加了这种情况的容错能力,能够用其他图像定位算法识别二维码,虽然速度可能会有影响(影响下一般感觉不出来),但是不排除有些旧的扫码SDK无法读取,或读取速度影响很大。
建议如果把二维码加入到深色背景上时,请加上浅色的边框,边框大小大于等于单位点阵大小为佳。
样式改进
- 尺寸更改
按照二维码规范,最小版本是17×17的点阵,最大是177×177点阵,常用的版本3和版本4大小也就分别是29和33个单位点阵,如果直接转成位图相素点的话,这么小是很难用扫码器扫出来的,存储或网络传输时可以考虑直接点阵存储,显示或打印出来,一般要按照固定比例放大,单个点单位长宽高等比放大即可。
- 矢量图
一般图形设计时会会用到矢量图,如果是正方形的单位点,一般来说位图直接放大图像也不会有什么失真,因为单位点都是黑白点,很多处理软件放大时进行插值算法(插入位图相素点),这种矩形失真很少。
规范图形处理还是要使用到矢量图,一般黑白二维码矢量生成都采用直接EPS文件结构生成代码:
/**
* Constructs an empty EpsDevice that writes directly to a file. Bounds must
* be set before use.
*/
EpsDocument(String title, OutputStream outputStream, int minX, int minY, int maxX, int maxY) throws IOException {
this.title = title;
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
this.stream = outputStream;
write(bufferedWriter);
}
/**
* Outputs the contents of the EPS document to the specified Writer,
* complete with headers and bounding box.
*/
public synchronized void write(Writer writer) throws IOException {
float offsetX = -minX;
float offsetY = -minY;
writer.write(“%!PS-Adobe-3.0 EPSF-3.0\n”);
writer.write(“%%Creator: EpsGraphics “ + EpsGraphics.VERSION
+ ” by M.Taobao.Com, http://www.taobao.com/\n“);
writer.write(“%%Title: “ + title + “\n”);
writer.write(“%%CreationDate: “ + new Date() + “\n”);
writer.write(“%%BoundingBox: 0 0 “ + ((int) Math.ceil(maxX + offsetX)) + ” “
+ ((int) Math.ceil(maxY + offsetY)) + “\n”);
writer.write(“%%DocumentData: Clean7Bit\n”);
writer.write(“%%LanguageLevel: 2\n”);
writer.write(“%%DocumentProcessColors: Black\n”);
writer.write(“%%ColorUsage: Color\n”);
writer.write(“%%Origin: 0 0\n”);
writer.write(“%%Pages: 1\n”);
writer.write(“%%Page: 1 1\n”);
writer.write(“%%EndComments\n\n”);
writer.write(“gsave\n”);
writer.write(offsetX + ” “ + (maxY + offsetY) + ” translate\n”);
writer.flush();
writer.write(“grestore\n”);
if (isClipSet()) {
writer.write(“grestore\n”);
}
writer.write(“showpage\n”);
writer.write(“\n”);
writer.write(“%%EOF”);
writer.flush();
}
这个生成的二维码矢量是黑白的,在某些设计上需要其他色彩的二维码,就不能用这个生成了,可以先找到点阵的向量信息,再用画矢量的一些工具(如EpsGraphics)进行绘制,绘制时可以填充需要的颜色,这里提供一种简易找到点阵矩形向量信息的算法:
1、把二维码点阵循环进行逐行处理,找到黑块的起止点,以线条为单位保存起来(保存起点坐标信息和终点坐标信息),直至最后一行结束;
2、把相同横坐标的起点和终点且纵坐标相同的线条合到一起,即找到黑块的矩形坐标向量集合。
以上绘制二维码只适合简单矩形或圆形的单位点阵二维码,如果涉及复杂的背景图,目前没有很好的矢量化方法,一般只能使用图形设计软件来人工制作了,如果您有把复杂图形矢量化的程序实现方法,欢迎提供。
- 点阵样式美化
点阵美化是常见的二维码美化手段,下面可以例举一些例子:
此类样式的处理方法也不复杂, 主要是看画图的算法实现,一般是先使用开源软件得到二维码原始二维占阵数据BitMatrix[][],然后通过特定的算法,逐个绘制定位符或者把黑点转成彩色圆点。
这里提供一个绘制定位符和黑点算法例子的部分java代码,国外网站上有些其他语言实现的算法,有兴趣的可以研究下。
/**
* 在指定位置绘制定位符
*
* @param startX 起点坐标X
* @param startY 起点坐标Y
* @param joinType 绘制类型 方角,圆角,斜角,
* @param argb 绘制颜色
* @param pointSize
* @return 绘制是否成功
*/
private boolean drawPartern(int startX, int startY, int joinType,int[] rgba, int pointSize, int makeMore) {
if (rgba == null || rgba.length != 4) {
log.error(“绘制颜色不对,必须为RGBA数组”);
return false;
}
this.smooth();
this.strokeWeight(pointSize);
this.strokeJoin(joinType);
// this.noFill();
this.fill(rgba[0], rgba[1], rgba[2], rgba[3]);
this.stroke(rgba[0], rgba[1], rgba[2], rgba[3]);
int xx = startX;
int yy = startY;
this.rect(xx + makeMore * pointSize + pointSize / 2, yy + makeMore
* pointSize + pointSize / 2, pointSize * 6, pointSize * 6);
this.stroke(255f);
this.strokeJoin(joinType);
this.fill(255f);
this.rect(xx + (makeMore + 1) * pointSize + pointSize / 2, yy
+ (makeMore + 1) * pointSize + pointSize / 2, pointSize * 4,
pointSize * 4);
this.fill(rgba[0], rgba[1], rgba[2], rgba[3]);
this.stroke(rgba[0], rgba[1], rgba[2], rgba[3]);
this.strokeJoin(joinType);
this.rect(xx + (makeMore + 2) * pointSize + pointSize / 2, yy
+ (makeMore + 2) * pointSize + pointSize / 2, pointSize * 2,
pointSize * 2);
// drawPartenInner(startX, startY, joinType, rgba, pointSize, makeMore);
return true;
}
/**
* 绘制自定义圆角填充方形
* @param xx 中心点X
* @param yy 中心点y
* @param asize 总大小
* @param rsize 圆角半径
* @param r 颜色R
* @param g 颜色G
* @param b 颜色B
* @param arf 颜色透明度
*/
private void drawRoundRect(float xx, float yy, float asize, float rsize,float r, float g, float b, float alpha) {
this.noStroke();
this.smooth();
float ax;
float ay;
// 四个90度扇形
ax = xx – asize / 2 + rsize;
ay = yy – asize / 2 + rsize;
this.arc(ax, ay, 2 * rsize, 2 * rsize, PI, PI + PI / 2);
ax = xx + asize / 2 – rsize;
ay = yy – asize / 2 + rsize;
this.arc(ax, ay, 2 * rsize, 2 * rsize, PI + PI / 2, 2 * PI);
ax = xx – asize / 2 + rsize;
ay = yy + asize / 2 – rsize;
this.arc(ax, ay, 2 * rsize, 2 * rsize, PI / 2, PI);
ax = xx + asize / 2 – rsize;
ay = yy + asize / 2 – rsize;
this.arc(ax, ay, 2 * rsize, 2 * rsize, 0, PI / 2);
ax = xx – asize / 2 + rsize;
ay = yy – asize / 2;
this.rect(ax – 1, ay, asize – 2 * rsize + 2, rsize + 1);// 上沿
ay = yy + asize / 2 – +rsize;
this.rect(ax – 1, ay – 1, asize – 2 * rsize + 2, rsize + 1);// 下沿
ax = xx – asize / 2;
ay = yy – asize / 2 + rsize;
this.rect(ax, ay – 1, asize, asize – 2 * rsize + 2);// 中间区块
}
public void drawContent() {
boolean[][] nMatrix = getBitMatrix();
boolean[][] cMatrix = nMatrix;
if (getBitMatrix() == null) {
// 矩阵没有生成
log.error(“生成错误”);
return;
}
int 拓展 = super.getDilatation();
int 码宽 = this.getBitMatrix().length;
float 雕刻宽度 = super.getUnitSize();
CImage img = this.loadImageJarIO(this.getBackGroundImage());
this.size(img.width, img.height);
this.image(img, 0, 0, img.width, img.height);
int xx = (img.width – this.getArctualSize()) / 2;
int yy = xx;
// 定位框
float[] rgba = { 151, 56, 124, 255 };
// 151, 56, 124
float[] rgbb = { 255, 126, 141, 255 };
float[] rgbc = getAvP(rgba, rgbb, 6f / (码宽 – 2 * this.getDilatation()));
this.noStroke();
drawParternRound(xx, yy, this.ROUND, rgbc, 雕刻宽度, 拓展);
drawParternRound(xx + 雕刻宽度 * (码宽 – 7 – 2 * 拓展), yy, this.ROUND, rgbc,雕刻宽度, 拓展);
rgbc = getAvP(rgba, rgbb, 0.9f);
drawParternRound(xx, yy + 雕刻宽度 * (码宽 – 7 – 2 * 拓展), this.ROUND, rgbc,雕刻宽度, 拓展);
// 去掉定位符涂黑
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 7; j++) {
nMatrix[i + 拓展][j + 拓展] = false;
nMatrix[码宽 – 7 – 拓展 + i][j + 拓展] = false;
nMatrix[i + 拓展][码宽 – 7 – 拓展 + j] = false;
}
}
this.noStroke();
float 半径 = 雕刻宽度 / 2;
for (int i = 0; i < 码宽; i++) {
for (int j = 0; j < 码宽; j++) {
if (nMatrix[i][j]) {
rgbc = getAvP(rgba,rgbb,(float) (j – this.getDilatation())/ (码宽 – 2 * this.getDilatation()));
this.fill(rgbc[0], rgbc[1], rgbc[2], rgbc[3]);
this.ellipse(xx + i * 雕刻宽度 + 半径, yy + j * 雕刻宽度 + 半径, 雕刻宽度,雕刻宽度);
}
}
}
}
- 背景图
二维码加背景也是常见的样式改进,和二维码增加logo大同小异,说白了,就是二维码图片和背景图片合成,关键是设计背景图的色彩和二维码色彩配当,二维码生成注意前面提到的内容增加版本增加时边框会有变化,需要保证有深色背景时的边框大小,常见的洋葱头二维码就是一个简单的例子:
使用突破
- 视觉效果
黑白二维码到处可见,彩色二维码你一定也见过,但是下面这样的二维码,你不一定见过下面这样的二维码:
这样的二维码是不是看起来美观多了?除了点阵外,还可以通过图片信息来给看到二维码的人传递更多的信息,
上面这种二维码是在二维码规范基础上作了一定的改进,因为没有违反二维码规范,所以通用的二维码扫码器都能扫码识别出来。这两种图片其实处理的关键技部分都一样的,第二个会动的GIF图,只是多了GIF图拆帧、单帧图片处理、再合成,GIF拆帧都是有现成成熟的处理方法的。所以关键还是单帧处理,业界上有类似的处理方法,但是效果不是特别好:
以上处理合成,不是整个图一次处理的,还是按照单位点来进行处理,因为要保证单位点处理完后的图片,在扫码器二值化后,识别出来和原始的二维码黑白点对应,这里二值化时,涉及到一个黑/白点阀值,所以得保证二维码和图像合成后的单位点处在阀值的左边还是右边。
淘宝最新的处理技术和业务有点不同,采用视觉码最新技术:
处理的过程大致如下:
1.背景图片与二维码重合部分处理成简单的bit流V0;
2. V0 Xor Qrcode bit流(V1),异或得到中间变量V2;
3. V1进行里德所罗门算法循环变换某些参数,得到变量结果V2’, V2’’, V2’’’…;
4. 中间变量V2再和V2’, V2’’, V2’’’…一系列值参考,得到最优化的mask图像;
5. mask图像和二维码bit流V1合并,得到视觉效果优化后的图像。
上面主要是使用到了德所罗门算法对数据的编码特性,二维码标准文档里生成纠错码写也是这样处理的,只是一般生成码实现,只是实现了一种纠结码生成方式,如zxing里的数据编码,这里处理要生成很多参选的V2’, V2’’, V2’’’…,所以效率上会有点影响。关于德所罗门算法有兴趣的同学可以深入了解下,这个技术使用还是很广泛的,比如碟片数据读取。
- 动态视频
和GIF一样,只是多了些视频处理技术,对视频单帧处理,增加二维码,然后再压制成视频,看起来就是一个视频了,由于压制视频比较耗资源,一般都是离线处理。
- 隐藏信息
以上两个二维码,用通码的扫码器或二维码解析出来的内容都是:http://s.tb.cn/3/H8R8Q/ZhpkPe ,但是第一个是正版商品二维码,第二个是盗版商从第一个解析出来内容,自己再生成赝品二维码,用手淘扫码,可以识别出是否是正品,原理是什么呢?
二信码除了正常的信息编码到点阵外,还可以把一些隐藏信息加入到二维码上面。前面提到过,不同版本的二维码信息容量不同,如果指定使用某个版本的二维码时,需要编码的信息不需要此版本的容量这么大,这时候多余的信息容量可以存放特殊的一些信息–即隐藏信息。当然这个不是二维码的标准规范,生成的二维码标准信息部分可以被通用的扫码器识别,你加上去的隐藏信息,就得你自己来解码了。
关于隐藏信息存放位置,想了解的同学,可以参考下《QR Code Specification》里面8.4.8章节和8.4.9章节关于数据编码的补齐码和终结符。
隐藏信息可以使用到很多地方,因为信息编码是你生成的,所以别处无法得知和防造,可以保证二维码生成的唯一性:
扫码得到商品信息判断真假就是一个典型的使用。
如果盗版者用拍照或扫描原始二维码图,进行拷贝,也可以利用二维码的扫码次数进行次要验证,同一商品码是否被大量扫码,验证是否正品,一般正品卖出来后,扫码次数都是只有买家扫的少数几次。
当然这种隐藏信息使用也有缺点,就是扫码需要你的专用SDK,不然无法读取,对于手淘扫码用户量这么大情况下,可以很好的利用起来。
其他二维码
除了Qrcode之外,还有不少其他类型的二维码,如PDF417、QRCCode、Data Matrix、Maxi Code、Code 49、Code 16K、Code One等二维码,这些互联网上都有不少介绍,这里不再赘述。这里举几个比较形象的例子:
- 彩链码
彩链码是一种利用二维点阵再加上其中基本色彩(Blue、Red、Green、Black)作为信息载体,所以包含的信息长度有限,但是排列组合可以很多:
上面的第一张图是最简单的彩链码,只有方块和颜色,再加上边框。第二三张是在第一张基础上的变形美化,扫码识别过程和第一种也是一样的,按照码的版本,对点阵图进行切割固定的块,然后对块主要色彩识别,这时块不是简单的0或1两进制了,而是4进制数据。
这种二维码可以在符合点阵规律的基础上,适当发挥把各种图形融合进来,以达到码信息传递和视觉效果的并存。
由于点阵不能太复杂,包含信息不多,所以不能直接表达复杂的信息,一般扫码后,需要通过这个特定排列的组合去服务端拿到真正的复杂数据信息。
除了需在服务端访问外,还有的缺点是新型的码很难被用户认知和使用起来,一来图形和码融合太紧密,需要让用户知道可以当作码扫,另外这个需要专用的SDK才能扫,如果拥有海量用户的APP可以很好推广起来,如手淘、微信。
- 4G码
4G码严格来说,不属于二维码范畴,属于图像特征信息扫描,不过利用了二维码定位符,扫码时将定位符内的图像裁剪然后进行图像特征处理,相对于整个图像处理地,这种方法提取的图像的特征会比较稳定,各种方式扫码得到的特征值都比较容易确定为某一值,容错能力比较强,生成码时,训练下扫码图像特征值,确定后与确定目标信息绑定,如商品连接,目前手淘扫码很快就支持4G码扫码:
这种码对图像的破坏很小,生成的码非常美观,不过用户的认知性会比较差,在引导用户扫码上需要成本。
原文地址: https://blog.csdn.net/u012909822/article/details/105892260
本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
相关文章