Android利用zxing生成二維碼


感謝大佬:https://blog.csdn.net/mountain_hua/article/details/80646089

**gayhub上的zxing可用於生成二維碼,識別二維碼
gayhub地址:https://github.com/zxing/zxing
此文只是簡易教程,文末附有完整代碼和demo下載地址,進入正題:

(1)下載並導入zxing.jar包

下載:
zxing.jar下載地址,只需要1積分,方便大家學習下載。
把下載好的zxing.jar放在app的libs文件夾內,如圖**
在這里插入圖片描述

導入:

進入project structure.如圖
在這里插入圖片描述
點進去之后,依次進入app——Dependencies——jar dependency:
在這里插入圖片描述
選擇zxing.jar,導入
在這里插入圖片描述

(2)生成二維碼:

生成二維碼的函數:

public void createQRcodeImage(String url)
    {
        im1=(ImageView)findViewById(R.id.imageView);
        w=im1.getWidth();
        h=im1.getHeight();
        try
        {
            //判斷URL合法性
            if (url == null || "".equals(url) || url.length() < 1)
            {
                return;
            }
            Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
            //圖像數據轉換,使用了矩陣轉換
            BitMatrix bitMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, w, h, hints);
            int[] pixels = new int[w * h];
            //下面這里按照二維碼的算法,逐個生成二維碼的圖片,
            //兩個for循環是圖片橫列掃描的結果
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    if (bitMatrix.get(x, y))
                    {
                        pixels[y * w + x] = 0xff000000;
                    }
                    else
                    {
                        pixels[y * w + x] = 0xffffffff;
                    }
                }
            }
            //生成二維碼圖片的格式,使用ARGB_8888
            Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            bitmap.setPixels(pixels, 0, w, 0, 0, w, h);
            //顯示到我們的ImageView上面
            im1.setImageBitmap(bitmap);
        }
        catch (WriterException e)
        {
            e.printStackTrace();
        }
    }

設置兩種轉換方式,默認轉換/自定義轉換:

        Button bt=(Button)findViewById(R.id.button);
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                createQRcodeImage("https://blog.csdn.net/mountain_hua");//url為我的csdn博客地址
            }
        });
 
        Button bt2=(Button)findViewById(R.id.button2);
        bt2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EditText editText=(EditText)findViewById(R.id.editText);
                createQRcodeImage(editText.getText().toString());//自定義轉換內容
            }
        });

默認轉換為我的博客: 自定義轉換:
在這里插入圖片描述在這里插入圖片描述

(3)識別二維碼

識別二維碼需要一個RGBLuminanceSource類:

//識別圖片所需要的RGBLuminanceSource類
    public class RGBLuminanceSource extends LuminanceSource {
 
        private byte bitmapPixels[];
 
        protected RGBLuminanceSource(Bitmap bitmap) {
            super(bitmap.getWidth(), bitmap.getHeight());
 
            // 首先,要取得該圖片的像素數組內容
            int[] data = new int[bitmap.getWidth() * bitmap.getHeight()];
            this.bitmapPixels = new byte[bitmap.getWidth() * bitmap.getHeight()];
            bitmap.getPixels(data, 0, getWidth(), 0, 0, getWidth(), getHeight());
 
            // 將int數組轉換為byte數組,也就是取像素值中藍色值部分作為辨析內容
            for (int i = 0; i < data.length; i++) {
                this.bitmapPixels[i] = (byte) data[i];
            }
        }
 
        @Override
        public byte[] getMatrix() {
            // 返回我們生成好的像素數據
            return bitmapPixels;
        }
 
        @Override
        public byte[] getRow(int y, byte[] row) {
            // 這里要得到指定行的像素數據
            System.arraycopy(bitmapPixels, y * getWidth(), row, 0, getWidth());
            return row;
        }
    }
 

