The last article is mainly to master the overall steps of ZXing decoding, on the details of the code on a brush, this article will go into details, more detailed explanation of the knowledge of the camera configuration.

Initial configuration of ZXing camera

Look directly at the code and find the code that calls the camera initialization configuration. In the previous article, we looked at how to call the initCamera method in CaptureActivity

private void initCamera(SurfaceHolder surfaceHolder) {
    if (surfaceHolder == null) {
      throw new IllegalStateException("No SurfaceHolder provided");
    }
    // The camera is already on
    if (cameraManager.isOpen()) {
      Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
      return;
    }
    try {
      // Open the camera and initialize the hardware parameters
      cameraManager.openDriver(surfaceHolder);
      // instantiate a handler and start previewing.
      if (handler == null) {
        handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
      }
      decodeOrStoreSavedBitmap(null.null);
    } catch (IOException ioe) {
      Log.w(TAG, ioe);
      displayFrameworkBugMessageAndExit();
    } catch (RuntimeException e) {
      // Barcode Scanner has seen crashes in the wild of this variety:
      // java.? lang.? RuntimeException: Fail to connect to camera service
      Log.w(TAG, "Unexpected error initializing camera", e); displayFrameworkBugMessageAndExit(); }}Copy the code

Cameramanager.opendriver (surfaceHolder); Instead of going into the openDriver method to look at the code in detail, let’s take a look at the openDriver code as follows

 public synchronized void openDriver(SurfaceHolder holder) throws IOException {
    OpenCamera theCamera = camera;
    if (theCamera == null) {
      // Open the corresponding camera with requestedCameraId
      theCamera = OpenCameraInterface.open(requestedCameraId);
      if (theCamera == null) {
        throw new IOException("Camera.open() failed to return object from driver");
      }
      camera = theCamera;
    }
    // Whether it is initialized. If it is not initialized, initialize it
    if(! initialized) { initialized =true;
      configManager.initFromCameraParameters(theCamera);A / / analysis
      if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) {
        setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight);
        requestedFramingRectWidth = 0;
        requestedFramingRectHeight = 0;
      }
    }

    Camera cameraObject = theCamera.getCamera();
    Camera.Parameters parameters = cameraObject.getParameters();
    String parametersFlattened = parameters == null ? null : parameters.flatten(); // Save these, temporarily
    try {
      configManager.setDesiredCameraParameters(theCamera, false);
    } catch (RuntimeException re) {
      // Driver failed
      Log.w(TAG, "Camera rejected parameters. Setting only minimal safe-mode parameters");
      Log.i(TAG, "Resetting to saved camera params: " + parametersFlattened);
      // Reset:
      if(parametersFlattened ! =null) {
        parameters = cameraObject.getParameters();
        parameters.unflatten(parametersFlattened);
        try {
          cameraObject.setParameters(parameters);
          configManager.setDesiredCameraParameters(theCamera, true);
        } catch (RuntimeException re2) {
          // Well, darn. Give up
          Log.w(TAG, "Camera rejected even safe-mode parameters! No configuration");
        }
      }
    }
    cameraObject.setPreviewDisplay(holder);

  }
Copy the code

Here we focus on the code in the “analysis one” initFromCameraParameters method, as follows

