前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >艺术二维码生成原理和实践

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

原创
作者头像
熊整文
修改2017-06-19 17:40:29
6K0
修改2017-06-19 17:40:29
举报
文章被收录于专栏:熊整文的专栏

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

一. 二维码原理

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

如下图所示:

[1497495769006_2205_1497495769260.jpg]
[1497495769006_2205_1497495769260.jpg]

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

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

[1497495788314_6357_1497495788440.jpg]
[1497495788314_6357_1497495788440.jpg]

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

2. 艺术二维码

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

[1497495807169_4560_1497495807366.jpg]
[1497495807169_4560_1497495807366.jpg]

二. 艺术二维码生成方法

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

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

[1497495828582_8319_1497495828646.png]
[1497495828582_8319_1497495828646.png]

代码如下:

代码语言:txt
复制
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等)。

代码语言:txt
复制
    /** 格式化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个单位,后续可以省去再去查找定位符以及计算码元点宽度的步骤。

代码语言:txt
复制
    /** 查找出所有符合规则的形状  */
    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的操作,将找出的定位符用符合定位符特征的图片绘制到画布上,再将各个特定矩形框也以相应的宽高尺寸绘制到画布,然后将单个的码元点以简单的图标绘制上去,最后还可以利用二维码的容错机制,在画布的中央小块位置画上个性化的头像。

代码语言:txt
复制
    /** 绘制艺术二维码的图像  */
    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扫一扫,有惊喜哦!

[1497496642219_1151_1497496642377.png]
[1497496642219_1151_1497496642377.png]

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 二维码原理
  • 二. 艺术二维码生成方法
  • 三. 总结
相关产品与服务
图像处理
图像处理基于腾讯云深度学习等人工智能技术,提供综合性的图像优化处理服务,包括图像质量评估、图像清晰度增强、图像智能裁剪等。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档