感謝大佬: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
對象中只有兩個域onColor
和offColor
, 文章開頭提到二維碼類似於二進制,這樣的配置表示生成的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
。
在討論BitMatrix
與Bitmap
的轉換之前,先研究一下兩者的內部結構。
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 < height; y++) { for (int x = 0; x < 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;
}
上面分為三步:
- 創建一個一維
int
數組存放轉換后的顏色值 - 根據
BitMatrix
中的位值設置相應像素點的顏色值 - 創建一個“相同”大小的
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.GlobalHistogramBinarizer
和com.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.RGBLuminanceSource
和com.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<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 (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<DecodeHintType, Object> hints = new HashMap<>(); 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