識別二維碼的函數:

     //識別二維碼的函數
    public void recogQRcode(ImageView imageView){
        Bitmap QRbmp = ((BitmapDrawable) (imageView).getDrawable()).getBitmap();   //將圖片bitmap化
        int width = QRbmp.getWidth();
        int height = QRbmp.getHeight();
        int[] data = new int[width * height];
        QRbmp.getPixels(data, 0, width, 0, 0, width, height);    //得到像素
        RGBLuminanceSource source = new RGBLuminanceSource(QRbmp);   //RGBLuminanceSource對象
        BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
        QRCodeReader reader = new QRCodeReader();
        Result re = null;
        try {
            //得到結果
            re = reader.decode(bitmap1);
        } catch (NotFoundException e) {
            e.printStackTrace();
        } catch (ChecksumException e) {
            e.printStackTrace();
        } catch (FormatException e) {
            e.printStackTrace();
        }
        //Toast出內容
        Toast.makeText(MainActivity.this,re.getText(),Toast.LENGTH_SHORT).show();
 
        //利用正則表達式判斷內容是否是URL,是的話則打開網頁
        String regex = "(((https|http)?://)?([a-z0-9]+[.])|(www.))"
                + "\\w+[.|\\/]([a-z0-9]{0,})?[[.]([a-z0-9]{0,})]+((/[\\S&&[^,;\u4E00-\u9FA5]]+)+)?([.][a-z0-9]{0,}+|/?)";//設置正則表達式
 
        Pattern pat = Pattern.compile(regex.trim());//比對
        Matcher mat = pat.matcher(re.getText().trim());
        if (mat.matches()){
            Uri uri = Uri.parse(re.getText());
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);//打開瀏覽器
            startActivity(intent);
        }
 
    }

下面看識別效果:

這是識別URL的結果: 這是識別一般文字的結果:
在這里插入圖片描述在這里插入圖片描述

(4)完整代碼:

Mainactivity:

package mountain_hua.learn_zxing;
 
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
 
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.EncodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.LuminanceSource;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.QRCodeWriter;
 
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
public class MainActivity extends AppCompatActivity {
 
    private ImageView im1;  //imageview圖片
    private int w,h;        //圖片寬度w,高度h
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        Button bt=(Button)findViewById(R.id.button);
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                createQRcodeImage("https://blog.csdn.net/mountain_hua");//url為我的csdn博客地址
            }
        });
 
        Button bt2=(Button)findViewById(R.id.button2);
        bt2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EditText editText=(EditText)findViewById(R.id.editText);
                createQRcodeImage(editText.getText().toString());//自定義轉換內容
            }
        });
 
        Button bt3=(Button)findViewById(R.id.button3);
        bt3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                im1=(ImageView)findViewById(R.id.imageView);
                recogQRcode(im1);
            }
        });
 
    }
 
    //轉換成二維碼QRcode的函數。參數為一個字符串
    public void createQRcodeImage(String url)
    {
        im1=(ImageView)findViewById(R.id.imageView);
        w=im1.getWidth();
        h=im1.getHeight();
        try
        {
            //判斷URL合法性
            if (url == null || "".equals(url) || url.length() < 1)
            {
                return;
            }
            Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();
            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
            //圖像數據轉換,使用了矩陣轉換
            BitMatrix bitMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, w, h, hints);
            int[] pixels = new int[w * h];
            //下面這里按照二維碼的算法,逐個生成二維碼的圖片,
            //兩個for循環是圖片橫列掃描的結果
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    if (bitMatrix.get(x, y))
                    {
                        pixels[y * w + x] = 0xff000000;
                    }
                    else
                    {
                        pixels[y * w + x] = 0xffffffff;
                    }
                }
            }
            //生成二維碼圖片的格式,使用ARGB_8888
            Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            bitmap.setPixels(pixels, 0, w, 0, 0, w, h);
            //顯示到我們的ImageView上面
            im1.setImageBitmap(bitmap);
        }
        catch (WriterException e)
        {
            e.printStackTrace();
        }
    }
 
    //識別二維碼的函數
    public void recogQRcode(ImageView imageView){
        Bitmap QRbmp = ((BitmapDrawable) (imageView).getDrawable()).getBitmap();   //將圖片bitmap化
        int width = QRbmp.getWidth();
        int height = QRbmp.getHeight();
        int[] data = new int[width * height];
        QRbmp.getPixels(data, 0, width, 0, 0, width, height);    //得到像素
        RGBLuminanceSource source = new RGBLuminanceSource(QRbmp);   //RGBLuminanceSource對象
        BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
        QRCodeReader reader = new QRCodeReader();
        Result re = null;
        try {
            //得到結果
            re = reader.decode(bitmap1);
        } catch (NotFoundException e) {
            e.printStackTrace();
        } catch (ChecksumException e) {
            e.printStackTrace();
        } catch (FormatException e) {
            e.printStackTrace();
        }
        //Toast出內容
        Toast.makeText(MainActivity.this,re.getText(),Toast.LENGTH_SHORT).show();
 
        //利用正則表達式判斷內容是否是URL,是的話則打開網頁
        String regex = "(((https|http)?://)?([a-z0-9]+[.])|(www.))"
                + "\\w+[.|\\/]([a-z0-9]{0,})?[[.]([a-z0-9]{0,})]+((/[\\S&&[^,;\u4E00-\u9FA5]]+)+)?([.][a-z0-9]{0,}+|/?)";//設置正則表達式
 
        Pattern pat = Pattern.compile(regex.trim());//比對
        Matcher mat = pat.matcher(re.getText().trim());
        if (mat.matches()){
            Uri uri = Uri.parse(re.getText());
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);//打開瀏覽器
            startActivity(intent);
        }
 
    }
 
    //識別圖片所需要的RGBLuminanceSource類
    public class RGBLuminanceSource extends LuminanceSource {
 
        private byte bitmapPixels[];
 
        protected RGBLuminanceSource(Bitmap bitmap) {
            super(bitmap.getWidth(), bitmap.getHeight());
 
            // 首先,要取得該圖片的像素數組內容
            int[] data = new int[bitmap.getWidth() * bitmap.getHeight()];
            this.bitmapPixels = new byte[bitmap.getWidth() * bitmap.getHeight()];
            bitmap.getPixels(data, 0, getWidth(), 0, 0, getWidth(), getHeight());
 
            // 將int數組轉換為byte數組,也就是取像素值中藍色值部分作為辨析內容
            for (int i = 0; i < data.length; i++) {
                this.bitmapPixels[i] = (byte) data[i];
            }
        }
 
        @Override
        public byte[] getMatrix() {
            // 返回我們生成好的像素數據
            return bitmapPixels;
        }
 
        @Override
        public byte[] getRow(int y, byte[] row) {
            // 這里要得到指定行的像素數據
            System.arraycopy(bitmapPixels, y * getWidth(), row, 0, getWidth());
            return row;
        }
    }
 
}

