安卓等间采样缩放算法的实现与解决ZXing生成DATA MATRIX二维码太小的问题

本文相关代码:https://gitee.com/mingyueyixi/ZxingLibraryTest

按我的上一篇文章所述,修改生成二维码的方法后,成功生成了DATA MATRIX格式的二维码,然而,这个二维码实在太小,以至于竟然看不见,堪比小芝麻。而Bitmap.createScaleBitmap()方法放大的图片又十分模糊,无法使用。

于是,寻找了等间采样算法来进行缩放。关于这个算法缩放的图片,其原理、优缺点自行研究,闲言少叙,以下是Android平台下实现等间采样缩放算法的代码:

BitmapFlex.java

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.util.Log;

/**
 * @author Yue
 * @date 2017/8/9 13:28
 * 此类中的方法已通过测试。主要用于等间采样缩放。等间采样缩放在单一色彩的图中表现较好,不会产生模糊,
 * 可用于放大 DATA_MATRIX 二维码。
 */
public class BitmapFlex {

    /**
     * 等间隔采样的图像缩放
     * @param bitmap     要缩放的图像对象
     * @param dstWidth   缩放后图像的宽
     * @param dstHeight 缩放后图像的高
     * @return 返回处理后的图像对象
     */
    public static Bitmap flex(Bitmap bitmap, int dstWidth, int dstHeight) {
        float wScale = (float) dstWidth / bitmap.getWidth();
        float hScale = (float) dstHeight / bitmap.getHeight();
        return flex(bitmap, wScale, hScale);
    }

    /**
     * 等间隔采样的图像缩放
     * @param bitmap 要缩放的bitap对象
     * @param wScale 要缩放的横列(宽)比列
     * @param hScale 要缩放的纵行(高)比列
     * @return 返回处理后的图像对象
     */
    public static Bitmap flex(Bitmap bitmap, float wScale, float hScale) {
        if (wScale <= 0 || hScale <= 0){
            return null;
        }
        float ii = 1 / wScale;    //采样的行间距
        float jj = 1 / hScale; //采样的列间距

        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int dstWidth = (int) (wScale * width);
        int dstHeight = (int) (hScale * height);

        int[] pixels = new int[width * height];
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

        int[] dstPixels = new int[dstWidth * dstHeight];

        for (int j = 0; j < dstHeight; j++) {
            for (int i = 0; i < dstWidth; i++) {
                dstPixels[j * dstWidth + i] = pixels[(int) (jj * j) * width + (int) (ii * i)];
            }
        }
        System.out.println((int) ((dstWidth - 1) * ii));
        Log.d(">>>",""+"dstPixels:"+dstWidth+" x "+dstHeight);

        Bitmap outBitmap = Bitmap.createBitmap(dstWidth, dstHeight, Config.ARGB_8888);
        outBitmap.setPixels(dstPixels, 0, dstWidth, 0, 0, dstWidth, dstHeight);

        return outBitmap;
    }
}

接着,用它来缩放data matrix、pdf417格式的二维码。pdf417格式的二维码好生奇葩,好多时候是长方形的。


package banding.com.google.Zxing.utils;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.text.TextUtils;
import android.util.Log;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.NotFoundException;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.aztec.encoder.Encoder;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import java.util.HashMap;
import java.util.Map;

/**
 * 条码码操作类。生成时需要特别注意,条码格式对字符编码有要求,有的不支持中文、字母等,需要依据格式额外处理。
 * 注意,配置:
 * <p> {@link BarcodeFormat#RSS_EXPANDED,BarcodeFormat#RSS_14,BarcodeFormat#UPC_EAN_EXTENSION}
 * 
 * 以及code 93 由于某些原因生成失败,暂未排查完毕。
 * 
 */
public class BarcodeUtils {

    /**
     * 默认前景色黑色,背景色透明
     * 生成一二维码
     * @param content 二维码文字内容
     * @param width   图片像素宽
     * @param height  图片像素高
     * @param format  二维码图片的编码方式
     * @return 二维码bitmap
     */
    public static Bitmap createBarcode(String content, int width, int height, BarcodeFormat format) {
        return createBarcode(content, width, height, format, 0xff000000, 0x00000000);
    }
    /**
     * 生成二维码(部分一维码不能使用此方法)
     * @param content   二维码文字内容
     * @param width     图片像素宽
     * @param height    图片像素高
     * @param format    二维码图片的编码方式
     * @param foreColor 前景色
     * @param backColor 背景色
     * @return Bitmap对象
     */
    public static Bitmap createBarcode(String content, int width, int height, BarcodeFormat format, int foreColor, int backColor) {
        return createBarcode(content, "utf-8", width, height, format, foreColor, backColor);
    }

