Posts 关于Android Zxing 3.3.0 的填坑
Post
Cancel

关于Android Zxing 3.3.0 的填坑

前言

一开始公司的项目已经集成了Zxing,也实现了扫二维码的功能,但是前一阵子,领导发现在扫码的时候,预览的成像有问题.当时让我去修改,但是我发现公司用的zxing版本比较新,用的3.3.0的版本,我当时在网上大概的找了一些,并没有找到这个版本直接的具体修改方案,但是却提供了思路.最后我是填了一些坑,完成了任务,然后我还把这些代码都集成到一个库中.

因此本文重点是讲如何填坑

  • 扫描预览走样
  • 扫描框走样

源码地址 使用方法都在github的README上面了,所以本文并不会再次讲解.

正文

Zxing 扫描走样

先上一个图说明一下原来的问题:(因为整个手机截图的图片比较大,我就放上关键的地方)

图1和图2相比,在预览成像的后,图1中显示的比例不对,导致硬币的Y轴被拉长,而图2中的预览成像则是正常的 而我们的目的就是要把图1的效果改成图2

接下来就会讲解如何修改.

  • 思路: 首先这个形成的图像是用Camera来实现的.

在zxing的camera包下可以看到这些类. 可以推测出

1
2
3
CameraConfigurationManager
CameraConfigurationUtils
CameraManager

以上三个类是比较关键的.

在CameraConfigurationManager中的initFromCameraParameters方法里(仅展示重要的地方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void initFromCameraParameters(OpenCamera camera) {
					.
					.
					.                           
    Point theScreenResolution = new Point();
    display.getSize(theScreenResolution);
    screenResolution = theScreenResolution;
    Log.i(TAG, "Screen resolution in current orientation: " + screenResolution);
    cameraResolution = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
    Log.i(TAG, "Camera resolution: " + cameraResolution);
    bestPreviewSize = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
    Log.i(TAG, "Best available preview size: " + bestPreviewSize);
					.
					.
					.
}

看到这里,可以明显的看到这里就是设置预览的分辨率的地方了. 这里先是获取到屏幕的分辨率,并把这个分辨率传入到CameraConfigurationUtils.findBestPreviewSizeValue方法里,以获取到最合适的预览大小 因此我们再进入到CameraConfigurationUtils.findBestPreviewSizeValue方法里面,对这个方法进行解释.(因为这个方法是关键的地方,以此全部拿出来进行解释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
  public static Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) {
    //在这里获取全部支持预览的分辨率
    List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
    if (rawSupportedSizes == null) {
      Log.w(TAG, "Device returned no supported preview sizes; using default");
      Camera.Size defaultSize = parameters.getPreviewSize();
      if (defaultSize == null) {
        throw new IllegalStateException("Parameters contained no preview size!");
      }
      return new Point(defaultSize.width, defaultSize.height);
    }

    // Sort by size, descending 对所有的分辨率进行排序
    List<Camera.Size> supportedPreviewSizes = new ArrayList<>(rawSupportedSizes);
    Collections.sort(supportedPreviewSizes, new Comparator<Camera.Size>() {
      @Override
      public int compare(Camera.Size a, Camera.Size b) {
        int aPixels = a.height * a.width;
        int bPixels = b.height * b.width;
        if (bPixels < aPixels) {
          return -1;
        }
        if (bPixels > aPixels) {
          return 1;
        }
        return 0;
      }
    });
     
     //这里会把所有支持预览的分辨率打印出来,这里可以方便进行调试	
    if (Log.isLoggable(TAG, Log.INFO)) {
      StringBuilder previewSizesString = new StringBuilder();
      for (Camera.Size supportedPreviewSize : supportedPreviewSizes) {
        previewSizesString.append(supportedPreviewSize.width).append('x')
            .append(supportedPreviewSize.height).append(' ');
      }
      Log.i(TAG, "Supported preview sizes: " + previewSizesString);
    }

    // 分辨率比例,这个一个关键点1
    double screenAspectRatio = screenResolution.y / (double) screenResolution.x;
//    double screenAspectRatio = screenResolution.x / (double) screenResolution.y;

    // Remove sizes that are unsuitable
    Iterator<Camera.Size> it = supportedPreviewSizes.iterator();
    while (it.hasNext()) {
      Camera.Size supportedPreviewSize = it.next();
      int realWidth = supportedPreviewSize.width;
      int realHeight = supportedPreviewSize.height;
      if (realWidth * realHeight < MIN_PREVIEW_PIXELS) {
        it.remove();
        continue;
      }

      boolean isCandidatePortrait = realWidth < realHeight;
      int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
      int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
      double aspectRatio = maybeFlippedWidth / (double) maybeFlippedHeight;
      double distortion = Math.abs(aspectRatio - screenAspectRatio);
      if (distortion > MAX_ASPECT_DISTORTION) {
        it.remove();
        continue;
      }

      if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {
        Point exactPoint = new Point(realWidth, realHeight);
        Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
        return exactPoint;
      }
    }

    // If no exact match, use largest preview size. This was not a great idea on older devices because
    // of the additional computation needed. We're likely to get here on newer Android 4+ devices, where
    // the CPU is much more powerful.
    if (!supportedPreviewSizes.isEmpty()) {
    //关键点2 ,这里是添加上去进行优化的.
      for (int i = 0;i<supportedPreviewSizes.size();i++){
        Camera.Size largestPreview = supportedPreviewSizes.get(i);
        if (largestPreview.width == screenResolution.y && largestPreview.height == screenResolution.x){
          Log.i(TAG, "Using same suitable preview size: " + largestPreview);
          return new Point(largestPreview.width, largestPreview.height);
        }
      }
      Camera.Size largestPreview = supportedPreviewSizes.get(0);
      Point largestSize = new Point(largestPreview.width, largestPreview.height);
      Log.i(TAG, "Using largest suitable preview size: " + largestSize);
      return largestSize;
    }

    // If there is nothing at all suitable, return current preview size
    Camera.Size defaultPreview = parameters.getPreviewSize();
    if (defaultPreview == null) {
      throw new IllegalStateException("Parameters contained no preview size!");
    }
//    Point defaultSize = new Point(defaultPreview.width, defaultPreview.height);
    Point defaultSize = new Point(defaultPreview.height, defaultPreview.width);
//    Log.i(TAG, "defaultPreview " + defaultPreview.width + " " + defaultPreview.height);
    Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize);
    return defaultSize;
  }

