艺术二维码生成原理和实践

二维码现在是大街小巷的标配设计,只要用手机扫一下就能快速的进入相应的页面,可以跳转到相应页面,或是查看名片、付款、收红包等等。本文依据二维码的生成原理,用艺术图标替代枯燥的黑白二维码,赋予二维码艺术性和鲜活的个性。

一. 二维码原理

二维码 (2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的。二维码的优点:二维码存储的数据量更大;可以包含数字、字符,及中文文本等混合内容;有一定的容错性(在部分损坏以后可以正常读取);空间利用率高。

如下图所示:

1. 二维码 QR(Quick-Response) code

是被广泛使用的一种二维码,解码速度快。它可以存储多种类型。如下图是一个qrcode的基本结构

其中:位置探测图形、位置探测图形分隔符、定位图形:用于对二维码的定位,对每个QR码来说,位置都是固定存在的,只是大小规格会有所差异;校正图形:规格确定,校正图形的数量和位置也就确定了;格式信息:表示改二维码的纠错级别,分为L、M、Q、H;版本信息:即二维码的规格,QR码符号共有40种规格的矩阵(一般为黑白色),从21x21(版本1),到177x177(版本40),每一版本符号比前一版本 每边增加4个模块。数据和纠错码字:实际保存的二维码信息,和纠错码字(用于修正二维码损坏带来的错误)。

2. 艺术二维码

依据二维码的结构特性,加入丰富生动的图案,提升其美观性。其原理就是针对黑白二维码中的黑色码元,用色彩绚丽的图案进行替换。

二. 艺术二维码生成方法

1.生成二维码的原始数据矩阵。

有很多开源的库工具可以直接用来生成二维码的BitMatrix,比如应用较广泛的google提供的zxing库。

代码如下:

public ArtQRCode(String url) throws IllegalArgumentException {
        BitMatrix bitMatrix = null;
        if (!TextUtils.isEmpty(url)) {
            //配置参数
            Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
            //容错级别
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
            //设置空白边距的宽度
            hints.put(EncodeHintType.MARGIN, 0); //default is 4
            // 图像数据转换,使用了矩阵转换
            try {
                bitMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, 0, 0, hints);
            } catch (WriterException e) {
                e.printStackTrace();
            }
        }
        if (bitMatrix!=null) {
            valid = formatBitMatrix(bitMatrix);
            Log.i(TAG, "bitMatrix:"+bitMatrix);
        } else {
            throw new IllegalArgumentException("bitMatrix should not be null!");
        }
    }

2.格式化BitMatrix,找出其中所有码元点,和符合特定矩形形状的码元集合。

按照二维码的原则,在BitMatrix中,先找出左、右、下等3个回字形定位符;然后遍历在BitMatrix,找出所有的码元点,再从码元点中找出特定矩形框(也即实际需求对二维码中要替换成特定图标的矩形框,比如33,23,22,12等)。

    /** 格式化BitMatrix,找出其中所有的码元点   */
    private boolean formatBitMatrix(BitMatrix bitMatrix) {
        boolean success = false;
        int matrixLength = bitMatrix.getWidth();
        int matrixHeight = bitMatrix.getHeight();
        if (matrixLength == matrixHeight && matrixLength>(LOCATOR_WIDTH+1)*2) {
            success = true;
            codeNumPerLine = matrixLength;
            leftLocator.set(0, 0, LOCATOR_WIDTH, LOCATOR_WIDTH);
            rightLocator.set(matrixLength - LOCATOR_WIDTH, 0, matrixLength, LOCATOR_WIDTH);
            bottomLocator.set(0, matrixLength - LOCATOR_WIDTH, LOCATOR_WIDTH, matrixLength);
            //遍历,得出所有的码元
            for (int x = 0; x < matrixLength; x++) {
                StringBuilder sb = new StringBuilder("列"+x);
                StringBuilder sb_p = new StringBuilder("point");
                //先列遍历,再行遍历
                for (int y = 0; y < matrixLength; y++) {
                    //查看此element是否为有信息的码元点
                    boolean info = bitMatrix.get(x, y);
                    Point point = new Point(x, y);
                    if (info) {
                        String pointStr = "["+y+","+x+"]";
                        if (!isElementInRect(leftLocator, point)
                                && !isElementInRect(rightLocator, point)
                                && !isElementInRect(bottomLocator, point)) {//不属于定位符的点
                            //以列优先的顺序存放,故需要把下标的行列反过来,便于后续的按列顺序查找
                            int columnRow = y + x*matrixLength;
                            //存储该码元在可用码元矩阵中的列行坐标
                            this.codeElements.put(columnRow, point);
                            sb.append(pointStr);
                        } else {
                            sb_p.append(pointStr);
                        }
                    } 
                }
                Log.w(TAG, sb.toString());
                sb_p.append(" in locator");
                Log.w(TAG, sb_p.toString());
            }            
        }
        return success;
    }

bitMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, 0, 0, hints);这句是用url链接来生成BitMatrix的,其中要传入需要生成矩阵的宽和高,经过对比,发现这里可以取巧,设置宽和高为0,这样生成的矩阵最小(保证码元点信息无遗漏),每个码元点的宽度为1个单位,后续可以省去再去查找定位符以及计算码元点宽度的步骤。

    /** 查找出所有符合规则的形状  */
    private void searchRect() {
        //遍历形状集Shape,查找出所有的矩形框,将其中的码元点标记为已发现,并添加到outElements中
        for (int i = 0; i < SHAPES.size(); i++) {
            Shape shape = SHAPES.valueAt(i);
            if (shape!=null && shape.width!=0 && shape.height!=0) {
                adjustRect(shape);
            }
        }
        //将已标记的码元点从codeElements中移除
        int size = codeElements.size();
        for (int index = size-1; index >= 0; index--) {
            //每次只删一条,保证下标index始终处于size范围内
            int columnRow = codeElements.keyAt(index);
            Point point = outElements.get(columnRow);
            if (point!=null) {
                Log.i(TAG, "remove code at columnRow["+point.x+","+point.y+"]");
                codeElements.remove(columnRow);
            }
        }
    }

3.绘制图形。

依据前面两步对BitMatrix的操作,将找出的定位符用符合定位符特征的图片绘制到画布上,再将各个特定矩形框也以相应的宽高尺寸绘制到画布,然后将单个的码元点以简单的图标绘制上去,最后还可以利用二维码的容错机制,在画布的中央小块位置画上个性化的头像。

    /** 绘制艺术二维码的图像  */
    public Bitmap createArtBitmap(Context context, int width, Bitmap logoBmp) {
        /**商  */
        int codeWidth = width/codeNumPerLine;
        /**余数  */
        int remainder = width%codeNumPerLine;
        int matrixWidth = width;
        if (remainder>0) {//调整初始图片的宽度,凑整,为了每个码元点占用整数个像素点
            codeWidth++;
            matrixWidth = codeWidth*codeNumPerLine;
        }
        Bitmap bitmap = null;
        if (valid) {
            searchRect();
            // 生成二维码图片的格式,使用ARGB_8888
            bitmap = Bitmap.createBitmap(matrixWidth, matrixWidth, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            Paint paint = new Paint();
            //先画原图
            drawPicture(context, canvas, paint, codeWidth);
            //再画logo
            addLogo(canvas, bitmap, logoBmp);
        }
        if (remainder>0) {//再将图像压缩至目标宽高
            bitmap = Bitmap.createScaledBitmap(bitmap, width, width, true);
        }
        return bitmap;
    }
    /** 在画布上绘出所有的矩形框和码元点  */
    private void drawPicture(Context context, Canvas canvas, Paint paint, int codeUnit) {
        // 按照二维码的算法,在canvas上逐个绘制二维码的码元点
        Resources resource = context.getResources();
        Bitmap locatorBmp = BitmapFactory.decodeResource(resource, R.drawable.qvip_pay_qrart_locator);
        Bitmap locatorIcon = Bitmap.createScaledBitmap(locatorBmp, LOCATOR_WIDTH*codeUnit, LOCATOR_WIDTH*codeUnit, true);
        //先画3个定位符
        canvas.drawBitmap(locatorIcon, leftLocator.left*codeUnit, leftLocator.top*codeUnit, paint);
        canvas.drawBitmap(locatorIcon, rightLocator.left*codeUnit, rightLocator.top*codeUnit, paint);
        canvas.drawBitmap(locatorIcon, bottomLocator.left*codeUnit, bottomLocator.top*codeUnit, paint);
          //再画所有找到的特殊矩形框
          int size1 = allRects.size();
          Log.i(TAG, "allRects size="+size1);
          for (int index1 = 0; index1 < size1; index1++) {
              int shapeId = allRects.keyAt(index1);//key为此集合中相同的shapeId
              SparseArray<Point> rects = allRects.valueAt(index1);
              Shape shape = SHAPES.get(shapeId);
              Bitmap bmp = BitmapFactory.decodeResource(resource, shape.resId);
            Bitmap shapeIcon = Bitmap.createScaledBitmap(bmp, shape.width*codeUnit, shape.height*codeUnit, true);
              int size2 = rects!=null ? rects.size() : 0;
              Log.i(TAG, "shape "+shapeId+" size="+size2);
              //对同一种矩形框,查出所有实例并绘制
              for (int index2 = 0; index2 < size2; index2++) {
                  Point point = rects.valueAt(index2);
                  if (point!=null) {
                      canvas.drawBitmap(shapeIcon, point.x*codeUnit, point.y*codeUnit, paint);
                }
            }
        }
          //再画剩下的单个的码元
        int size = codeElements.size();
        Bitmap singleBmp = BitmapFactory.decodeResource(resource, R.drawable.qvip_pay_qrart_3301);
        Bitmap codeBmp = Bitmap.createScaledBitmap(singleBmp, codeUnit, codeUnit, true);
        for (int singleIndex = 0; singleIndex < size; singleIndex++) {
            Log.i(TAG, "single code size="+size);
            Point singlePoint = codeElements.valueAt(singleIndex);
            if (singlePoint!=null) {
                canvas.drawBitmap(codeBmp, singlePoint.x*codeUnit, singlePoint.y*codeUnit, paint);
            }
        }
    }

至此,二维码图片Bitmap就已经生成OK了。

三. 总结

利用二维码的结构特性,将其中连成片的特殊形状用个性化的图片代替,可以使二维码更加美观和生动。

在手Q中,用此方法,实际可用生成面对面红包、付款码等二维码图形。

附:试用手Q扫一扫,有惊喜哦!

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

熊整文的专栏

1 篇文章1 人订阅

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端说吧

css - 评分效果的星星✨外衣

这种效果,如果遇到一分一个星,没有半星(或者有也可以,直接加一个半星的类名)的情况,还可以通过添加多个结构实现。

281
来自专栏coding...

Objective-C 使用核心动画CAAnimation实现动画先来看看效果吧Demo地址

https://github.com/gongxiaokai/CAAnimationDemo

743
来自专栏walterlv - 吕毅的博客

Grid 布局算法!自己动手实现一个 Grid

2018-05-20 07:11

502
来自专栏MixLab科技+设计实验室

自己动手做一个识别手写数字的web应用04

接着往期的3篇继续,一步步动手做: 自己动手做一个识别手写数字的web应用01 自己动手做一个识别手写数字的web应用02 自己动手做一个识别手写数字的web应...

7265
来自专栏Code_iOS

OpenGL ES 2.0 (iOS)[05-1]:进入 3D 世界,从正方体开始

a. 渲染管线的基础知识 《OpenGL ES 2.0 (iOS)[01]: 一步从一个小三角开始》

1143
来自专栏阿凯的Excel

巧妙设置蛋糕图(Excel绘制图表系列课程)!

一直以踏踏实实做人!安安静静分享Excel技巧为宗旨的我,今天还是标题党了! 为啥尼! 我今天想分享这个图的绘制过程! ? 我真心不知道除了面积折线组合图外还能...

3375
来自专栏数据小魔方

Xcelsius(水晶易表)系列2——单值部件

今天专门跟大家分享水晶易表中的一大类部件——单值部件。 单值部件使用频率很高,从它的名称就能猜个大概,它是用来表达单个指标的图表部件。 水晶易表中的单值部件大体...

2515
来自专栏代码GG之家

一波开源库来袭

一波开源库来袭 最近在做MVVM的教程,同时在github上闲逛,发现了一些好的开源库,于是乎推荐给大伙了。 1 SmallChart图表库 SmallChar...

1925
来自专栏机器人网

一文教你识别:数控机床电柜内那些常用的元件

断路器、接触器、中间继电器、热继电器、按钮、指示灯、万能转换开关和行程开关是电气控制回路中最常见的八种元件。本文以图文并茂的方式介绍常用电气元件的原理及应用,通...

3245
来自专栏河湾欢儿的专栏

精灵图

为什么要有精灵图? 最早的时候网速十分有限,为了提升用户体验,我们会将一张大图分解成多张小图来提高页面打开速度,但是网速得到了提升,为了能够让服务器承载更多的...

641

扫码关注云+社区