Search code examples
javaandroidandroid-camerasurfaceviewaspect-ratio

Android Camera Preview is stretched on Full screen


I'm using a Custom Camera for my app for AR View. The Camera preview is getting stretched on full screen portrait mode.

I have tried many solutions on stackoverflow but it's not working for my code. Can anyone please tell me how to fix this?

I have added my camera working code and a screenshot below where the preview is getting stretched.

This is my Custom Camera Class:

public class ARCamera extends ViewGroup implements SurfaceHolder.Callback {
    private final static float Z_NEAR = 0.5f;
    private final static float Z_FAR = 2000;
    private final String TAG = ARCamera.class.getSimpleName();
    SurfaceView surfaceView;
    SurfaceHolder surfaceHolder;
    Camera camera;
    Activity activity;
    float[] projectionMatrix = new float[16];
    int cameraWidth;
    int cameraHeight;
    int screenWidth, screenHeight;
    int rotation;

    public ARCamera(Context context, SurfaceView surfaceView) {
        super(context);

        this.surfaceView = surfaceView;
        this.activity = (Activity) context;
        surfaceHolder = this.surfaceView.getHolder();
        surfaceHolder.addCallback(this);

        DisplayMetrics displayMetrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);

        rotation = windowManager.getDefaultDisplay()
                .getRotation();

        this.screenWidth = displayMetrics.widthPixels;
        this.screenHeight = displayMetrics.heightPixels;


    }

    public void setCamera(Camera camera) {
        this.camera = camera;

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

    }

    public void surfaceCreated(SurfaceHolder holder) {
        try {
            if (camera != null) {

                Camera.CameraInfo info = new Camera.CameraInfo();
                Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);

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

                camera.setDisplayOrientation((info.orientation - degrees + 360) % 360);

                camera.setPreviewDisplay(holder);
            }
        } catch (IOException exception) {
            Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        if (camera != null) {
            camera.setPreviewCallback(null);
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (camera != null) {
            this.cameraWidth = width;
            this.cameraHeight = height;

            Camera.Parameters parameters = camera.getParameters();
            try {
                List<Camera.Size> supportedSizes = camera.getParameters().getSupportedPreviewSizes();
                for (Camera.Size element : supportedSizes) {
                    element.width -= cameraWidth;
                    element.height -= cameraHeight;
                }
                Collections.sort(supportedSizes, new ResolutionOrders());
                parameters.setPreviewSize(width + supportedSizes.get(supportedSizes.size() - 1).width, height + supportedSizes.get(supportedSizes.size() - 1).height);
            } catch (Exception ex) {
                parameters.setPreviewSize(screenWidth, screenHeight);
            }

            this.camera.setParameters(parameters);
            this.camera.startPreview();

            generateProjectionMatrix();
        }
    }

    private void generateProjectionMatrix() {
        float ratio = (float) this.screenWidth / this.screenHeight;
        final int OFFSET = 0;
        final float LEFT = -ratio;
        final float RIGHT = ratio;
        final float BOTTOM = -1;
        final float TOP = 1;
        Matrix.frustumM(projectionMatrix, OFFSET, LEFT, RIGHT, BOTTOM, TOP, Z_NEAR, Z_FAR);
    }

    public float[] getProjectionMatrix() {
        return projectionMatrix;
    }

    class ResolutionOrders implements java.util.Comparator<Camera.Size> {
        public int compare(Camera.Size left, Camera.Size right) {
            return Float.compare(left.width + left.height, right.width + right.height);
        }
    }

}

enter image description here


