Android利用ZXing生成带LOGO的二维码图片

Android利用ZXing生成带LOGO的二维码图片

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

前言:
昨天有个小伙伴问我怎么动态生成带logo的二维码,虽然我也见过带logo的二维码,但是不知道具体实现,我猜想ZXing能直接生成带logo的二维码吧。于是我去百度了一番,结果都是需要图片合成的。想了一下,二维码的主要概念使用来存储文字信息,并不能存储图片信息,而带logo的二维码只是提供方加上去让别人知道这个二维码的提供方是谁。在百度搜索了一番,搜到的资料或多或少有些瑕疵,而且大多都是互相转载,内容也没解释清楚,也有少部分讲解二维码算法(简单的生成带logo的矩形二维码不涉及二维码算法),于是自己想写一篇相关的文章。
一、概述
本文是根据github上一个二维码库QRCode中第三个生成二维码demo改造而来,主要涉及:

  • Bitmap合成(图片合成)
  • 二维码矩阵与一维像素相互转换(将logo转成像素放入二维码中间)
  • 调用系统分享

Android利用ZXing生成带LOGO的二维码图片_第1张图片

二、实现

1、通过Bitmap合成logo与白色背景,我们来看一下白色背景图和logo图:

Android利用ZXing生成带LOGO的二维码图片_第2张图片 Android利用ZXing生成带LOGO的二维码图片_第3张图片

咳咳咳~请原谅我盗用郭霖博客上婚纱照头像,我表示单身狗受到了一万点暴击伤害……白色背景图犹豫全白的无法显示,于是截了灰色的边框突出以下。我们来看看合成后的图:

Android利用ZXing生成带LOGO的二维码图片_第4张图片

我们来看下是怎么合成的吧:

**
 * 
 * 项目名称 : ZXingScanQRCode
* 创建人 : skycracks
* 创建时间 : 2016-4-19下午9:53:29
* 版本 : [v1.0]
* 类描述 : LOGO图片加上白色背景图片
*/ public class LogoConfig { /** * @return 返回带有白色背景框logo */ public Bitmap modifyLogo(Bitmap bgBitmap, Bitmap logoBitmap) { int bgWidth = bgBitmap.getWidth(); int bgHeigh = bgBitmap.getHeight(); //通过ThumbnailUtils压缩原图片,并指定宽高为背景图的3/4 logoBitmap = ThumbnailUtils.extractThumbnail(logoBitmap,bgWidth*3/4, bgHeigh*3/4, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); Bitmap cvBitmap = Bitmap.createBitmap(bgWidth, bgHeigh, Config.ARGB_8888); Canvas canvas = new Canvas(cvBitmap); // 开始合成图片 canvas.drawBitmap(bgBitmap, 0, 0, null); canvas.drawBitmap(logoBitmap,(bgWidth - logoBitmap.getWidth()) /2,(bgHeigh - logoBitmap.getHeight()) / 2, null); canvas.save(Canvas.ALL_SAVE_FLAG);// 保存 canvas.restore(); if(cvBitmap.isRecycled()){ cvBitmap.recycle(); } return cvBitmap; } }

因为我使用的白色背景图比较小,因此代码中以白色背景图得到的bitmap创建画布Canvas并且缩放logo图片。可以看到通过ThumbnailUtils.extractThumbnail(logoBitmap,bgWidth*3/4,bgHeigh*3/4, ThumbnailUtils.OPTIONS_RECYCLE_INPUT)将logo宽高缩放为白色背景图的3/4,我认为做好的比例。图片合成其实不难,主要通过Canvas 这个类中的方法实现,想要详细了解的同学请自行百度(以后可能会整理图片合成写一篇博文)。

2、二维码矩阵与一维像素相互转换(将logo转成像素放入二维码中间)
生成带logo二维码的方法:

/**
     * 黑点颜色
     */
    private static final int BLACK = 0xFF000000;
    /**
     * 白色
     */
    private static final int WHITE = 0xFFFFFFFF; 
    /**
     * 正方形二维码宽度
     */
    private static final int CODE_WIDTH = 440;
    /**
     * LOGO宽度值,最大不能大于二维码20%宽度值,大于可能会导致二维码信息失效
     */
    private static final int LOGO_WIDTH_MAX = CODE_WIDTH / 5;
    /**
     *LOGO宽度值,最小不能小于二维码10%宽度值,小于影响Logo与二维码的整体搭配
     */
    private static final int LOGO_WIDTH_MIN = CODE_WIDTH / 10;
    /**
     * 生成带LOGO的二维码
     */

    public Bitmap createCode(String content, Bitmap logoBitmap)
            throws WriterException {
        int logoWidth = logoBitmap.getWidth();
        int logoHeight = logoBitmap.getHeight();
        int logoHaleWidth = logoWidth >= CODE_WIDTH ? LOGO_WIDTH_MIN
                : LOGO_WIDTH_MAX;
        int logoHaleHeight = logoHeight >= CODE_WIDTH ? LOGO_WIDTH_MIN
                : LOGO_WIDTH_MAX;
        // 将logo图片按martix设置的信息缩放
        Matrix m = new Matrix();
        /*
         * 给的源码是,由于CSDN上传的资源不能改动,这里注意改一下
         * float sx = (float) 2*logoHaleWidth / logoWidth;
         * float sy = (float) 2*logoHaleHeight / logoHeight;
         */
        float sx = (float) logoHaleWidth / logoWidth;
        float sy = (float) logoHaleHeight / logoHeight;
        m.setScale(sx, sy);// 设置缩放信息
        Bitmap newLogoBitmap = Bitmap.createBitmap(logoBitmap, 0, 0, logoWidth,
                logoHeight, m, false);
        int newLogoWidth = newLogoBitmap.getWidth();
        int newLogoHeight = newLogoBitmap.getHeight();
        Hashtable hints = new Hashtable();
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);//设置容错级别,H为最高
        hints.put(EncodeHintType.MAX_SIZE, LOGO_WIDTH_MAX);// 设置图片的最大值
        hints.put(EncodeHintType.MIN_SIZE, LOGO_WIDTH_MIN);// 设置图片的最小值
        hints.put(EncodeHintType.MARGIN, 2);//设置白色边距值
        // 生成二维矩阵,编码时指定大小,不要生成了图片以后再进行缩放,这样会模糊导致识别失败
        BitMatrix matrix = new MultiFormatWriter().encode(content,
                BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_WIDTH, hints);
        int width = matrix.getWidth();
        int height = matrix.getHeight();
        int halfW = width / 2;
        int halfH = height / 2;
        // 二维矩阵转为一维像素数组,也就是一直横着排了
        int[] pixels = new int[width * height];
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
            /*
                 * 取值范围,可以画图理解下  
                 * halfW + newLogoWidth / 2 - (halfW - newLogoWidth / 2) = newLogoWidth
                 * halfH + newLogoHeight / 2 - (halfH - newLogoHeight) = newLogoHeight
                 */
                if (x > halfW - newLogoWidth / 2&& x < halfW + newLogoWidth / 2
                        && y > halfH - newLogoHeight / 2 && y < halfH + newLogoHeight / 2) {// 该位置用于存放图片信息
                    /*
                     *  记录图片每个像素信息
                     *  halfW - newLogoWidth / 2 < x < halfW + newLogoWidth / 2 
                     *  --> 0 < x - halfW + newLogoWidth / 2 < newLogoWidth
                     *   halfH - newLogoHeight / 2  < y < halfH + newLogoHeight / 2
                     *   -->0 < y - halfH + newLogoHeight / 2 < newLogoHeight
                     *   刚好取值newLogoBitmap。getPixel(0-newLogoWidth,0-newLogoHeight);
                     */
                    pixels[y * width + x] = newLogoBitmap.getPixel(
                            x - halfW + newLogoWidth / 2, y - halfH + newLogoHeight / 2);
                } else {
                    pixels[y * width + x] = matrix.get(x, y) ? BLACK: WHITE;// 设置信息
                }
            }
        }
        Bitmap bitmap = Bitmap.createBitmap(width, height,
                Bitmap.Config.ARGB_8888);
        // 通过像素数组生成bitmap,具体参考api
        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
        return bitmap;
    }

