Search code examples
androidandroid-camerascreen-resolutionandroid-camera2android-textureview

preview stretches in camera2 apis


Following are the screenshots when using texture view in camera2 apis.In full screen the preview stretches,but it works when using lower resolution(second image). How to use this preview in full screen without stretching it.


Solution

  • Below answer assumes you are in portrait mode only.

    Your question is

    How to use the preview in full-screen without stretching it

    Let's break it down to 2 things:

    1. You want the preview to fill the screen
    2. The preview cannot be distorted

    First you need to know that this is logically impossible without crop, if your device's viewport has a different aspect ratio with any available resolution the camera provides.

    So I would assume you accept cropping the preview.

    Step 1: Get a list of available resolutions

    StreamConfigurationMap map = mCameraCharacteristics.get(
                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    if (map == null) {
        throw new IllegalStateException("Failed to get configuration map: " + mCameraId);
    }
    Size[] sizes = map.getOutputSizes(SurfaceTexture.class);
    

    Now you get a list of available resolutions (Sizes) of your device's camera.

    Step 2: Find the best aspect ratio

    The idea is to loop the sizes and see which one best fits. You probably need to write your own implementation of "best fits".

    I am not going to provide any code here since what I have is quite different from your use case. But ideally, it should be something like this:

    Size findBestSize (Size[] sizes) {
        //Logic goes here
    }
    

    Step 3: Tell the Camera API that you want to use this size

        //...
        textureView.setBufferSize(bestSize.getWidth(), bestSize.getHeight());
        Surface surface = textureView.getSurface();
        try {
            mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);
            mCamera.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                    mSessionCallback, null);
        } catch (final Exception e) {
            //...
        }
    

    Step 4: Make your preview extends beyond your viewport

    This is then nothing related to the Camera2 API. We "crop" the preview by letting the SurfaceView / TextureView extends beyond device's viewport.

    First place your SurfaceView or TextureView in a RelativeLayout.

    Use the below to extend it beyond the screen, after you get the aspect ratio from step 2.
    Note that in this case you probably need to know this aspect ratio before you even start the camera.

       //Suppose this value is obtained from Step 2.
        //I simply test here by hardcoding a 3:4 aspect ratio, where my phone has a thinner aspect ratio.
        float cameraAspectRatio = (float) 0.75;
    
        //Preparation
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        int screenWidth = metrics.widthPixels;
        int screenHeight = metrics.heightPixels;
        int finalWidth = screenWidth;
        int finalHeight = screenHeight;
        int widthDifference = 0;
        int heightDifference = 0;
        float screenAspectRatio = (float) screenWidth / screenHeight;
    
        //Determines whether we crop width or crop height
        if (screenAspectRatio > cameraAspectRatio) { //Keep width crop height
            finalHeight = (int) (screenWidth / cameraAspectRatio);
            heightDifference = finalHeight - screenHeight;
        } else { //Keep height crop width
            finalWidth = (int) (screenHeight * cameraAspectRatio);
            widthDifference = finalWidth - screenWidth;
        }
    
        //Apply the result to the Preview
        RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) cameraView.getLayoutParams();
        lp.width = finalWidth;
        lp.height = finalHeight;
        //Below 2 lines are to center the preview, since cropping default occurs at the right and bottom
        lp.leftMargin = - (widthDifference / 2);
        lp.topMargin = - (heightDifference / 2);
        cameraView.setLayoutParams(lp);
    

    If you don't care about the result of Step 2, you can actually ignore Step 1 to Step 3 and simply use a library out there, as long as you can configure its aspect ratio. (It looks like this one is the best, but I haven't tried yet)

    I have tested using my forked library. Without modifying any code of my library, I managed to make the preview fullscreen just by using Step 4:

    Before using Step 4:
    enter image description here

    After using Step 4:
    enter image description here

    And the preview just after taking a photo will not distort as well, because the preview is also extending beyond your screen.
    But the output image will include area that you cannot see in the preview, which makes perfect sense.

    The code of Step 1 to Step 3 are generally referenced from Google's CameraView.