布局文件activity_main:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
 
    <ImageView android:id="@+id/imageView" android:layout_width="300dp" android:layout_height="300dp" app:srcCompat="@android:color/background_light" />
 
    <Button android:id="@+id/button" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAllCaps="false" android:text="轉換成二維碼(mountain_hua的博客)" />
 
    <EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="輸入要轉換的內容" android:inputType="textPersonName" />
 
    <Button android:id="@+id/button2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="自定義轉換" />
 
    <Button android:id="@+id/button3" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="識別二維碼" />
</LinearLayout>

demo下載地址:https://download.csdn.net/download/mountain_hua/10471127
本文參考:
https://www.jianshu.com/p/20db116b6279
https://blog.csdn.net/qq_29634351/article/details/78688315
http://www.cnblogs.com/mythou/p/3280023.html
https://www.2cto.com/kf/201603/495847.html

續:在二維碼中間填充圖片logo

首先說明,二維碼是有一定的糾錯功能的,二維條碼因穿孔、污損等引起局部損壞時,照樣可以正確得到識讀,損毀面積達30%仍可恢復信息。但三個角上的“回”字及周圍的底色不要改變,這是用於二維碼定位的,最好是填充圖片在中間,越小越好。

下面進入正題:

填充圖片函數:

    //給二維碼添加圖片
    //第一個參數為原二維碼,第二個參數為添加的logo
    private static Bitmap addLogo(Bitmap src, Bitmap logo) {
        //如果原二維碼為空,返回空
        if (src ==null ) {
            return null;
        }
        //如果logo為空,返回原二維碼
        if (src ==null ||logo ==null) {
            return src;
        }
 
        //這里得到原二維碼bitmap的數據
        int srcWidth = src.getWidth();
        int srcHeight = src.getHeight();
        //logo的Width和Height
        int logoWidth = logo.getWidth();
        int logoHeight = logo.getHeight();
 
        //同樣如果為空,返回空
        if (srcWidth == 0 || srcHeight == 0) {
            return null;
        }
        //同樣logo大小為0,返回原二維碼
        if (logoWidth == 0 || logoHeight == 0) {
            return src;
        }
 
        //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(bitmap);
            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) {
            bitmap = null;
            e.getStackTrace();
        }
 
        return bitmap;
    }

然后在button中設置監聽:

Button bt4=(Button)findViewById(R.id.button4);
        bt4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                im1=(ImageView)findViewById(R.id.imageView);
                Bitmap QRbmp = ((BitmapDrawable) (im1).getDrawable()).getBitmap();   //將圖片bitmap化
                //將drawable里面的圖片bitmap化
                Bitmap logo = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
                im1.setImageBitmap(addLogo(QRbmp,logo));
            }
        });