在实际操作当中我们的logo会遮挡中间部分的二维码,可能导致二维码失效,这个不用太担心,二维码识别有容错级别的,我们将容错级别设置为最大hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H),但是logo过大遮挡住关键信息还是会导致二维码失效,因此我们需要控制logo在二维码中占得比例,通过常量LOGO_WIDTH_MAX最大为二维码宽度的20%、LOGO_WIDTH_MIN最小为二维码宽度的10%,具体大小还是以缩放后的logo宽高为准。然后最关键的是怎么将带白色边框的logo放入二维码中间,在代码中可以看到通过双重for循环来将二维矩阵转换为一维像素数组,注释比较详细,就不多讲了。

3、调用系统分享

Intent shareIntent = new Intent();
                shareIntent.setAction(Intent.ACTION_SEND);
                shareIntent.putExtra(Intent.EXTRA_STREAM, imageFileUri);
                shareIntent.setType("image/*");
//自定义提示语                startActivity(Intent.createChooser(shareIntent, "分享到"));

从调用系统分享图片的代码可以看出我们需要先存储生成的二维码图片,通过saveBitmap(Bitmap bitmap, String bitName)保存图片。

    /**
     * 生成的二维码图片存储的URI
     */

    private Uri imageFileUri;
    /**
     * 保存生成的二维码图片
     */

    private void saveBitmap(Bitmap bitmap, String bitName){
        //获取与应用相关联的路径
        String imageFilePath = getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath();
        File imageFile = new File(imageFilePath,"/" + bitName);// 通过路径创建保存文件
        imageFileUri = Uri.fromFile(imageFile);
        if (imageFile.exists()) {
            imageFile.delete();
        }
        FileOutputStream out;
        try {
            out = new FileOutputStream(imageFile);
            if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)) {
                out.flush();
                out.close();
            }
        } catch (Exception e) {
        }
    }

好了,模拟器没有QQ之类的,调用系统分享或其他具体操作请下载demo演示

We have to be greater than what we suffer.

上源码

你可能感兴趣的:(二维码)