Android高仿微信/支付宝 扫一扫(弱光检测扫一扫自动放大功能)

前言


 目前市面上App携带的扫一扫功能大多是乞丐版,怎么说,就是只有扫一扫.而目前来说扫一扫做的最好的还是微信,微信有弱光环境的检测(可以自动提示用户打开闪光灯),同时,当发现扫描目标距离过远时,还可以自动的放大镜头,亲测可以多次的放大,所以说细节决定成败,支付宝虽然也有微信的功能,但是我觉得支付宝的弱光做的一般,自动放大也有点鸡肋,不过也很不错了,毕竟一般来说,实现扫一扫乞丐版就基本完事了,而我也遇到了这个需求,就是要实现微信和支付宝类似的效果.


效果图走一波(用的gif大师,录制的质量比较低,质量过高的传不上去,见谅)


第一帧gif当为弱光时,动态显示“手电筒”,点击打开后,一直显示“关系手电筒”.

第二个gif帧就是扫一扫自动放大的效果.

 



需求分析


1.中间的frame框就不说了,比较的简单,ondraw里边修改,用安卓纯纯的坐标系,就可以实现.
2.弱光检测: 这块我花了两天的时间研究,ios获取后置摄像头的光感比较的方便,几行代码就可以获取,他们的是brightnessvalue这个值;而安卓第一版我用的光传感器,你要知道,光传感器是在前置摄像头附近,而扫一扫是用后置摄像头来扫描的,光传感器晚上是没有问题的,白天不是非常的精确,就放弃了这个方案,最后查了相关的资料我使用jpegReader.metadata(),exifinterface来读取实时帧流,均以失败告终,我想Camera2应该提供了某些的api,但是要求是5.0之后了,我也就没有细研究,之后,我看到支付宝的效果后,我就明白了,他分析的是后摄像头拍照的图片颜色来区分的,多次尝试发现,是这样,同理,微信应该也是类似的实现,只不过他调的比较细,优化的比较好而已.
3.扫一扫自动放大:这个你思考下,其实也很简单,Camera有放大的属性,无非是触发条件怎么来判断,微信扫一扫是当镜头中有二维码的是才会进行自动放大,并且会多次的放大.


代码实现


我们项目用的是zxing,不用说了要修改源码.

ui层就不说了,真的简单,安卓坐标系,cavas 画布api,来绘制rect区域,在ViewFindView这个类里边的onDraw方法修改即可.


弱光检测


   上面分析完后,就知道了,咱们要实时的分析图片的颜色值(agb值),既然说到了实时的分析,我们就要找到二维码处理解码实时帧的方法,zxing使用decodeThread,decodeHanlder,decodeThread线程不断的分析流并解码.
[java]  view plain  copy
  1. /** 
  2.      * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency, 
  3.      * reuse the same reader objects from one decode to the next. 
  4.      * 
  5.      * @param data   The YUV preview frame. 
  6.      * @param width  The width of the preview frame. 
  7.      * @param height The height of the preview frame. 
  8.      */  
  9.     private void decode(byte[] data, int width, int height)   

这个data是YUV格式的,谷歌也提供了相关的转换方法Yuvimage.

将YUV转换为agb方法(网上摘抄,天下文章一大抄)

[java]  view plain  copy
  1. private int[] decodeYUV420SP(byte[] yuv420sp, int width, int height) {  
  2.         final int frameSize = width * height;  
  3.   
  4.         int rgb[] = new int[width * height];  
  5.         for (int j = 0, yp = 0; j < height; j++) {  
  6.             int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;  
  7.             for (int i = 0; i < width; i++, yp++) {  
  8.                 int y = (0xff & ((int) yuv420sp[yp])) - 16;  
  9.                 if (y < 0) y = 0;  
  10.                 if ((i & 1) == 0) {  
  11.                     v = (0xff & yuv420sp[uvp++]) - 128;  
  12.                     u = (0xff & yuv420sp[uvp++]) - 128;  
  13.                 }  
  14.   
  15.                 int y1192 = 1192 * y;  
  16.                 int r = (y1192 + 1634 * v);  
  17.                 int g = (y1192 - 833 * v - 400 * u);  
  18.                 int b = (y1192 + 2066 * u);  
  19.   
  20.                 if (r < 0) r = 0;  
  21.                 else if (r > 262143) r = 262143;  
  22.                 if (g < 0) g = 0;  
  23.                 else if (g > 262143) g = 262143;  
  24.                 if (b < 0) b = 0;  
  25.                 else if (b > 262143) b = 262143;  
  26.   
  27.                 rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) &  
  28.                         0xff00) | ((b >> 10) & 0xff);  
  29.   
  30.   
  31.             }  
  32.         }  
  33.         return rgb;  
  34.     }  