效果如下:
在這里插入圖片描述在這里插入圖片描述
附完整版demo(帶填充圖片):https://download.csdn.net/download/mountain_hua/10473011


小白補充:
https://www.jianshu.com/p/6607e69b1121
https://www.jb51.net/article/102494.htm


感謝大佬:https://www.jianshu.com/p/6607e69b1121

ZXing應用詳解

現在的項目中需要加上二維碼掃描,雖然使用了第三方庫,也還好用,但是對這部分只是還是比較感興趣,所以研究一下。

分類

二維碼處理分為兩部分:編碼與解碼

編碼:使用字符串生成圖片。

解碼:解析圖片中的字符串。

首先明確一個概念:二維碼圖片存在的形式非常多,文件、紙張、手機、電腦屏幕。在不同的平台上存在的形式是不一樣的。

ZXing介紹

摘自百度百科

二維條碼/二維碼(2-dimensional bar code)是用某種特定的幾何圖形按一定規律在平面(二維方向上)分布的黑白相間的圖形記錄數據符號信息的;在代碼編制上巧妙地利用構成計算機內部邏輯基礎的“0”、“1”比特流的概念,使用若干個與二進制相對應的幾何形體來表示文字數值信息,通過圖象輸入設備或光電掃描設備自動識讀以實現信息自動處理:它具有條碼技術的一些共性:每種碼制有其特定的字符集;每個字符占有一定的寬度;具有一定的校驗功能等。同時還具有對不同行的信息自動識別功能、及處理圖形旋轉變化點。

目前的認知告訴我們,二維碼是以正方形的形式存在,以類似於二進制的方式存儲數據。

在Zxing中,使用BitMatrix來描述一個二維碼,在其內部存儲一個看似boolean值的矩陣數組。這個類很好的抽象了二維碼。

轉換成圖片

只使用zxing-core包,那么我們最多可以得到一個BitMatrix, 我們想要看見二維碼,則還需要將其轉換成一個圖片,而圖片在不同的平台則是以不同的形式存在的。如png文件, jpg文件、Android的Bitmap, Java SE的 BufferedImage.

具體轉換成圖片的方式,不同平台有不同的方法,后面會詳細總結,這里只是盡快明確一下概念。

生成二維碼介紹

zxing將生成圖形編碼的方式抽象成了一個類com.google.zxing.Writer, 在實現類中不僅僅生成二維碼,還可以生成條形碼等其他圖形編碼

Writer
BitMatrix encode(String contents, BarcodeFormat format, int width, int height) throws WriterException
BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Map<EncodeHintType,?> hints) throws WriterException;

如上所示,Writer共有兩個方法,都是用於生成二維碼。方法參數說明如下

參數 說明
String contents 編碼的內容
BarcodeFormat format 編碼的方式(二維碼、條形碼...)
int width 首選的寬度
int height 首選的高度
Map<EncodeHintType,?> hints 編碼時的額外參數

從上面可以看出,除了我們常規認為的編碼需要內容之外,還有其他不少的信息,如編碼的方式(這里只探討二維碼),二維碼的首選寬高(首選的意思是:生成的圖片的參考尺寸,如二維碼是正方形,但給一個矩形,則會留白,條形碼為矩形,設置一個正方形,則也會留白)。

下面詳細討論一下額外的參數,雖然不一定所有都用到,但是盡量討論一些可能會用到的參數。編碼額外的參數是以一個Map<EncodeHintType, ?>存在的,key為EncodeHintType枚舉,那么可以看到所有的參數類型。

參數 說明
ERROR_CORRECTION 容錯率,指定容錯等級,例如二維碼中使用的ErrorCorrectionLevel, Aztec使用Integer
CHARACTER_SET 編碼集
DATA_MATRIX_SHAPE 指定生成的數據矩陣的形狀,類型為SymbolShapeHint
MARGIN 生成條碼的時候使用,指定邊距,單位像素,受格式的影響。類型Integer, 或String代表的數字類型
PDF417_COMPACT 指定是否使用PDF417緊湊模式(具體含義不懂)類型Boolean
PDF417_COMPACTION 指定PDF417的緊湊類型
PDF417_DIMENSIONS 指定PDF417的最大最小行列數
AZTEC_LAYERS aztec編碼相關,不理解
QR_VERSION 指定二維碼版本,版本越高越復雜,反而不容易解析