void initFromCameraParameters(OpenCamera camera) {
    Camera.Parameters parameters = camera.getCamera().getParameters();
    WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    // Get the WindowManager default Display
    Display display = manager.getDefaultDisplay();
    // Screen rotation Angle
    int displayRotation = display.getRotation();

    int cwRotationFromNaturalToDisplay;
    switch (displayRotation) {
      case Surface.ROTATION_0:
        cwRotationFromNaturalToDisplay = 0;
        break;
      case Surface.ROTATION_90:
        cwRotationFromNaturalToDisplay = 90;
        break;
      case Surface.ROTATION_180:
        cwRotationFromNaturalToDisplay = 180;
        break;
      case Surface.ROTATION_270:
        cwRotationFromNaturalToDisplay = 270;
        break;
      default:
        // Have seen this return incorrect values like -90
        if (displayRotation % 90= =0) {
          cwRotationFromNaturalToDisplay = (360 + displayRotation) % 360;
        } else {
          throw new IllegalArgumentException("Bad rotation: " + displayRotation);
        }
    }
    Log.i(TAG, "Display at: " + cwRotationFromNaturalToDisplay);

    int cwRotationFromNaturalToCamera = camera.getOrientation();
    Log.i(TAG, "Camera at: " + cwRotationFromNaturalToCamera);

    // Still not 100% sure about this. But acts like we need to flip this:
    if (camera.getFacing() == CameraFacing.FRONT) {
      cwRotationFromNaturalToCamera = (360 - cwRotationFromNaturalToCamera) % 360;
      Log.i(TAG, "Front camera overriden to: " + cwRotationFromNaturalToCamera);
    }

    /* SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String overrideRotationString; if (camera.getFacing() == CameraFacing.FRONT) { overrideRotationString = prefs.getString(PreferencesActivity.KEY_FORCE_CAMERA_ORIENTATION_FRONT, null); } else { overrideRotationString = prefs.getString(PreferencesActivity.KEY_FORCE_CAMERA_ORIENTATION, null); } if (overrideRotationString ! = null && !" -".equals(overrideRotationString)) { Log.i(TAG, "Overriding camera manually to " + overrideRotationString); cwRotationFromNaturalToCamera = Integer.parseInt(overrideRotationString); } * /

    cwRotationFromDisplayToCamera =
        (360 + cwRotationFromNaturalToCamera - cwRotationFromNaturalToDisplay) % 360;
    Log.i(TAG, "Final display orientation: " + cwRotationFromDisplayToCamera);
    if (camera.getFacing() == CameraFacing.FRONT) {
      Log.i(TAG, "Compensating rotation for front camera");
      cwNeededRotation = (360 - cwRotationFromDisplayToCamera) % 360;
    } else {
      cwNeededRotation = cwRotationFromDisplayToCamera;
    }
    Log.i(TAG, "Clockwise rotation from display to camera: " + cwNeededRotation);

    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);

    boolean isScreenPortrait = screenResolution.x < screenResolution.y;
    boolean isPreviewSizePortrait = bestPreviewSize.x > bestPreviewSize.y;

    if (isScreenPortrait == isPreviewSizePortrait) {
      previewSizeOnScreen = bestPreviewSize;
    } else {
      previewSizeOnScreen = new Point(bestPreviewSize.y, bestPreviewSize.x);
    }
    Log.i(TAG, "Preview size on screen: " + previewSizeOnScreen);
  }
Copy the code

Although this method is a bit too much code, but because this method is used for the initial configuration of the camera, so, to analyze in detail, first look at this part of the code

Camera.Parameters parameters = camera.getCamera().getParameters();
    WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    // Get the WindowManager default Display
    Display display = manager.getDefaultDisplay();
    // Screen rotation Angle
    int displayRotation = display.getRotation();

    int cwRotationFromNaturalToDisplay;
    switch (displayRotation) {
      case Surface.ROTATION_0:
        cwRotationFromNaturalToDisplay = 0;
        break;
      case Surface.ROTATION_90:
        cwRotationFromNaturalToDisplay = 90;
        break;
      case Surface.ROTATION_180:
        cwRotationFromNaturalToDisplay = 180;
        break;
      case Surface.ROTATION_270:
        cwRotationFromNaturalToDisplay = 270;
        break;
      default:
        // Have seen this return incorrect values like -90
        if (displayRotation % 90= =0) {
          cwRotationFromNaturalToDisplay = (360 + displayRotation) % 360;
        } else {
          throw new IllegalArgumentException("Bad rotation: " + displayRotation);
        }
    }
    Log.i(TAG, "Display at: " + cwRotationFromNaturalToDisplay);

    int cwRotationFromNaturalToCamera = camera.getOrientation();
    Log.i(TAG, "Camera at: " + cwRotationFromNaturalToCamera);

    // Still not 100% sure about this. But acts like we need to flip this:
    if (camera.getFacing() == CameraFacing.FRONT) {
      cwRotationFromNaturalToCamera = (360 - cwRotationFromNaturalToCamera) % 360;
      Log.i(TAG, "Front camera overriden to: " + cwRotationFromNaturalToCamera);
    }

    cwRotationFromDisplayToCamera =
        (360 + cwRotationFromNaturalToCamera - cwRotationFromNaturalToDisplay) % 360;
    Log.i(TAG, "Final display orientation: " + cwRotationFromDisplayToCamera);
    if (camera.getFacing() == CameraFacing.FRONT) {
      Log.i(TAG, "Compensating rotation for front camera");
      cwNeededRotation = (360 - cwRotationFromDisplayToCamera) % 360;
    } else {
      cwNeededRotation = cwRotationFromDisplayToCamera;
    }
Copy the code

For those of you who have no experience in camera development, this code will be blind.Ok, let’s go step by step. Before we understand this code, we need to grasp the following concepts.

  • Screen coordinates: in Android, the top left corner of the screen is the origin (0,0) coordinates of the coordinate system. The origin goes to the right in the positive X direction, the origin goes down in the positive Y direction.
  • Natural orientation: Every device has a natural orientation, phones and tablets have different natural orientations. The natural orientation of the phone is portrait, and the natural orientation of the tablet is landscape.
  • Image Sensor direction: The Image data of the mobile phone camera comes from the Image Sensor of the camera hardware. After the Sensor is fixed to the mobile phone, it has a default view direction, as shown below. The coordinate origin is located in the upper left corner of the mobile phone when it is horizontal:

  • Camera image preview Orientation: Android provides an API to manually set the Camera preview orientation, called setDisplayOrientation. By default this value is 0, consistent with the image Sensor orientation, so there is no need to change the Camera preview orientation for landscape applications. However, if your app is portrait, you’ll have to use the API to rotate the Camera’s preview orientation 90 degrees to align it with the phone’s screen to get the correct preview.
  • Direction of photos captured by the camera: This has nothing to do with the preview direction of the camera. The direction of photos captured by the camera is consistent with the direction of the Image Sensor. If a portrait photo is taken and saved directly, the saved photos will be landscape.

I strongly suggest you take a look at this article Android: In fact, the function of the above code is to set the preview direction of the picture captured by the Camera, that is, whether the phone is horizontal or vertical screen, you see the image is consistent with the direction of the phone.

Set the best scale for camera preview images

In the previous part of the article, has analyzed ZXing set preview direction code, but only set preview direction is not enough, but also according to the width to height ratio of the screen to find the camera capture picture the most appropriate preview size, otherwise there will be a camera preview stretching deformation problem. Continue with the code in the initFromCameraParameters method, as follows

 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);

    boolean isScreenPortrait = screenResolution.x < screenResolution.y;
    boolean isPreviewSizePortrait = bestPreviewSize.x > bestPreviewSize.y;

    if (isScreenPortrait == isPreviewSizePortrait) {
      previewSizeOnScreen = bestPreviewSize;
    } else {
      previewSizeOnScreen = new Point(bestPreviewSize.y, bestPreviewSize.x);
    }
Copy the code

The screenResolution variable in the code above is the screenResolution, from which you can get the pixel values for the width and height of the screen. Let’s focus on these two lines of code

cameraResolution = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
bestPreviewSize = CameraConfigurationUtils.findBestPreviewSizeValue(parameters, screenResolution);
    
Copy the code

The code above is to get the best resolution for the camera, and the code below is to get the best preview size for the camera. Now to get the best size, the code for the findBestPreviewSizeValue method is as follows, okay

public static Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) {
    // Get the size supported by the camera, different phone will have different values
    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);
    }

    if (Log.isLoggable(TAG, Log.INFO)) {
      StringBuilder previewSizesString = new StringBuilder();
      for (Camera.Size size : rawSupportedSizes) {
        previewSizesString.append(size.width).append('x').append(size.height).append(' ');
      }
      Log.i(TAG, "Supported preview sizes: " + previewSizesString);
    }

    // This code is to get the ratio of screen width to height
    double screenAspectRatio = screenResolution.x / (double) screenResolution.y;

    // Find a suitable size, with max resolution
    int maxResolution = 0;
    Camera.Size maxResPreviewSize = null;
    // The for loop is used to find the right size and maximum resolution for the camera, here
    // Proper size means the same size as the aspect ratio of the screen.
    for (Camera.Size size : rawSupportedSizes) {
      int realWidth = size.width;
      int realHeight = size.height;
      int resolution = realWidth * realHeight;
      if (resolution < MIN_PREVIEW_PIXELS) {
        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) {
        continue;
      }
    // Find a size that matches the aspect ratio of the screen, otherwise use the camera's default size
      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;
      }

      // Resolution is suitable; record the one with max resolution
      if(resolution > maxResolution) { maxResolution = resolution; maxResPreviewSize = size; }}// 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(maxResPreviewSize ! =null) {
      Point largestSize = new Point(maxResPreviewSize.width, maxResPreviewSize.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);
    Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize);
    return defaultSize;
  }

Copy the code

From the comments in the code above you can see there are a few small problems, the logic of the above code is proportion with screen pixel size did not return to the same camera, otherwise, the default size with a camera, camera, the default size may be related to the size of the screen there is a difference than, the distortion will appear the preview image.

Here you can optimize your code to return the camera size that is closest to the aspect ratio of the screen. This optimization will be explained in more detail in a later article.

The code above is set the value of some variable, in the end, configure the parameters of the camera in the setDesiredCameraParameters CameraConfigurationManager classes, here are analyzed in detail.

Rotate the direction of the captured image

The collected photos are not processed here, and the collected photo data is still in horizontal screen, as follows

onPreviewFrame

@Override
  public void onPreviewFrame(byte[] data, Camera camera) {
    Point cameraResolution = configManager.getCameraResolution();
    Handler thePreviewHandler = previewHandler;
    if(cameraResolution ! =null&& thePreviewHandler ! =null) {
      Point screenResolution = configManager.getScreenResolution();
      Message message;
      if (screenResolution.x < screenResolution.y){
        // The phone is in portrait mode
        message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.y,
                cameraResolution.x, data);
      } else {
        // When the phone is landscape
        message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
                cameraResolution.y, data);
      }
      message.sendToTarget();
      previewHandler = null;
    } else {
      Log.d(TAG, "Got preview callback, but no handler or resolution available");
    }
Copy the code

Explanation: When the mobile phone is in portrait mode, the data collected by the camera sensor is in horizontal mode. In order to correspond with the portrait mode, the width and height of the pictures collected by the camera need to be changed. Here, the width and height are only changed, but the width and height of the data collected are not changed, so the width and height of the data need to be changed.

The following code

 // Convert the data from the original image sensor to portrait
    if (width < height) {
      // portrait
      byte[] rotatedData = new byte[data.length];
      for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++)
          rotatedData[y * width + width - x - 1] = data[y + x * height];
      }
      data = rotatedData;
    }

Copy the code

Add the above code to the start of the decode method in the DecodeHandler class.

Although the horizontal screen data collected by the camera has been converted to vertical screen, but the work is not finished, we still need to set the area to obtain the TWO-DIMENSIONAL code, the method of setting is the getFramingRectInPrevie method in the CameraManager class. Here I will subsidize the specific code, you according to the previous content and their own thinking to modify the code inside.

conclusion

This paper mainly analyzes the code of the camera configuration, the selection of the best size of the image and the processing of the data collected by the camera, the emphasis is to understand the Settings of the camera data collection and image preview. The modified code for this article is here.

Android Camera preview: What should I pay attention to

This article has been published by the public id AndroidShared

Scan the code to pay attention to the public number, reply “get information” have surprise