安卓等間採樣縮放算法的實現與解決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博客地址:

這裏寫圖片描述]![這裏寫圖片描述

額,混入了一個一維碼,條形碼雖然放大了那麼多倍,但是估計也沒人掃的出,太密集了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章