在这里解释一下在上述代码中注释上写的关键点1的部分

1
2
3
4
// 分辨率比例,这个一个关键点1(修改后)
    double screenAspectRatio = screenResolution.y / (double) screenResolution.x;
// 修改前
//    double screenAspectRatio = screenResolution.x / (double) screenResolution.y;

可以看到这里部分的代码我仅仅是调换x和y而已,但是就是这么简单就可以基本的修改了扫描走样的问题. 原因是,例如*在之前传入的分辨率Point(1080, 1920)和在接下来的分辨率对比获取最佳的分辨率的时候,刚好是相反的,具体看下log*

举个例子: 传入的是1080/1920=0.5625,而下面的代码计算的比例是 1920/1080=1.7777… 所以显示比例永远不对. 因此只要把x和y对换就可以了.在这样对比的时候,就能获取到分辨率比例一样的分辨率,从而解决预览走样的问题. 但是这样仅仅是第一步. 接下来讲解下关键点2 在算了传入的分辨率比例后,和支持的分辨率比例做对比,只要比例不同的就会移除. 但是问题来,例如: 3840x2160和1920x1080的比例是一样的. 而原本的代码是直接返回来了 supportedPreviewSizes.get(0); 所以导致比例一样,分辨率不一样,在形成图像上是不会有问题,但是会影响扫描的速度.在一些设备上会有很明显的差异!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 if (!supportedPreviewSizes.isEmpty()) {
    //关键点2 ,这里是添加上去进行优化的.
      for (int i = 0;i<supportedPreviewSizes.size();i++){
        Camera.Size largestPreview = supportedPreviewSizes.get(i);
        if (largestPreview.width == screenResolution.y && largestPreview.height == screenResolution.x){
          Log.i(TAG, "Using same suitable preview size: " + largestPreview);
          return new Point(largestPreview.width, largestPreview.height);
        }
      }
      Camera.Size largestPreview = supportedPreviewSizes.get(0);
      Point largestSize = new Point(largestPreview.width, largestPreview.height);
      Log.i(TAG, "Using largest suitable preview size: " + largestSize);
      return largestSize;
    }

