preview stretches in camera2 apis

5.7k views Asked by At

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.

4

There are 4 answers

2
Sira Lam On BEST ANSWER

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.

0
ajinkya On

I also faced similar situation, but this one line solved my problem

view_finder.preferredImplementationMode = PreviewView.ImplementationMode.TEXTURE_VIEW

in your xml:

<androidx.camera.view.PreviewView
    android:id="@+id/view_finder"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

For camera implementation using cameraX you can refer https://github.com/android/camera-samples/tree/master/CameraXBasic

1
Igor Kiulian On

That's a common problem on some devices. I've noticed it mostly on samsung. You may use a trick with setting transformation on your TextureView to make it centerCrop like ImageView behaviour

1
Test Code On

I figured out what was your poroblem. You were probably trying something like this:

textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int j) {
        cam.startPreview(surfaceTexture, i, j);
        cam.takePicture();
    }

    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) { }
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { return false; }
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { }
});