目前android中實(shí)現(xiàn)掃二維碼大多數(shù)使用的是zxing這個(gè)開源框架虏杰,要使用android的核心源碼讥蟆,因?yàn)槲覀冃枰谠创a中做修改,將框架添加到項(xiàng)目中纺阔,這里就不多說了瘸彤,網(wǎng)上都有,這里只說一下放大攝像頭部分笛钝。涉及到的文件主要有DecodeHandler质况,MultiFormatReader,QRCodeReader玻靡。
實(shí)際應(yīng)用中结榄,我們都知道鏡頭離二維碼太遠(yuǎn)或者太近都影響識(shí)別,二維碼恰好處于掃描框中最好囤捻。
思路:
1臼朗、當(dāng)要掃的二維碼處于掃描框中時(shí),獲取該二維碼在掃描框中的寬度蝎土,與掃描框的寬度進(jìn)行對(duì)比视哑,小于掃描框?qū)挾鹊?/4,則認(rèn)為二維碼在掃描框中較刑苎摹(鏡頭較遠(yuǎn))挡毅,則需要放大攝像頭焦距,而不需要移動(dòng)手機(jī)來調(diào)整
2暴构、攝像頭焦距的放大
一跪呈、獲取二維碼在掃描框中的寬度
要獲取二維碼在掃描框中的寬度,首先要對(duì)QR碼相關(guān)部分了解丹壕,具體可以參考這篇博客http://blog.csdn.net/mihenyinghua/article/details/17224019庆械,我也是讀了這篇博客后知道從哪一塊進(jìn)行著手薇溃,而不是盲目去看源碼菌赖,畢竟zxing這個(gè)庫的源碼還是不少的,而且我們只關(guān)心二維碼這一塊沐序,所以沒必要所有源碼都去閱讀琉用。
從這篇博客中了解了從掃描到得到二維碼中的信息分三步堕绩,具體的大家去閱讀博客就行了:
1)、將圖像進(jìn)行二值化處理邑时,1奴紧、0代表黑、白晶丘。
2)黍氮、尋找定位符、校正符浅浮,然后將原圖像中符號(hào)碼部分取出沫浆。(detector代碼實(shí)現(xiàn)的功能)
3)、對(duì)符號(hào)碼矩陣按照編碼規(guī)范進(jìn)行解碼滚秩,得到實(shí)際信息(decoder代碼實(shí)現(xiàn)的功能)
首先是掃描框的尺寸专执,在CamerManager中getFramingRect()方法是獲取掃描框的矩形尺寸,frameRect.right-frameRect.left來獲取掃描框?qū)挾取?/p>
Rect frameRect = activity.cameraManager.getFramingRect();
if(frameRect!=null){
int frameWidth = frameRect.right-frameRect.left;
}
最終的結(jié)果在DecodeHandler文件中郁油,decode(byte[] data, int width, int height)本股,是對(duì)掃描結(jié)果的解析處理。先看一下源碼:
/**
* Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
* reuse the same reader objects from one decode to the next.
*
* @param data The YUV preview frame.
* @param width The width of the preview frame.
* @param height The height of the preview frame.
*/
private void decode(byte[] data, int width, int height) {
long start = System.currentTimeMillis();
Result rawResult = null;
PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
if (source != null) {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = multiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
}
Handler handler = activity.getHandler();
if (rawResult != null) {
// Don't log the barcode contents for security.
long end = System.currentTimeMillis();
Log.d(TAG, "Found barcode in " + (end - start) + " ms");
if (handler != null) {
Message message = Message.obtain(handler, Ids.decode_succeeded, rawResult);
Bundle bundle = new Bundle();
bundleThumbnail(source, bundle);
message.setData(bundle);
message.sendToTarget();
}
} else {
if (handler != null) {
Message message = Message.obtain(handler, Ids.decode_failed);
message.sendToTarget();
}
}
}
Result 就是結(jié)果桐腌,我們需要從中獲取一些信息拄显。要獲取二維碼的尺寸,只需要獲取兩個(gè)定位的點(diǎn)就行了哩掺,這個(gè)通過Result的getResultPoints()來獲取凿叠,得到的是一個(gè)ResultPoint數(shù)組,通過調(diào)試嚼吞,結(jié)合安卓中的坐標(biāo)系得知的結(jié)果是resultPoints[0],resultPoints[1]分別對(duì)應(yīng)的是下圖中的左下角--左上角的點(diǎn)盒件,有這兩個(gè)點(diǎn)就足夠了,然后通過兩點(diǎn)間的距離公式來獲得大致尺寸舱禽。
現(xiàn)在來獲取掃描框中的二維碼大谐吹蟆:
下面是我提取出來相關(guān)的主要文件,一步一步涉及到的文件過程如下
DecodeHandler-->
-->MultiFormatReader multiFormatReader = new MultiFormatReader();
MultiFormatReader-->
setHints(){
//二維碼
if (formats.contains(BarcodeFormat.QR_CODE)) {
readers.add(new QRCodeReader());
}
}
QRCodeReader-->Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints){
}
我們先添加一個(gè)QRCodeReader的構(gòu)造方法誊稚,因?yàn)閿z像頭的相關(guān)配置以及掃描框相關(guān)信息在CaptureActivity中翔始,所以把CaptureActivity放到QRCodeReader的構(gòu)造方法中去
private CaptureActivity activity;
public QRCodeReader(CaptureActivity activity) {
this.activity = activity;
}
接著是MultiFormatReader,
private CaptureActivity activity;
public void setActivity(CaptureActivity activity) {
this.activity = activity;
}
//這個(gè)方法中有關(guān)QRCodeReader的調(diào)用改成調(diào)用有參的構(gòu)造方法
setHints(Map<DecodeHintType,?> hints){
if (formats.contains(BarcodeFormat.QR_CODE)) {
readers.add(new QRCodeReader(activity));
}
//..... 接著是
if (readers.isEmpty()){
readers.add(new QRCodeReader(activity));
}
}
然后在DecodeHandler的構(gòu)造方法中對(duì)MultiFormatReader的調(diào)用setHints之前先調(diào)用setActivity()保證CaptureActivity不為空
DecodeHandler(CaptureActivity activity, Map<DecodeHintType, Object> hints) {
multiFormatReader = new MultiFormatReader();
multiFormatReader.setActivity(activity);
multiFormatReader.setHints(hints);
this.activity = activity;
}
接下來看QRCodeReader中的這個(gè)方法,我又加了三個(gè)關(guān)鍵注釋
@Override
public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
throws NotFoundException, ChecksumException, FormatException {
DecoderResult decoderResult;
ResultPoint[] points;
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
BitMatrix bits = extractPureBits(image.getBlackMatrix());
decoderResult = decoder.decode(bits, hints);
points = NO_POINTS;
} else {
//1里伯、將圖像進(jìn)行二值化處理城瞎,1、0代表黑疾瓮、白脖镀。( 二維碼的使用getBlackMatrix方法 )
//2、尋找定位符狼电、校正符蜒灰,然后將原圖像中符號(hào)碼部分取出弦蹂。(detector代碼實(shí)現(xiàn)的功能)
DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
//3、對(duì)符號(hào)碼矩陣按照編碼規(guī)范進(jìn)行解碼强窖,得到實(shí)際信息(decoder代碼實(shí)現(xiàn)的功能)
decoderResult = decoder.decode(detectorResult.getBits(), hints);
points = detectorResult.getPoints();
}
// If the code was mirrored: swap the bottom-left and the top-right points.
if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
}
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
List<byte[]> byteSegments = decoderResult.getByteSegments();
if (byteSegments != null) {
result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
}
String ecLevel = decoderResult.getECLevel();
if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
if (decoderResult.hasStructuredAppend()) {
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
decoderResult.getStructuredAppendSequenceNumber());
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderResult.getStructuredAppendParity());
}
return result;
}
通過DetectorResult 凸椿,我們可以獲取二維碼的定位符,所以在執(zhí)行解碼前利用定位符來獲取在掃描框中的二維碼大小翅溺,和掃描框進(jìn)行大小對(duì)比來判斷是否需要獲取放大攝像頭脑漫。
處理結(jié)果如下
@Override
public final Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
throws NotFoundException, ChecksumException, FormatException {
DecoderResult decoderResult;
ResultPoint[] points;
if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
BitMatrix bits = extractPureBits(image.getBlackMatrix());
decoderResult = decoder.decode(bits, hints);
points = NO_POINTS;
} else {
//1、將圖像進(jìn)行二值化處理咙崎,1窿撬、0代表黑、白叙凡。( 二維碼的使用getBlackMatrix方法 )
//2劈伴、尋找定位符、校正符握爷,然后將原圖像中符號(hào)碼部分取出跛璧。(detector代碼實(shí)現(xiàn)的功能)
DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
if(activity!=null){
CameraManager cameraManager = activity.cameraManager;
ResultPoint[] p = detectorResult.getPoints();
//計(jì)算掃描框中的二維碼的寬度,兩點(diǎn)間距離公式
float point1X = p[0].getX();
float point1Y = p[0].getY();
float point2X = p[1].getX();
float point2Y = p[1].getY();
int len =(int) Math.sqrt(Math.abs(point1X-point2X)*Math.abs(point1X-point2X)+Math.abs(point1Y-point2Y)*Math.abs(point1Y-point2Y));
Rect frameRect = cameraManager.getFramingRect();
if(frameRect!=null){
int frameWidth = frameRect.right-frameRect.left;
Camera camera = cameraManager.getOpenCamera().getCamera();
Camera.Parameters parameters = camera.getParameters();
int maxZoom = parameters.getMaxZoom();
int zoom = parameters.getZoom();
if(parameters.isZoomSupported()){
if(len <= frameWidth/4) {//二維碼在掃描框中的寬度小于掃描框的1/4新啼,放大鏡頭
if (zoom == 0) {
zoom = maxZoom / 2;
} else if (zoom <= maxZoom - 10) {
zoom = zoom + 10;
} else {
zoom = maxZoom;
}
parameters.setZoom(zoom);
camera.setParameters(parameters);
return null;
}
}
}
}
//3追城、對(duì)符號(hào)碼矩陣按照編碼規(guī)范進(jìn)行解碼,得到實(shí)際信息(decoder代碼實(shí)現(xiàn)的功能)
decoderResult = decoder.decode(detectorResult.getBits(), hints);
points = detectorResult.getPoints();
}
// If the code was mirrored: swap the bottom-left and the top-right points.
if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
}
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
List<byte[]> byteSegments = decoderResult.getByteSegments();
if (byteSegments != null) {
result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
}
String ecLevel = decoderResult.getECLevel();
if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
if (decoderResult.hasStructuredAppend()) {
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
decoderResult.getStructuredAppendSequenceNumber());
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderResult.getStructuredAppendParity());
}
return result;
}
//計(jì)算掃描框中的二維碼的寬度燥撞,兩點(diǎn)間距離公式
float point1X = rawResult.getResultPoints()[0].getX();
float point1Y = rawResult.getResultPoints()[0].getY();
float point2X = rawResult.getResultPoints()[1].getX();
float point2Y = rawResult.getResultPoints()[1].getY();
int len =(int) Math.sqrt(Math.abs(point1X-point2X)*Math.abs(point1X-point2X)+Math.abs(point1Y-point2Y)*Math.abs(point1Y-point2Y));
有了掃描框的寬度和二維碼的尺寸座柱,就可以判斷二維碼在掃描框中是不是太小。
小于掃描框?qū)挾鹊?/4物舒,就放大鏡頭色洞,通過handler告知此次掃描失敗重新掃描,否則不用
二冠胯、放大攝像頭火诸,調(diào)整焦距
Camera的當(dāng)前設(shè)置信息在Parameters可以獲取,通過getParameters()獲取Parameters荠察。
放大攝像頭有個(gè)前提條件就是你的手機(jī)要支持?jǐn)z像頭焦距的放大和縮小置蜀,不過目前大多數(shù)手機(jī)都支持了,我們還是判斷一下為好悉盆。parameters.isZoomSupported()盯荤,判斷是否支持焦距縮放。支持焕盟,然后設(shè)置需要放大多少秋秤,通過parameters.setZoom(int value)來設(shè)置,這個(gè)值有個(gè)限制, The valid range is 0 to {@link #getMaxZoom}.
也就是最大能設(shè)置到maxzoom航缀,這個(gè)最大值通過getMaxZoom()來獲取。設(shè)置完成后堰怨,然后再調(diào)用camera.setParameters(parameters);讓設(shè)置生效芥玉。另外,我加了一個(gè)判斷备图,當(dāng)掃描的是二維碼才進(jìn)行焦距的縮放rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE
不需要的可以不用加灿巧。
最后運(yùn)行的時(shí)候會(huì)有個(gè)QRCodeMultiReader報(bào)的錯(cuò)誤,因?yàn)樗^承了QRCodeReader揽涮,而我們添加了一個(gè)有參的構(gòu)造函數(shù)抠藕,所以按照提示把它的構(gòu)造也添加上就行了
public QRCodeMultiReader(CaptureActivity activity) {
super(activity);
}
運(yùn)行后就可以實(shí)現(xiàn)類似微信的那種效果了。
需要demo的蒋困,可以到https://github.com/Alvin9234/CommonLibrary中下載