Solution

  • In the surface changed block, you have to choose the optimal size according to your device programmatically.

    List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes();
    

    this will give you total supported camera sizes. you have to choose appropriate size from this. I have use the function called "chooseOptimalSize".

    Size aspectRatio = new Size(matrix.widthPixels, matrix.heightPixels);
    
    Camera.Size previewSize = chooseOptimalSize(previewSizes, PREFERRED_PREVIEW_WIDTH, PREFERRED_PREVIEW_HEIGHT, aspectRatio, this);
    

    This is the function "chooseOptimalSize":-

    public static Camera.Size chooseOptimalSize(List<Camera.Size> choices, int width, int height, Size aspectRatio, Context c) {
            // Collect the supported resolutions that are at least as big as the preview Surface
            List<Size> bigEnough = new ArrayList<>();
            int w = aspectRatio.getWidth();
            int h = aspectRatio.getHeight();
            double ratio = (double) h / w;
            int loopCounter=0;
            for (Camera.Size size : choices) {
                int orientation = c.getResources().getConfiguration().orientation;
                if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                    //if((size.getWidth()/16) == (size.getHeight()/9) && size.getWidth() <=720) {
                    //if((size.getWidth()/16) == (size.getHeight()/9) && size.getWidth() <=3840 ) {
                    //if((size.getWidth()/16) == (size.getHeight()/9) && size.getWidth() <=5120 ) {//Retina 5K
                    if((size.width/16) == (size.height/9) && size.width <=7680 ) {//8K UHDTV Super Hi-Vision
                        Log.e(TAG, "chooseOptimalSize:"+size+"--LoopPosition---==>"+loopCounter);
                        return size;
                    }
                } else {
                    Log.e(TAG, "chooseOptimalSize:--given--"+size);
                    if((size.width/16) == (size.height/9) && ((size.width <=1280)||(size.height<=1920))) {
                        //if((size.getWidth()/16) == (size.getHeight()/9) && (size.getWidth() <=4320 ) ) {//8K UHDTV Super Hi-Vision
                        //if((size.getWidth()/16) == (size.getHeight()/9) && (size.getWidth() <=2880 ) ) {//Retina 5K
                        //if((size.getWidth()/16) == (size.getHeight()/9) && (size.getWidth() <=2160 ) ) {
                        //if((size.getWidth()/16) == (size.getHeight()/9) && (size.getWidth() <=1280 ) ) {
                        //if((size.getWidth()/16) == (size.getHeight()/9) && (size.getWidth() <=4480 && size.getWidth() >=1280) ) {
                        Log.e(TAG, "chooseOptimalSize:"+size+"-16:9"+"--LoopPosition---==>"+loopCounter);
                        return size;
                    }else if((size.width/18) == (size.height/9) && ((size.width <=3840)||(size.height<=2160))) {
                        Log.e(TAG, "chooseOptimalSize:"+size+"-18:9"+"--LoopPosition---==>"+loopCounter);
                        return size;
                    }else if((size.width/18.5) == (size.height/9) && ((size.width <=3840)||(size.height<=2160))) {
                        Log.e(TAG, "chooseOptimalSize:"+size+"-18.5:9"+"--LoopPosition---==>"+loopCounter);
                        return size;
                    }else if((width/19) == (height/9) && ((width <=3840)||(height<=2160))) {
                        /*if((size.getWidth()/19) == (size.getHeight()/9) && ((size.getWidth() <=3840)||(size.getHeight()<=2160))) {*/
                        Log.e(TAG, "chooseOptimalSize:"+size+"-19:9"+"--LoopPosition---==>"+loopCounter);
                        return size;
                    }else if((size.width/19.5) == (size.height/9) && ((size.width<=3840)||(size.height<=2160))) {
                        Log.e(TAG, "chooseOptimalSize:"+size+"-19.5:9"+"--LoopPosition---==>"+loopCounter);
                        return size;
                    }else{
                        Log.e(TAG, "chooseOptimalSize"+" not proper aspect resolution");
                    }
                    //2340
                }
    
    //            if(screenWidth==size.getWidth()){
    //                Log.e(TAG1, loopCounter+".choose:width Matched:"+screenWidth+"="+size.getWidth());
    //            }else{
    //                Log.e(TAG1, loopCounter+".choose:width Not Matched:"+screenWidth+"="+size.getWidth());
    //            }
    //
    //            if(screenHeight==size.getHeight()){
    //                Log.e(TAG1, loopCounter+".choose:height Matched:"+screenHeight+"="+size.getHeight());
    //            }else{
    //                Log.e(TAG1, loopCounter+".choose:height Not Matched:"+screenHeight+"="+size.getHeight());
    //            }
                loopCounter++;
            }
            // Pick the smallest of those, assuming we found any
    //        if (bigEnough.size() > 0) {
    //            return Collections.min(bigEnough, new CompareSizesByArea());
    //        } else {
    //            Log.e(TAG, "Couldn't find any suitable preview size");
                return choices.get(0);
    //        }
        }
    
    
        /*
         * Compares two {@code Size}s based on their areas.
         */
        static class CompareSizesByArea implements Comparator<Size> {
            @Override
            public int compare(Size lhs, Size rhs) {
                // We cast here to ensure the multiplications won't overflow
                return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                        (long) rhs.getWidth() * rhs.getHeight());
            }
        }
    
    

    you can uncomment the code if you want in my case there is no need of that code.

    This function will give you optimal size of the device's camera. you can set that using :

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

    and you are done !!!