從上面的參數表格可以看出,適用於二維碼的有:ERROR_CORRECTION, CHARACTER_SET, MARGIN, QR_VERSION.

參數 使用說明
ERROR_CORRECTION 分為四個等級:L/M/Q/H, 等級越高,容錯率越高,識別速度降低。例如一個角被損壞,容錯率高的也許能夠識別出來。通常為H
CHARACTER_SET 編碼集,通常有中文,設置為 utf-8
MARGIN 默認為4, 實際效果並不是填寫的值,一般默認值就行
QR_VERSION 通常不變,設置越高,反而不好用

下面是最簡化的生成二維碼的代碼

/**
 * 生成二維碼
 *
 * @param contents 二維碼內容
 * @return 二維碼的描述對象 BitMatrix
 * @throws WriterException 編碼時出錯
 */
private BitMatrix encode(String contents) throws WriterException {
    final Map<EncodeHintType, Object> hints = new HashMap<>();
    hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
    hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
    return new QRCodeWriter().encode(contents, BarcodeFormat.QR_CODE, 320, 320, hints);
}

沒錯,就是這么簡單。但是我們得到的是一個BitMatrix, 如果需要顯示出來則要根據不同平台來處理。

BitMatrix 轉換成圖片

首先明確Java SE平台和Android平台的區別:Android平台移除關於swing部分的代碼,所以如果SE平台使用到這部分代碼,Android平台則不能用,不幸的是,官方的代碼恰恰用到了這部分。

明確另外一個概念:圖片在一個平台的存在形式有兩種,內存和文件。雖然文件在不同平台通用,但是轉換成文件的過程卻不是通用的。如Android中將Bitmap轉換成圖片文件,SE中將BufferedImage轉換成圖片文件。所以實際上,最重要的是將BitMatrix轉換成在內存中圖片的存在形式。

Java SE平台

BitMatrix轉換成BufferedImage.

在官方提供的zxing-javase包中已經有了相應的方法。下面是示例代碼:

BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(bitMatrix, new MatrixToImageConfig(Color.BLACK.getRGB(), Color.WHITE.getRGB()));

方法原型為:

public static BufferedImage toBufferedImage(BitMatrix matrix, MatrixToImageConfig config)

關於其中的參數,如下表格所示:

參數 說明
BitMatrix 二維碼的描述對象
MatrixToImageConfig 二維碼轉換成BufferedImage的配置參數

MatrixToImageConfig對象中只有兩個域onColoroffColor, 文章開頭提到二維碼類似於二進制,這樣的配置表示生成的BufferedImage用兩種顏色來表示二維碼上的開關。

BitMatrix轉換成圖片文件

在官方提供的zxing-javase包中實際上有將BitMatrix轉換成圖片文件的方法,不過實際上是先將BitMatrix轉換成BufferedImage, 然后將其轉換成圖片文件。

轉換方法(javax.imageio.ImageIO)

public static boolean write(RenderedImage im, String formatName, File output) throws IOException

參數說明:

參數 說明
RenderedImage im BufferedImage實現了RenderedImage接口
String formatName 圖片文件格式,通常使用 png
File output 圖片文件

上面兩步結合起來就直接將BitMatrix轉換成文件了,下面是MatrixToImageWriter的方法(類型Path表示文件路徑,可以使用File.toPath()方法得到)

public static void writeToPath(BitMatrix matrix, String format, Path file, MatrixToImageConfig config) throws IOException

Android 平台

類似的,在Android中也是先將BitMatrix轉換成Bitmap, 然后再寫入到文件中。

Bitmap寫入到文件中則非常熟悉了,如下所示:

Bitmap.compress(CompressFormat format, int quality, OutputStream stream)

其中的參數就不再解釋了,主要需要討論的是將BitMatrix轉換成Bitmap

在討論BitMatrixBitmap的轉換之前,先研究一下兩者的內部結構。

BitMatrix

翻譯:BitMatrix表示位數組的二維矩陣。而它內部則是使用一維int數組來實現的,一個int數組有32位。不過比較特別的是,每一行都是由一個新的int值開始,如果列數不是32的倍數,一行最后一個int值中有沒有用到的位。另外位是從int值的最小位開始排的,這是為了和com.google.zxing.common.BitArray更好的轉換。

不關心其內部實現,在其抽象的數據結構中,x表示列數,y表示行數,可以通過BitMatrix.get(int x, int y)獲取該位置是否為1(開).

BitMatrix中幾個可能應該熟悉方法如下

