Android Camera Preview Stretch Issue

In the Mobile platform, the Camera is the most usable feature than the other features. Almost 80% of applications have embedded camera features and are used to capture images, video recording, or barcode scanning, etc. Nowadays the camera is used in a more advanced manner like Face recognition, Object recognition, Shape recognition, etc.

As a mobile application developer implementing the camera feature in the application is an easy thing. But most of the developers face the camera preview stretch issue, so we need to implement the camera feature in the most efficient way.

back camera stretch image

In one of our recent applications, we are also facing the same issue, and after spending a lot of time on the internet and reading multiple blogs found one working solution. We combined different answers to working functionality.

In this blog, we are going to solve the Camera Preview Stretch Issue, Captured Image Stretch Issue, Resize Camera Preview With Aspect Ratio, Captured Image Rotation Issue.

The most important bug in the Camera Preview Stretch Issue is the ratio given to display a camera preview. So if the preview ratio is not given in the right way then the camera preview will be going to stretch. Maybe it will work for some of the devices but definitely it will break in many of the devices. So the preview ratio plays the most important role in camera preview.

First, we will discuss how to display a Camera Preview In Full Screen? So while displaying the camera preview in full screen we need to keep in mind that most of the android devices don’t support the full-screen preview. You can see in the default camera application also has some space at the top and bottom, and they are using those spaces to display settings button and capture button.

We were also doing the same, we will find the optimal preview size to display camera preview and display in the middle of the screen. Let me share the code and we will discuss it in detail.

Related Post: Image Compression In Android

package com.vk.camerapreviewexample.camera;


import android.annotation.SuppressLint;

import android.app.Activity;

import android.content.Context;

import android.hardware.Camera;

import android.os.Handler;

import android.util.Log;

import android.view.Surface;

import android.view.SurfaceHolder;

import android.view.SurfaceView;


import com.vk.camerapreviewexample.exception.ExceptionUtility;


import java.util.List;


@SuppressLint("ViewConstructor")

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {


    private static final String TAG = "CameraPreview";


    private Context mContext;

    private SurfaceHolder mHolder;

    private Camera mCamera;

    public List<Camera.Size> mSupportedPreviewSizes;

    public List<Camera.Size> mSupportedPictureSizes;

    private Camera.Size mPreviewSize;

    public Camera.Size mPictureSize;

    Camera.PreviewCallback cameraPreviewCallBack;


    public CameraPreview(Context context, Camera camera, Camera.PreviewCallback cameraPreviewCallBack) {

        super(context);

        mContext = context;

        mCamera = camera;

        this.cameraPreviewCallBack = cameraPreviewCallBack;

        // supported preview sizes

        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();

        for (Camera.Size str : mSupportedPreviewSizes)

        // supported picture sizes

        mSupportedPictureSizes = mCamera.getParameters().getSupportedPictureSizes();

        // Install a SurfaceHolder.Callback so we get notified when the

        // underlying surface is created and destroyed.

        mHolder = getHolder();

        mHolder.addCallback(this);

        // deprecated setting, but required on Android versions prior to 3.0

        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    }


    public void surfaceCreated(SurfaceHolder holder) {

        // empty. surfaceChanged will take care of stuff

    }


    public void surfaceDestroyed(SurfaceHolder holder) {

    }


    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

        // If your preview can change or rotate, take care of those events here.

        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null) {

            // preview surface does not exist

            return;

        }


        // stop preview before making changes

        try {

            mCamera.stopPreview();

        } catch (Exception e) {

            ExceptionUtility.logError(TAG, "startCameraPreview", e);

        }


        // set preview size and make any resize, rotate or reformatting changes here

        // start preview with new settings

        try {

            Camera.Parameters parameters = mCamera.getParameters();

            List<String> focusModes = parameters.getSupportedFocusModes();

            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {

                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

            } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {

                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);

            } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {

                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);

            }

            parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);

            parameters.setPictureSize(mPictureSize.width, mPictureSize.height);

            mCamera.setParameters(parameters);

            mCamera.setDisplayOrientation(setCameraDisplayOrientation((Activity) mContext, 1));

            mCamera.setPreviewDisplay(mHolder);

            mCamera.setPreviewCallback(cameraPreviewCallBack);

            mCamera.startPreview();

        } catch (Exception e) {

            new Handler().postDelayed(() -> {

                try {

                    mCamera.startPreview();

                } catch (Exception ex) {

                    ExceptionUtility.logError(TAG, "After 2000 mils startCameraPreview:::::::::::::::::::::::::", ex);

                }

            }, 2000);

            ExceptionUtility.logError(TAG, "startCameraPreview", e);

        }

    }


    @Override

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);

        final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);


        if (mSupportedPreviewSizes != null) {

            mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);

        }

        if (mSupportedPictureSizes != null) {

            mPictureSize = getOptimalPreviewSize(mSupportedPictureSizes, width, height);

        }


        if (mPreviewSize != null) {

            float ratio;

            if (mPreviewSize.height >= mPreviewSize.width)

                ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;

            else

                ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;


            // One of these methods should be used, second method squishes preview slightly

            setMeasuredDimension(width, (int) (width * ratio));

            //        setMeasuredDimension((int) (width * ratio), height);

        }

    }


    public Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

        final double ASPECT_TOLERANCE = 0.1;

        double targetRatio = (double) h / w;


        if (sizes == null)

            return null;


        Camera.Size optimalSize = null;

        double minDiff = Double.MAX_VALUE;


        int targetHeight = h;


        for (Camera.Size size : sizes) {

            double ratio = (double) size.height / size.width;

            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)

                continue;


            if (Math.abs(size.height - targetHeight) < minDiff) {

                optimalSize = size;

                minDiff = Math.abs(size.height - targetHeight);

            }

        }


        if (optimalSize == null) {

            minDiff = Double.MAX_VALUE;

            for (Camera.Size size : sizes) {

                if (Math.abs(size.height - targetHeight) < minDiff) {

                    optimalSize = size;

                    minDiff = Math.abs(size.height - targetHeight);

                }

            }

        }


        return optimalSize;

    }


    public int setCameraDisplayOrientation(Activity activity, int cameraId) {

        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();

        android.hardware.Camera.getCameraInfo(cameraId, info);

        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();

        int degrees = 0;

        switch (rotation) {

            case Surface.ROTATION_0:

                degrees = 0;

                break;

            case Surface.ROTATION_90:

                degrees = 90;

                break;

            case Surface.ROTATION_180:

                degrees = 180;

                break;

            case Surface.ROTATION_270:

                degrees = 270;

                break;

        }


        int result;

        //int currentapiVersion = android.os.Build.VERSION.SDK_INT;

        // do something for phones running an SDK before lollipop

        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {

            result = (info.orientation + degrees) % 360;

            result = (360 - result) % 360; // compensate the mirror

        } else { // back-facing

            result = (info.orientation - degrees + 360) % 360;

        }

        return (result);

    }


    public Camera.Size getPictureSize() {

        return mPictureSize;

    }

}