    /**
     * 生成一二维码
     * @param content     二维码文字内容
     * @param charset 字符编码
     * @param width       图片像素宽
     * @param height      图片像素高
     * @param format      二维码图片的编码方式
     * @param foreColor   前景色
     * @param backColor   背景色
     * @return Bitmap对象
     */
    public static Bitmap createBarcode(String content, String charset, int width, int height, BarcodeFormat format, int foreColor, int backColor) {
        if (TextUtils.isEmpty(content)) {
            return null;
        }
        if (TextUtils.isEmpty(charset)){
            charset = "utf-8";
        }
        //配置参数
        HashMap<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
        hints.put(EncodeHintType.CHARACTER_SET, charset);//字符串编码

        //错误纠正,Aztec格式,pdf417格式和其它条码配置不同,不能通用,否则会产生错误。
        if (format == BarcodeFormat.AZTEC) {//错误校正词的最小百分比
            hints.put(EncodeHintType.ERROR_CORRECTION, Encoder.DEFAULT_EC_PERCENT);//默认,可以不设
        } else if (format == BarcodeFormat.PDF_417) {
            hints.put(EncodeHintType.ERROR_CORRECTION, 2);//纠错级别,允许为0到8。默认2,可以不设
        } else {
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);//
        }
        //设置空白边距的宽度,默认值为4
        hints.put(EncodeHintType.MARGIN, 0);