方法 說明
public boolean get(int x, int y) 獲取(x, y)的位值,true表示黑色
public void set(int x, int y) 設置(x, y)的位值為true
public void unset(int x, int y) 設置(x, y)的位值為false
public void flip(int x, int y) 對(x, y)的位值做非運算
public BitMatrix(int width, int height) 構造函數,指定寬高

另外說明一下com.google.zxing.common.BitArray這個類,這個類數據結構和BitMatrix的一行是一樣的,使用int數組來表示一維位數組,同樣的,最后一位int值可能有部分位沒有用到。也同樣的,位是從int值的最小位開始排列。

Bitmap

Bitmap內部是使用C實現的,所以不能直觀看到,不過可以猜測到,其內部應該使用的是一維int數組來實現的,一個int值就表示一個點的顏色。

下面列舉一些可能用到的一些方法

方法 說明
public static Bitmap createBitmap(int width, int height, Config config) 構造方法,創建一個透明的Bitmap
public void setPixels(@ColorInt int[] pixels, int offset, int stride, int x, int y, int width, int height) 使用數組中的顏色替換Bitmap的像素點的顏色
public void setPixel(int x, int y, @ColorInt int color) 設置Bitmap中指定像素點的顏色值

只列舉上面幾個方法是因為跟我們的理解和使用比較密切。

BitMatrix轉換成Bitmap

從前面的理解,我們可以看出,實際上BitMatrix轉換成Bitmap就是將其所代表的點的開關用顏色來表示。默認情況下,我們習慣使用黑色代表開,白色代表關。我們需要創建一個和BitMatrix長寬“相等”的Bitmap, 在轉換過程中,我們發現BitMatrix某一個位置是開,我們則設置Bitmap相應位置的顏色為開的顏色,反之同理。(當然我們也可以根據特殊需求修改其中的顏色)

代碼示例

private Bitmap bitMatrixToBitmap(BitMatrix bitMatrix) {
    final int width = bitMatrix.getWidth();
    final int height = bitMatrix.getHeight();
final int[] pixels = new int[width * height];
for (int y = 0; y &lt; height; y++) {
    for (int x = 0; x &lt; width; x++) {
        pixels[y * width + x] = bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF;
    }
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);

return bitmap;

}

上面分為三步:

  1. 創建一個一維int數組存放轉換后的顏色值
  2. 根據BitMatrix中的位值設置相應像素點的顏色值
  3. 創建一個“相同”大小的Bitmap, 使用代表顏色的數組為其賦值

注意:顏色值中前兩位默認為00, 這樣表示透明,所以一般都是要設置為FF, 不然在調試過程中就比較坑。

關於Bitmap.setPixels(@ColorInt int[] pixels, int offset, int stride, int x, int y, int width, int height)這個方法其中的參數比較多,詳細說明一下

參數 說明
int[] pixels 像素點顏色數組
int offset 從偏移顏色數組第一個像素多少開始讀起
int stride 每隔多少個點跳行,通常和寬度相同,不過也可以更大,設置為負值
int x Bitmap接收值的x軸起點
int y Bitmap接收值的y軸起點
int width 每一行復制多少顏色點
int height 一個復制多少行

因為考慮到像素點顏色數組和Bitmap大小本身存在不同所以才有這些參數,實際上,像素點顏色數組的大小和Bitmap的大小是相同的。那么其中的參數分別是:像素點顏色數組、0表示不偏移,直接從第一位復制、Bitmap寬度,復制完剛好一行則開始從下一個點開始進行復制下一行、0表示從左上角開始復制、0表示從左上角開始復制、Bitmap的寬度表示剛好復制到整個Bitmap, Bitmap的寬度表示剛好復制到整個Bitmap

解析二維碼介紹

zxing將解析圖形編碼的方式抽象成了一個接口com.google.zxing.Reader, 實現類中可以解析多種圖形編碼。

Reader
Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException
Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints) throws NotFoundException, ChecksumException, FormatException
void reset()

Reader共有三個方法,decode方法用於解析圖形編碼,返回一個解析結果;reset將重置解析器的狀態,便於復用。

關於encode的參數和返回值:

參數 說明
BinaryBitmap image 被解析的圖片
Map<DecodeHintType, ?> hints 幫助解析的一些額外的參數
Result 解析的結果

關於解碼時額外的參數