CameraPreview.java

Created one class named CameraPreview.java extends SurfaceView implements SurfaceHolder.Callback. The first thing we are going to do is find out the supported preview sizes by

List<Camera.Size> mSupportedPreviewSizes = 
mCamera.getParameters().getSupportedPreviewSizes();

and supported picture sizes by

List<Camera.Size> mSupportedPictureSizes = 
mCamera.getParameters().getSupportedPictureSizes();

Now we are going to find out the Optimal preview size or the best fitting aspect ratio to display camera preview by

public Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
   final double ASPECT_TOLERANCE = 0.1;
   double targetRatio = (double) h / w;

   if (sizes == null)
       return null;

   Camera.Size optimalSize = null;
   double minDiff = Double.MAX_VALUE;

   int targetHeight = h;

   for (Camera.Size size : sizes) {
       double ratio = (double) size.height / size.width;
       if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
           continue;

       if (Math.abs(size.height - targetHeight) < minDiff) {
           optimalSize = size;
           minDiff = Math.abs(size.height - targetHeight);
       }
   }

   if (optimalSize == null) {
       minDiff = Double.MAX_VALUE;
       for (Camera.Size size : sizes) {
           if (Math.abs(size.height - targetHeight) < minDiff) {
               optimalSize = size;
               minDiff = Math.abs(size.height - targetHeight);
           }
       }
   }

   return optimalSize;
}

We are going to call the getOptimalPreviewSize() function in the overridden method named onMeasure() as

Camera.Size mPreviewSize = 

getOptimalPreviewSize(mSupportedPreviewSizes, width, height);

Camera.Size mPictureSize = 

getOptimalPreviewSize(mSupportedPictureSizes, width, height);
And will set the camera parameters as

Camera.Parameters parameters = mCamera.getParameters();

parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);

parameters.setPictureSize(mPictureSize.width, mPictureSize.height);

mCamera.setParameters(parameters);
Now we are done with our camera preview and picture stretch issue. So the question is how to use our CameraPreview class?. Here is the solution

<FrameLayout

   android:id="@+id/fl_camera_preview"

   android:layout_width="match_parent"

   android:layout_height="match_parent"

   android:layout_centerInParent="true"

   android:background="@color/colorBlack" >

</FrameLayout>

CameraPreview maPreview = new CameraPreview(context, mCamera, null);

activityFullScreenPreviewBinding.flCameraPreview.addView(maPreview);

FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) maPreview.getLayoutParams();

params.gravity = Gravity.CENTER;