使用Bitmap.createBitmap转换为bitmap图片下,分析图片颜色的平均值,颜色都是16进制的,不懂的可以网上搜下,黑色的颜色 对应int = -16777216, 所以我们认为当前的平均值 / black(-16777216) 小于等于1 同时大于0.99,就认为是弱光(这个值还可以调节)

[java]  view plain  copy
  1. private int getAverageColor(Bitmap bitmap) {  
  2.      int redBucket = 0;  
  3.      int greenBucket = 0;  
  4.      int blueBucket = 0;  
  5.      int pixelCount = 0;  
  6.   
  7.      for (int y = 0; y < bitmap.getHeight(); y++) {  
  8.          for (int x = 0; x < bitmap.getWidth(); x++) {  
  9.              int c = bitmap.getPixel(x, y);  
  10.   
  11.              pixelCount++;  
  12.              redBucket += Color.red(c);  
  13.              greenBucket += Color.green(c);  
  14.              blueBucket += Color.blue(c);  
  15.          }  
  16.      }  
  17.   
  18.      int averageColor = Color.rgb(redBucket / pixelCount, greenBucket  
  19.              / pixelCount, blueBucket / pixelCount);  
  20.      return averageColor;  
  21.  }  


最终的方法,为了防止内存的溢出,取当前帧的八分之一宽高获取agb数组,同时用bmp的八分之一来分析颜色的平均值,分析完后直接释放bitmap.


[java]  view plain  copy
  1. //分析预览帧中图片的arg 取平均值  
  2.   
  3.     private void analysisColor(byte[] data, int width, int height) {  
  4.         int[] rgb = decodeYUV420SP(data, width / 8, height / 8);  
  5.         Bitmap bmp = Bitmap.createBitmap(rgb, width / 8, height / 8, Bitmap.Config.ARGB_8888);  
  6.         if (bmp != null) {  
  7.             //取以中心点宽高10像素的图片来分析  
  8.             Bitmap resizeBitmap = Bitmap.createBitmap(bmp, bmp.getWidth() / 2, bmp.getHeight() / 21010);  
  9.             float color = (float) getAverageColor(resizeBitmap);  
  10.             DecimalFormat decimalFormat1 = new DecimalFormat("0.00");  
  11.             String percent = decimalFormat1.format(color / -16777216);  
  12.             float floatPercent = Float.parseFloat(percent);  
  13.             Constants.isWeakLight = floatPercent >= 0.99 && floatPercent <= 1.00;  
  14.             if (null != resizeBitmap) {  
  15.                 resizeBitmap.recycle();  
  16.             }  
  17.             bmp.recycle();  
  18.         }  
  19.     }  

上述基本实现了弱光的检测,还可以进行微调,都是自己来控制的.


扫一扫自动放大的功能



二维码携带有坐标数据,根据坐标算出二维码的矩形大小并和当前frame边框的坐标进行比对,来进行放大,目前看微信好像也是这样实现的,不过弊端是什么,就是我是扫描出来这个界面结果后进行放大的,有点多此一举的感觉,目前先这样,后续可以根据时间来优化或修改吧.代码如下:

[java]  view plain  copy
  1. /* 
  2.  * Copyright (C) 2010 ZXing authors 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.   
  17. package com.google.zxing.client.android;  
  18.   
  19. import com.google.zxing.BinaryBitmap;  
  20. import com.google.zxing.DecodeHintType;  
  21. import com.google.zxing.MultiFormatReader;  
  22. import com.google.zxing.ReaderException;  
  23. import com.google.zxing.Result;  
  24. import com.google.zxing.common.Constants;  
  25. import com.google.zxing.common.HybridBinarizer;  
  26.   
  27. import android.graphics.Bitmap;  
  28. import android.graphics.Color;  
  29. import android.graphics.Rect;  
  30. import android.hardware.Camera;  
  31. import android.os.Bundle;  
  32. import android.os.Handler;  
  33. import android.os.Looper;  
  34. import android.os.Message;  
  35. import android.util.Log;  
  36.   
  37. import java.text.DecimalFormat;  
  38. import java.util.Map;  
  39.   
  40. final class DecodeHandler extends Handler {  
  41.   
  42.     private static final String TAG = DecodeHandler.class.getSimpleName();  
  43.   
  44.     private final CaptureActivity activity;  
  45.     private final MultiFormatReader multiFormatReader;  
  46.     private boolean running = true;  
  47.     private int frameCount;  
  48.   
  49.     DecodeHandler(CaptureActivity activity, Map<DecodeHintType, Object> hints) {  
  50.         multiFormatReader = new MultiFormatReader();  
  51.         multiFormatReader.setHints(hints);  
  52.         this.activity = activity;  
  53.     }  
  54.   
  55.     @Override  
  56.     public void handleMessage(Message message) {  
  57.         if (!running) {  
  58.             return;  
  59.         }  
  60.         if (message.what == R.id.decode) {  
  61.             decode((byte[]) message.obj, message.arg1, message.arg2);  
  62.         } else if (message.what == R.id.quit) {  
  63.             running = false;  
  64.             Looper.myLooper().quit();  
  65.         }  
  66.     }  
  67.   
  68.     /** 
  69.      * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency, 
  70.      * reuse the same reader objects from one decode to the next. 
  71.      * 
  72.      * @param data   The YUV preview frame. 
  73.      * @param width  The width of the preview frame. 
  74.      * @param height The height of the preview frame. 
  75.      */  
  76.     private void decode(byte[] data, int width, int height) {  
  77.   
  78.         byte[] rotatedData = new byte[data.length];  
  79.         for (int y = 0; y < height; y++) {  
  80.             for (int x = 0; x < width; x++)  
  81.                 rotatedData[x * height + height - y - 1] = data[x + y * width];  
  82.         }  
  83.         frameCount++;  
  84.         //丢弃前2帧并每隔2帧分析下预览帧color值  
  85.         if (frameCount > 2 && frameCount % 2 == 0) {  
  86.             analysisColor(rotatedData, width, height);  
  87.         }  
  88.         long start = System.currentTimeMillis();  
  89.         Result rawResult = null;  
  90.         final PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(rotatedData, height, width);  
  91.         if (source != null) {  
  92.             BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));  
  93.             try {  
  94.                 rawResult = multiFormatReader.decodeWithState(bitmap);  
  95.             } catch (ReaderException re) {  
  96.                 // continue  
  97.             } finally {  
  98.                 multiFormatReader.reset();  
  99.             }  
  100.         }  
  101.         final Handler handler = activity.getHandler();  
  102.         if (rawResult != null) {  
  103.             // Don't log the barcode contents for security.  
  104.             long end = System.currentTimeMillis();  
  105.             Log.d(TAG, "Found barcode in " + (end - start) + " ms");  
  106.             if (handler != null) {  
  107.                 float point1X = rawResult.getResultPoints()[0].getX();  
  108.                 float point1Y = rawResult.getResultPoints()[0].getY();  
  109.                 float point2X = rawResult.getResultPoints()[1].getX();  
  110.                 float point2Y = rawResult.getResultPoints()[1].getY();  
  111.                 int len = (int) Math.sqrt(Math.abs(point1X - point2X) * Math.abs(point1X - point2X) + Math.abs(point1Y - point2Y) * Math.abs(point1Y - point2Y));  
  112.                 Rect frameRect = activity.getCameraManager().getFramingRect();  
  113.                 if (frameRect != null) {  
  114.                     int frameWidth = frameRect.right - frameRect.left;  
  115.                     Camera camera = activity.getCameraManager().getCameraNotStatic();  
  116.                     Camera.Parameters parameters = camera.getParameters();  
  117.                     final int maxZoom = parameters.getMaxZoom();  
  118.                     int zoom = parameters.getZoom();  
  119.                     if (parameters.isZoomSupported()) {  
  120.                         if (len <= frameWidth / 4) {  
  121.                             if (zoom == 0) {  
  122.                                 zoom = maxZoom / 3;  
  123.                             } else {  
  124.                                 zoom = zoom + 5;  
  125.                             }  
  126.                             if (zoom > maxZoom) {  
  127.                                 zoom = maxZoom;  
  128.                             }  
  129.                             parameters.setZoom(zoom);  
  130.                             camera.setParameters(parameters);  
  131.                             final Result finalRawResult = rawResult;  
  132.                             postDelayed(new Runnable() {  
  133.                                 @Override  
  134.                                 public void run() {  
  135.                                     Message message = Message.obtain(handler, R.id.decode_succeeded, finalRawResult);  
  136.                                     Bundle bundle = new Bundle();  
  137.                                     bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());  
  138.                                     message.setData(bundle);  
  139.                                     message.sendToTarget();  
  140.                                 }  
  141.                             }, 1000);  
  142.                         } else {  
  143.                             Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);  
  144.                             Bundle bundle = new Bundle();  
  145.                             bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());  
  146.                             message.setData(bundle);  
  147.                             message.sendToTarget();  
  148.                         }  
  149.                     }  
  150.                 } else {  
  151.                     Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);  
  152.                     Bundle bundle = new Bundle();  
  153.                     bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());  
  154.                     message.setData(bundle);  
  155.                     message.sendToTarget();  
  156.                 }  
  157.             }  
  158.         } else {  
  159.             if (handler != null) {  
  160.                 Message message = Message.obtain(handler, R.id.decode_failed);  
  161.                 message.sendToTarget();  
  162.             }  
  163.         }  
  164.     }  
  165.   
  166.     //分析预览帧中图片的arg 取平均值  
  167.   
  168.     private void analysisColor(byte[] data, int width, int height) {  
  169.         int[] rgb = decodeYUV420SP(data, width / 8, height / 8);  
  170.         Bitmap bmp = Bitmap.createBitmap(rgb, width / 8, height / 8, Bitmap.Config.ARGB_8888);  
  171.         if (bmp != null) {  
  172.             //取以中心点宽高10像素的图片来分析  
  173.             Bitmap resizeBitmap = Bitmap.createBitmap(bmp, bmp.getWidth() / 2, bmp.getHeight() / 21010);  
  174.             float color = (float) getAverageColor(resizeBitmap);  
  175.             DecimalFormat decimalFormat1 = new DecimalFormat("0.00");  
  176.             String percent = decimalFormat1.format(color / -16777216);  
  177.             float floatPercent = Float.parseFloat(percent);  
  178.             Constants.isWeakLight = floatPercent >= 0.99 && floatPercent <= 1.00;  
  179.             if (null != resizeBitmap) {  
  180.                 resizeBitmap.recycle();  
  181.             }  
  182.             bmp.recycle();  
  183.         }  
  184.     }  
  185.   
  186.     private int[] decodeYUV420SP(byte[] yuv420sp, int width, int height) {  
  187.         final int frameSize = width * height;  
  188.   
  189.         int rgb[] = new int[width * height];  
  190.         for (int j = 0, yp = 0; j < height; j++) {  
  191.             int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;  
  192.             for (int i = 0; i < width; i++, yp++) {  
  193.                 int y = (0xff & ((int) yuv420sp[yp])) - 16;  
  194.                 if (y < 0) y = 0;  
  195.                 if ((i & 1) == 0) {  
  196.                     v = (0xff & yuv420sp[uvp++]) - 128;  
  197.                     u = (0xff & yuv420sp[uvp++]) - 128;  
  198.                 }  
  199.   
  200.                 int y1192 = 1192 * y;  
  201.                 int r = (y1192 + 1634 * v);  
  202.                 int g = (y1192 - 833 * v - 400 * u);  
  203.                 int b = (y1192 + 2066 * u);  
  204.   
  205.                 if (r < 0) r = 0;  
  206.                 else if (r > 262143) r = 262143;  
  207.                 if (g < 0) g = 0;  
  208.                 else if (g > 262143) g = 262143;  
  209.                 if (b < 0) b = 0;  
  210.                 else if (b > 262143) b = 262143;  
  211.   
  212.                 rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) &  
  213.                         0xff00) | ((b >> 10) & 0xff);  
  214.   
  215.   
  216.             }  
  217.         }  
  218.         return rgb;  
  219.     }  
  220.   
  221.     private int getAverageColor(Bitmap bitmap) {  
  222.         int redBucket = 0;  
  223.         int greenBucket = 0;  
  224.         int blueBucket = 0;  
  225.         int pixelCount = 0;  
  226.   
  227.         for (int y = 0; y < bitmap.getHeight(); y++) {  
  228.             for (int x = 0; x < bitmap.getWidth(); x++) {  
  229.                 int c = bitmap.getPixel(x, y);  
  230.   
  231.                 pixelCount++;  
  232.                 redBucket += Color.red(c);  
  233.                 greenBucket += Color.green(c);  
  234.                 blueBucket += Color.blue(c);  
  235.             }  
  236.         }  
  237.   
  238.         int averageColor = Color.rgb(redBucket / pixelCount, greenBucket  
  239.                 / pixelCount, blueBucket / pixelCount);  
  240.         return averageColor;  
  241.     }  
  242. }  





  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值