參數 說明
OTHER 未指定作用,應用自定義,Object類型
PURE_BARCODE Boolean類型,指定圖片是一個純粹的二維碼
POSSIBLE_FORMATS 可能的編碼格式,List類型
TRY_HARDER 花更多的時間用於尋找圖上的編碼,優化准確性,但不優化速度,Boolean類型
CHARACTER_SET 編碼字符集,通常指定UTF-8
ALLOWED_LENGTHS 允許的編碼數據長度 - 拒絕多余的數據。不懂這是什么,int[]類型
ASSUME_CODE_39_CHECK_DIGIT CODE 39 使用,不關心
ASSUME_GS1 假設使用GS1編碼來解析,不關心
RETURN_CODABAR_START_END CODABAR編碼使用,不關心
NEED_RESULT_POINT_CALLBACK 當解析到可能的結束點時進行回調
ALLOWED_EAN_EXTENSIONS 允許EAN或UPC編碼有額外的長度,不關心

從上面的參數表格可以看出,適用於二維碼的有:PURE_BARCODE, POSSIBLE_FORMATS, TRY_HARDER, CHARACTER_SET. 不過一般不會使用PURE_BARCODE, POSSIBLE_FORMATS設置為BarcodeFormat.QR_CODE, CHARACTER_SET設置為utf-8.

下面是最簡單的解析二維碼的代碼

/**
 * 解析二維碼
 *
 * @param binaryBitmap 被解析的圖形對象
 * @return 解析的結果
 */
private String decode(BinaryBitmap binaryBitmap) {
    try {
        Map<DecodeHintType, Object> hints = new HashMap<>();
        hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
        hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
        hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE);
    Result result = new QRCodeReader().decode(binaryBitmap, hints);

    return result.getText();
} catch (NotFoundException | ChecksumException | FormatException e) {
    e.printStackTrace();
    return null;
}

}

解析時,我們需要一個BinaryBitmap, 其只有一個構造器,接受一個com.google.zxing.Binarizer對象,所以無論是在哪個平台,無論圖片是以什么樣的形式存在的(文件、內存、Bitmap、BufferedImage),都需要提供一個Binarizer對象,將圖片轉換成一個BinaryBitmap.

BinaryBitmap

翻譯:這是在ZXing中用於代表一個位數據的核心位圖類。Reader對象接受一個BinaryBitmap並試圖對它進行解碼。

這個類使用了final修飾,只有一個接受Binarizer對象的構造器,並在其內部實質上也只有一個Binarizer對象,其所有方法都是代理到Binarizer或是Binarizer構造的一個BitMatrix對象。

Binarizer

這個類使用了abstract修飾。

翻譯:這個類在層次上提供了一組方法用於將亮度數據(luminance data)轉換成一個位數據。它允許算法多種多樣,例如允許服務器使用非常耗資源的閾值計算,允許手機使用比較快的算法。它也允許實現類多樣化,例如安卓上使用JNI,其他平台使用備選的版本。

摘自百度知道

PS解釋:“閾值”命令將灰度或彩色圖像轉換為高對比度的黑白圖像。您可以指定某個色階作為閾值。所有比閾值亮的像素轉換為白色;而所有比閾值暗的像素轉換為黑色。“閾值”命令對確定圖像的最亮和最暗區域很有用。

我的解釋,就是拿黑白2色去闡述你的圖片,是可調節的。

單詞Binarizer的翻譯:二值化。通常在圖像處理上使用比較多,可以和閾值計算處理看做類似的概念,因為對於目前的圖形編碼來說,一張圖片只認為有兩種顏色,表示開關。所以需要將一張彩色的圖片轉換成一張黑白色的圖。這個過程就成為二值化(Binarizer).

這個類只有一個使用protected修飾的構造器,這個構造器只接受一個LuminanceSource對象。其所有的方法都是抽象的。

Binarizer有兩個子類,com.google.zxing.common.GlobalHistogramBinarizercom.google.zxing.common.HybridBinarizer.

翻譯:GlobalHistogramBinarizer, 全局直方圖二值化。這個Binarizer的實現類使用了早期的ZXing全局直方圖方法。它適合沒有足夠CPU和內存的低端手機來使用本地閾值算法。但它選擇了全部的黑點來計算,因此不能處理陰影和漸變兩種情況。快速的手機設備和所有的桌面應用應該使用HybridBinarizer.