Now we will discuss the Dynamic Resize Camera Preview. So to achieve dynamic resizing of the camera preview we don’t need to change anything in our CameraPreview.java class. but her display preview ratio is very important.

We will provide the height and width in the percentage of the device pixels but that is not sufficient. We need to find the best fitting ratio of given height-width and set it to display a camera preview. So to find the ratio of given height-width we are going to use one Utility function as

public static int[] getOptimalDimensions(float mediaWidth, float mediaHeight, int width, int height) {

        int layoutWidth = width;

        int layoutHeight = height;

        float ratioWidth = layoutWidth / mediaWidth;

        float ratioHeight = layoutHeight / mediaHeight;

        float aspectRatio = mediaWidth / mediaHeight;

        if (ratioWidth > ratioHeight) {

            layoutWidth = (int) (layoutHeight * aspectRatio);

        } else {

            layoutHeight = (int) (layoutWidth / aspectRatio);

        }

        Log.i(TAG, "layoutWidth: " + layoutWidth);

        Log.i(TAG, "layoutHeight: " + layoutHeight);

        Log.i(TAG, "aspectRatio: " + aspectRatio);

        return new int[]{layoutWidth, layoutHeight};

    }

getOptimalDimensions() in Utility.java class

The getOptimalDimensions() has 4 parameters. First 2 aremediaWidth and mediaHeight we will provide the device width pixels as mediaWidth and device height pixels as mediaHeight and for the next 2 parameters, we will provide the percentage pixels provided by the user as

DisplayMetrics displayMetrics = new DisplayMetrics();