Zxing 扫描框走样

这个问题我一开始也并没有发现,但是我经过测试过其他一些设备发现,在一些分辨率上,扫码框会严重变形,成一个长方形…(这个大家可以自行脑补一下,我就不上图了,刚好身边没有设备) 在ViewfinderView.java这个类中,在ondraw中有

1
2
 Rect frame = cameraManager.getFramingRect();
 Rect previewFrame = cameraManager.getFramingRectInPreview();

从这里看到扫描框的大小就是从cameraManager.getFramingRect()获取的.进入到这个具体的方法来分析一下 在这个类中可以看到设定宽高的最大最小值

1
2
3
4
5
    private static final int MIN_FRAME_WIDTH = 240;
    private static final int MAX_FRAME_WIDTH = 675; 

    private static final int MIN_FRAME_HEIGHT = 240;
    private static final int MAX_FRAME_HEIGHT = 675; // = 5/8 * 1080

进入到getFramingRect()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
     * Calculates the framing rect which the UI should draw to show the user where to place the
     * barcode. This target helps with alignment as well as forces the user to hold the device
     * far enough away to ensure the image will be in focus.
     *
     * @return The rectangle to draw on screen in window coordinates.
     */
    public synchronized Rect getFramingRect() {
        if (framingRect == null) {
            if (camera == null) {
                return null;
            }
            Point screenResolution = configManager.getScreenResolution();
            if (screenResolution == null) {
                // Called early, before init even finished
                return null;
            }
            Log.i(TAG, "getFramingRect: " + screenResolution);
//            width
            int width = findDesiredDimensionInRange(screenResolution.x, MIN_FRAME_WIDTH,
                    MAX_FRAME_WIDTH);
//            height
            int height = findDesiredDimensionInRange(screenResolution.y, MIN_FRAME_HEIGHT,
                    MAX_FRAME_HEIGHT);
            int min = width > height ? height : width ;
            Log.i(TAG, "findDesiredDimensionInRange: width" + width + " height "+height);
            int leftOffset = (screenResolution.x - min) / 2;
            int topOffset = (screenResolution.y - min) / 2;
            framingRect = new Rect(leftOffset, topOffset, leftOffset + min, topOffset + min);
            Log.i(TAG, "getFramingRect: "+framingRect);
            if (DEBUG)
                Log.d(TAG, "Calculated framing rect: " + framingRect + ", w: " + framingRect.width() + ", h: " + framingRect.height());
        }
        return framingRect;
    }

    private static int findDesiredDimensionInRange(int resolution, int hardMin, int hardMax) {
        int dim = 5 * resolution / 8; // Target 5/8 of each dimension
        if (dim < hardMin) {
            return hardMin;
        }
        if (dim > hardMax) {
            return hardMax;
        }
        return dim;
    }

首先按照上面的代码进行修改就可以解决扫描框走样的问题. 可以到上面的代码中计算width和height ,因为屏幕的宽和高是肯定不一样的,所以在源码是分别算入到Rect中,出来的效果肯定不可能是个正方形,因为,只有选择width和height较小值算入Rect后,出来的效果才是一个正方形.(这里涉及到具体的view一些知识就不细讲了),当然,这里是长方形还是正方形本身都是没有错的,都是看项目的具体需求来定,所以想要什么形状都可以按照这个思路来改~

注:

  1. 如果说想方便快速接入zxing的话,可以直接去源码地址 下载代码,先看看demo的部分,然后把zxing库整个放到项目中导入即可使用!十分快捷没有副作用!至于为什么没有打包,因为这个扫描的UI肯定是需要根据不同的项目要求来进行修改的,如果打包了就不方便根据需求自定义了.
  2. 如果想自己导入官方的zxing 的话,可以根据上述的思路进行修改即可.
  3. 若有任何问题和意见都可以在issues上留言给我.
This post is licensed under CC BY 4.0 by the author.