        return createBarcode(content, width, height, format, hints, foreColor, backColor);
    }

    /**
     * 生成一二维码。
     * @param content   文字内容
     * @param format    条码编码方式
     * @param hints     条码其他配置对象
     * @param foreColor 条码前景色
     * @param backColor 条码背景色
     * @return bitmap 条码bitmap对象
     */
    public static Bitmap createBarcode(String content, int width, int height, BarcodeFormat format, HashMap<EncodeHintType, Object> hints, int foreColor, int backColor) {
        return createBarcode(content, width, height, format, hints, foreColor, backColor, true);
    }

    /**
     * QR二维码生成
     *
     * @param content
     * @param widthAndHeight
     * @return 二维码bitmap
     */
    public static Bitmap createQRCode(String content, int widthAndHeight) {
        return createBarcode(content, widthAndHeight, widthAndHeight, BarcodeFormat.QR_CODE);
    }

    /**
     * 128条形码
     *
     * @param content
     * @param width
     * @param height
     * @return 一维码bitmap
     */
    public static Bitmap create128Code(String content, int width, int height) {
        return createBarcode(content, width, height, BarcodeFormat.CODE_128);
    }

    /**
     * 条形码生成方法
     *
     * @param content     文字内容
     * @param width       宽
     * @param height      高
     * @param format      条形码格式,pdf417,code128,QRcode等
     * @param hints       条码的Map配置
     * @param foreColor   前景色
     * @param backColor   背景色
     * @param fitSizeFlag 是否自动适应到预期的宽高(data matrix格式的二维码,生成时非常小,需要放大,其他格式也可能不到预期的大小)
     *                    <p> true,使用等间采样算法,缩放bitmap到期望的尺寸
     *                    <p> false则不处理
     * @return 条码
     */
    public static Bitmap createBarcode(String content, int width, int height, BarcodeFormat format,
                                       Map<EncodeHintType, Object> hints, int foreColor, int backColor, boolean fitSizeFlag) {
        // 图像数据转换,使用了矩阵转换
        BitMatrix bitMatrix = null;
        try {
            MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
            bitMatrix = multiFormatWriter.encode(content, format, width, height, hints);

        } catch (WriterException e) {
            e.printStackTrace();
        }
        if (bitMatrix == null) {
            return null;
        }
        //注,以下代码修正DATA_MATRIX和PDF_417生成错误。
        //网络上广泛使用预设的width和height生成像素矩阵,因这两种格式的二维码,其bitMatrix不一定等于预期的像素矩阵(width * height)
        //会导致数组下标异常
        int bitWidth = bitMatrix.getWidth();
        int bitHeight = bitMatrix.getHeight();

        int[] pixels = new int[bitWidth * bitHeight];

        //遍历bitmatrix,为像素矩阵按一行行(横列)设置像素颜色。
        for (int y = 0; y < bitHeight; y++) {
            for (int x = 0; x < bitWidth; x++) {
                if (bitMatrix.get(x, y)) {
                    pixels[y * bitWidth + x] = foreColor;
                } else {
                    pixels[y * bitWidth + x] = backColor;
                }
            }
        }

        Bitmap bitmap = Bitmap.createBitmap(bitWidth, bitHeight, Bitmap.Config.ARGB_8888);
        bitmap.setPixels(pixels, 0, bitWidth, 0, 0, bitWidth, bitHeight);

        Log.d(">>>", "预期宽高:" + width + " * " + height + "  矩阵宽高:" + bitWidth + " * " + bitHeight);
        if (fitSizeFlag) {
            //因为bitmap可能并不等于预先设置的width和height,需要进行等比缩放,尤其是BarcodeFormat.DATA_MATRIX格式,小的不可想象
            float wMultiple = ((float) bitWidth) / (float) width;//生成的bitmap的宽除以预期的宽
            float hMultiple = ((float) bitHeight) / (float) height;//生成的bitmap的高除以预期的高
            Log.d(">>>", wMultiple + "--" + hMultiple);
            if (wMultiple == 1f || hMultiple == 1f) {//说明生成的条形码符合预期,不需要缩放
                Log.d(">>>", "...re1");
                return bitmap;
            }

            if (wMultiple > hMultiple) {//说明宽超出范围更多,以宽的比例为标准进行缩放。
                int dstWidth = width;// bitWidth / wMultiple
                int dstHeight = (int) (bitHeight / wMultiple);
//          bitmap = Bitmap.createScaledBitmap(bitmap,dstWidth,dstHeight,true);//安卓的这个方法不行

                bitmap = BitmapFlex.flex(bitmap, dstWidth, dstHeight);//等间采样算法进行缩放
            } else {//说明相当或高超出范围更多,以高的比例为标准进行缩放。
                int dstHeight = height;// bitHeight / hMultiple
                int dstWidth = (int) (bitWidth / hMultiple);

                bitmap = BitmapFlex.flex(bitmap, dstWidth, dstHeight);//等间采样算法进行缩放
            }
        }
        return bitmap;
    }

    /**
     * 一、二维码的解析
     * @param bitmap 图片对象
     * @param charaterset 编码。以此文字编码解析一二维码文字内容
     * @return 解析结果
     */
    public static Result parseCode(Bitmap bitmap, String charaterset) {
        Map<DecodeHintType, String> hints = new HashMap<DecodeHintType, String>();
        hints.put(DecodeHintType.CHARACTER_SET, charaterset);
        return parseBarCode(bitmap,hints);
    }

    /**
     * 一二维码的解析
     * @param bitmap 图片对象
     * @param hints 解码map配置
     * @return 解析结果
     */
    public static Result parseBarCode(Bitmap bitmap,Map<DecodeHintType,?> hints){
        MultiFormatReader formatReader = new MultiFormatReader();

        int bw = bitmap.getWidth();
        int bh = bitmap.getHeight();
        int[] pixels = new int[bw * bh];
        bitmap.getPixels(pixels, 0, bw, 0, 0, bw, bh);
        LuminanceSource source = new RGBLuminanceSource(bw, bh, pixels);
        Binarizer binarizer = new HybridBinarizer(source);
        BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);

        Result result = null;
        try {
            result = formatReader.decode(binaryBitmap, hints);
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 在bitma中间添加Logo图案(用于QR二维码等场合)
     * @param src 原图
     * @param logo logo图
     */
    public static void drawLogo(Bitmap src, Bitmap logo) {
        if (src == null) {
            return;
        }

        if (logo == null) {
            return;
        }

        //获取图片的宽高
        int srcWidth = src.getWidth();
        int srcHeight = src.getHeight();
        int logoWidth = logo.getWidth();
        int logoHeight = logo.getHeight();

        if (srcWidth == 0 || srcHeight == 0) {
            return;
        }
        if (logoWidth == 0 || logoHeight == 0) {
            return;
        }
        //logo大小为二维码整体大小的1/5
        float scaleFactor = srcWidth * 1.0f / 5 / logoWidth;
        //      Bitmap bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);
        try {
            Canvas canvas = new Canvas(src);
            canvas.drawBitmap(src, 0, 0, null);
            canvas.scale(scaleFactor, scaleFactor, srcWidth / 2, srcHeight / 2);
            canvas.drawBitmap(logo, (srcWidth - logoWidth) / 2, (srcHeight - logoHeight) / 2, null);

            canvas.save(Canvas.ALL_SAVE_FLAG);
            canvas.restore();
        } catch (Exception e) {
            e.getStackTrace();
        }

    }
}

注:

RSS_EXPANDED
RSS_14
BarcodeFormat
UPC_EAN_EXTENSION
UPCE_E
以及CODE_93
由于某些原因生成失败,暂未排查完毕。(其中有部分是扩展的条形码,可能需要自己写相关代码吧。)

上诉修改过后的代码,支持正常生成ZXing的所有二维码格式。

最后,利用这个工具类生成二维码。

图片像素很大,可以在新建标签中打开查看。

本文地址:

这里写图片描述

我的csdn博客地址:

这里写图片描述]![这里写图片描述

额,混入了一个一维码,条形码虽然放大了那么多倍,但是估计也没人扫的出,太密集了。

发布了105 篇原创文章 · 获赞 70 · 访问量 34万+

猜你喜欢

转载自blog.csdn.net/Mingyueyixi/article/details/77102884