((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);

int width = (int) (displayMetrics.widthPixels *((float)inputWidth/100));

int height = (int) (displayMetrics.heightPixels * ((float)inputHeight/100));

And we will set layout parameters as 

int optimalSize[] = Utility.getOptimalDimensions(mScreenWidth

       , mScreenHeight

       , width

       , height);

activityFullScreenPreviewBinding.flCameraPreview.getLayoutParams().width = optimalSize[0];

activityFullScreenPreviewBinding.flCameraPreview.getLayoutParams().height = optimalSize[1];

Here we are done with Camera Preview Stretch Issue, Captured Image Stretch Issue, Resize Camera Preview With Aspect Ratio. Take look for ViewModel code

package com.vk.camerapreviewexample.viewmodel;


import android.app.Activity;

import android.content.Context;

import android.hardware.Camera;

import android.os.Bundle;

import android.util.DisplayMetrics;

import android.view.Gravity;

import android.view.View;

import android.widget.FrameLayout;

import android.widget.Toast;


import com.vk.camerapreviewexample.camera.CameraPreview;

import com.vk.camerapreviewexample.exception.ExceptionUtility;

import com.vk.camerapreviewexample.databinding.ActivityFullScreenPreviewBinding;

import com.vk.camerapreviewexample.utility.Utility;


import java.io.File;

import java.io.FileOutputStream;


import static com.vk.camerapreviewexample.constant.Constant.FULL_SCREEN;

import static com.vk.camerapreviewexample.constant.Constant.INPUT_HEIGHT;

import static com.vk.camerapreviewexample.constant.Constant.INPUT_WIDTH;


public class CameraPreviewViewModel {


    private Camera mCamera;

    private String TAG = "FullScreenPreviewViewModel";

    private CameraPreview maPreview;

    private Context context;

    private ActivityFullScreenPreviewBinding activityFullScreenPreviewBinding;


    public CameraPreviewViewModel(Context context, ActivityFullScreenPreviewBinding activityFullScreenPreviewBinding) {

        this.context = context;

        this.activityFullScreenPreviewBinding = activityFullScreenPreviewBinding;

    }


    public void startCameraPreview() {

        try {

            Bundle extras = ((Activity) context).getIntent().getExtras();

            long inputWidth = 0;

            long inputHeight = 0;

            if (extras != null) {

                inputWidth=extras.getInt(INPUT_WIDTH,0);

                inputHeight=extras.getInt(INPUT_HEIGHT,0);

            }

            mCamera = getCameraInstance();

            maPreview = new CameraPreview(context, mCamera, null);

            activityFullScreenPreviewBinding.flCameraPreview.addView(maPreview);

            FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) maPreview.getLayoutParams();

            params.gravity = Gravity.CENTER;

            if(inputWidth>0&&inputHeight>0){

                setLayoutWH(inputWidth,inputHeight);

            }

        } catch (Exception exc) {

            ExceptionUtility.logError(TAG, "startCameraPreview", exc);

        }

    }


    private void setLayoutWH( long inputWidth,long inputHeight) {

        DisplayMetrics displayMetrics = new DisplayMetrics();

        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);

        int mScreenWidth = displayMetrics.widthPixels;

        int mScreenHeight = displayMetrics.heightPixels;

        int width = (int) (displayMetrics.widthPixels *((float)inputWidth/100));

        int height = (int) (displayMetrics.heightPixels * ((float)inputHeight/100));

        int optimalSize[] = Utility.getOptimalDimensions(mScreenWidth

                , mScreenHeight

                , width

                , height);

        activityFullScreenPreviewBinding.flCameraPreview.getLayoutParams().width = optimalSize[0];

        activityFullScreenPreviewBinding.flCameraPreview.getLayoutParams().height = optimalSize[1];

    }


    private Camera getCameraInstance() {

        if (mCamera == null)

            // mCamera = Camera.open(useBackCamera ? 0 : 1);

            mCamera = Camera.open(1);

        return mCamera;

    }


    public void captureImage() {

        activityFullScreenPreviewBinding.ivCapture.setOnClickListener((View view) -> {

            try {

                mCamera.takePicture(() -> {

                }, null, (final byte[] bytes, Camera camera) -> {

                    try {

                        mCamera.stopPreview();

                        saveImage(bytes);

                    } catch (Exception e) {

                        ExceptionUtility.logError(TAG, "takePicture", e);

                    }

                });

            } catch (Exception e) {

                ExceptionUtility.logError(TAG, "capturePhoto", e);

            }

        });

    }


    private void saveImage(final byte[] data) {

        //make a new picture file

        try {

            byte[] imageData = Utility.rotateImageData((Activity) context, data, 1);

            File userDIR = Utility.createExternalDirectory(FULL_SCREEN);

            File file = new File(userDIR, System.currentTimeMillis() + ".jpeg");

            FileOutputStream outPut = new FileOutputStream(file);

            outPut.write(imageData, 0, imageData.length);

            outPut.close();

            Toast.makeText(context, file.getAbsolutePath(), Toast.LENGTH_SHORT).show();

            ((Activity) context).finish();

        } catch (Exception e) {

            ExceptionUtility.logError(TAG, "saveImage", e);

        }

    }

}
CameraPreviewViewModel.java
For image rotation, we have a utility function
public static byte[] rotateImageData(Activity activity, byte[] data, int cameraId) throws Exception {

        Bitmap imageBitmap = null;

        // COnverting ByteArray to Bitmap - >Rotate and Convert back to Data

        if (data != null) {

            imageBitmap = BitmapFactory.decodeByteArray(data, 0, (data != null) ? data.length : 0);

            if (activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {

                Matrix mtx = new Matrix();

                int cameraEyeValue = setPhotoOrientation(activity, cameraId); // CameraID = 1 : front 0:back

                if (cameraId == 1) { // As Front camera is Mirrored so Fliping the Orientation

                    if (cameraEyeValue == 270) {

                        mtx.postRotate(90);

                    } else if (cameraEyeValue == 90) {

                        mtx.postRotate(270);

                    }

                } else {

                    mtx.postRotate(cameraEyeValue); // cameraEyeValue is default to Display Rotation

                }

                imageBitmap = Bitmap.createBitmap(imageBitmap, 0, 0, imageBitmap.getWidth(), imageBitmap.getHeight(), mtx, true);

            } else {// LANDSCAPE MODE

                //No need to reverse width and height

                Bitmap scaled = Bitmap.createScaledBitmap(imageBitmap, imageBitmap.getWidth(), imageBitmap.getHeight(), true);

                imageBitmap = scaled;

            }

        }

        // Converting the Die photo to Bitmap

        ByteArrayOutputStream stream = new ByteArrayOutputStream();

        imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);

        return stream.toByteArray();

    }

    private static int setPhotoOrientation(Activity activity, int cameraId) {

        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();

        android.hardware.Camera.getCameraInfo(cameraId, info);

        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();

        int degrees = 0;

        switch (rotation) {

            case Surface.ROTATION_0:

                degrees = 0;

                break;

            case Surface.ROTATION_90:

                degrees = 90;

                break;

            case Surface.ROTATION_180:

                degrees = 180;

                break;

            case Surface.ROTATION_270:

                degrees = 270;

                break;

        }

        int result;

        // do something for phones running an SDK before lollipop

        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {

            result = (info.orientation + degrees) % 360;

            result = (360 - result) % 360; // compensate the mirror

        } else { // back-facing

            result = (info.orientation - degrees + 360) % 360;

        }

        return result;

    }
coma

Conclusion

So this is how we solved the Camera Preview Stretch Issue, Captured Image Stretch Issue, Resize Camera Preview With Aspect Ratio, Captured Image Rotation Issue. The root of all issues is the height-width ratio to display the camera preview. Download the Full source code. Happy coding….!!

Content Team

This blog is from Mindbowser‘s content team – a group of individuals coming together to create pieces that you may like. If you have feedback, please drop us a message on contact@mindbowser.com

Keep Reading

Keep Reading

  • Service
  • Career
  • Let's create something together!

  • We’re looking for the best. Are you in?