翻譯:HybridBinarizer, 混合型二值化。這個Binarizer的實現類使用了本地閾值算法,比GlobalHistogramBinarizer要慢,相對而言也比較精准。它專門為以白色為背景的連續黑色塊二維碼圖像解析而設計,也更適合用來解析具有嚴重陰影和漸變的二維碼圖像。(部分參考文章zxing掃描二維碼和識別圖片二維碼及其優化策略

兩者的大概意思是GlobalHistogramBinarizer適合CPU和內存比較差的低端手機,解析效果沒有HybridBinarizer好,但是HybridBinarizer耗費的資源更多,解析速度也稍慢,不過對於目前市面上的手機CPU和內存都不會太差,所以大可以直接使用HybridBinarizer. 另外,HybridBinarizer繼承自GlobalHistogramBinarizer, 兩者都只有一個接受一個LuminanceSource的構造器。

LuminanceSource

翻譯:這個類層次的目的是抽象在不同平台上的位圖,實現成一個標准的接口用於請求灰度的亮度值。這個接口值提供不可改變的方法;因此剪切或者旋轉時將創造一個副本(不復用)。這樣是為了保證一個Reader不能修改原亮度數據,而且讓他對於在處理鏈的其他Reader保持一個未知的狀態。

對於這個類的作用還不太清楚,不過我們可以知道的是,我們需要將在不同平台的圖片對象轉換成LuminanceSource對象,這樣就可以交給Zxing來進行解析了。

在zxing-core包中,有兩個LuminanceSource的實現類,com.google.zxing.RGBLuminanceSourcecom.google.zxing.PlanarYUVLuminanceSource. 在zxing-javase包中,有一個實現類com.google.zxing.client.j2se.BufferedImageLuminanceSource.

RGBLuminanceSource, 這個類用於幫助解析圖片文件,這個圖片文件是從一個ARGB的像素數組轉換成一個RGB數據的。但是不支持旋轉。

PlanarYUVLuminanceSource, 這個對象繼承自LuminanceSource, 多從相機設備中返回的YUV數據數組轉換得到,可以選擇性的將YUV的完整數據剪切其中一部分用於解析(具體參數可以查看其構造函數)。這樣可以用於取出邊界外多余的像素用於加快解析速度。

Java SE平台

既然官方在zxing-core包中提供了BufferedImageLuminanceSource, 我們直接使用即可。它接受一個BufferedImage作為構造器參數,也有一個重載構造器,用於取得BufferedImage的部分來進行解析。

下面代碼展示解析一個圖片文件上的二維碼

/**
 * 解析圖片文件上的二維碼
 *
 * @param imageFile 圖片文件
 * @return 解析的結果,null表示解析失敗
 */
private String decode(File imageFile) {
    try {
        BufferedImage image = ImageIO.read(imageFile);
        LuminanceSource luminanceSource = new BufferedImageLuminanceSource(image);
        Binarizer binarizer = new HybridBinarizer(luminanceSource);
    BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);

    Map&lt;DecodeHintType, Object&gt; hints = new HashMap&lt;&gt;();
    hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
    hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
    hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE);

    Result result = new QRCodeReader().decode(binaryBitmap, hints);

    return result.getText();
} catch (Exception e) {
    e.printStackTrace();
    return null;
}

}

Android 平台

官方包中並沒有一個所謂的BitmapLuminanceSource, 而網上也有定義這樣一個類,但是實現效果並不好,多是使用Bitmap構造一個RGBLuminanceSource. 下面是演示代碼

/**
 * 解析Bitmap中的二維碼
 *
 * @param bitmap
 * @return 解析結果,null表示解析失敗
 */
private String decode(Bitmap bitmap) {
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    final int[] pixels = new int[width * height];
    bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
    RGBLuminanceSource luminanceSource = new RGBLuminanceSource(width, height, pixels);
    BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(luminanceSource));
try {
    final Map&lt;DecodeHintType, Object&gt; hints = new HashMap&lt;&gt;();
    hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
    hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE);
    hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
    Result result = new QRCodeReader().decode(binaryBitmap, hints);

    return result.toString();
} catch (Exception e) {
    e.printStackTrace();
    return null;
}

}

不過使用相機掃描解析二維碼卻不同,在Android API 21以下使用android.hardware.Camera來進行掃描時,通常在預覽狀態下得到的是一個byte數組,這時,就比較容易用來構造一個com.google.zxing.PlanarYUVLuminanceSource, 具體如何使用,在討論到相機時會再說明。

標注

Demo地址ZxingDemo

使用到的jar包:core-3.3.0.jar, javase-3.3.0.jar

參考

zxing掃描二維碼和識別圖片二維碼及其優化策略


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM