Search code examples
androidmatrixandroid-cameraaspect-ratiomeasure

Camera PreviewView is stretched in some Android devices


I'm playing with the API2 Camera of Google and I'm having some problems with my code. I had two different CameraSessions, one for video and another one for images. To do it more efficient I change the code to use a unique Session and make the app more efficient.

After I did this, my camera preview is not working adequately. When I'm using a 4:3 aspect ratio my preview become stretched at height. In other way it looks fine when I'm using 16:9 ratio. In both cases my pictures looks fine, I mean, preview doesn't work correctly but the pictures that I took, have the correct aspect ratio.

I already check different post with the same problem: Camera Preview Stretched on Few Android Devices

Camera display / preview in full screen does not maintain aspect ratio - image is skewed, stretched in order to fit on the screen

But the different answers didn't help me. I know that the problem is inside my onMeasure(), setTransformMatrix() or OnLayoutChangeListener() methods, but I don't know what I'm doing wrong.

Ignore the code about Rotation, right now it's dynamic. It always enter at else condition.

Here is my code:

private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right,
                               int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {

        Log.d(TAG, "[onLayoutChange] " + mCameraUI.getTextureView().getMeasuredWidth() + "x" + mCameraUI.getTextureView().getMeasuredHeight());

        int width = right - left;
        int height = bottom - top;
        if (mPreviewWidth != width || mPreviewHeight != height
                || (mOrientationResize != mPrevOrientationResize)
                || mAspectRatioResize || mOrientationChanged) {

            Log.i(TAG, "[onLayoutChange] Layout changed");
            mPreviewWidth = width;
            mPreviewHeight = height;
            Log.i(TAG, "[onLayoutChange] Preview size: "+ mPreviewWidth + "x" + mPreviewHeight);
            setTransformMatrix(width, height);
            mController.onScreenSizeChanged((int) mSurfaceTextureUncroppedWidth,
                    (int) mSurfaceTextureUncroppedHeight);
            mAspectRatioResize = false;
            mOrientationChanged = true;
        }
    }
};

setTransform

 private void setTransformMatrix(int width, int height) {


    Log.i(TAG, "Screen: " + mPreviewWidth + "x" + mPreviewHeight);

    mMatrix = new Matrix();
    //mCameraUI.getTextureView().getTransform(mMatrix);
    float scaleX = 1f, scaleY = 1f;
    float scaledTextureWidth, scaledTextureHeight;

     mAspectRatio= (float)height/(float)width;
    if (mAspectRatio==(4f / 3f)){

            scaledTextureWidth = Math.max(width,
                    (int) (height / mAspectRatio));
            scaledTextureHeight = Math.max(height,
                    (int) (width * mAspectRatio));
        Log.i(TAG, "[PhotoUIManager]: Aspect Ratio 4:3=" + scaledTextureWidth + "x" + scaledTextureHeight );
    }
    else{
        scaledTextureWidth = Math.max(width,
                (int) (height / mAspectRatio));
        scaledTextureHeight = Math.max(height,
                (int) (width * mAspectRatio));
        Log.i(TAG, "[PhotoUIManager]: Aspect Ratio 16:9=" + scaledTextureWidth + "x" + scaledTextureHeight );
    }       


    if (mSurfaceTextureUncroppedWidth != scaledTextureWidth || mSurfaceTextureUncroppedHeight != scaledTextureHeight) {
        Log.e(TAG,"mi SurfaceWidth = " + mSurfaceTextureUncroppedWidth + "and mi scaledWidth=" + scaledTextureWidth);
        Log.e(TAG,"mi SurfaceHeigh = " + mSurfaceTextureUncroppedHeight + "and mi scaledHeight=" + scaledTextureHeight);
        mSurfaceTextureUncroppedWidth = scaledTextureWidth;
        mSurfaceTextureUncroppedHeight = scaledTextureHeight;
        Log.e(TAG,"Surfaces: " + mSurfaceTextureUncroppedWidth + "x" + mSurfaceTextureUncroppedHeight);
        if (mSurfaceTextureSizeListener != null) {
            mSurfaceTextureSizeListener.onSurfaceTextureSizeChanged(
                    (int) mSurfaceTextureUncroppedWidth, (int) mSurfaceTextureUncroppedHeight);
        }
    }
    scaleX = scaledTextureWidth / width;
    scaleY = scaledTextureHeight / height;
    mMatrix.setScale(scaleX, scaleY, scaledTextureWidth/2, scaledTextureHeight/2);
    Log.e(TAG, "scale: X= " + scaleX + " Y=" + scaleY + "Width= " + scaledTextureWidth + "Height= " + scaledTextureHeight);

    // init the position (this seems to be necessary too when the ratio is 16/9
    mCameraUI.getTextureView().setX(0);
    mCameraUI.getTextureView().setY(0);

    // Translate the preview with the rotation is aspect ration is 4/3
    if (mAspectRatio == 4f / 3f) {
        Log.e(TAG, "aspect ratio standard");
        float verticalTranslateOffset = (mCameraUI.getTextureView().getMeasuredHeight() - scaledTextureHeight) / 2;
        float horizontalTranslateOffset = (mCameraUI.getTextureView().getMeasuredWidth() - scaledTextureWidth) / 2;
        int rotation = CameraUtil.getDisplayRotation(mActivity);
        switch (rotation) {
            case 0:
                // phone portrait; translate the preview up
                mCameraUI.getTextureView().setY(-verticalTranslateOffset);
                mFaceView.setStandardPreviewTranslationOffset(-verticalTranslateOffset);
                mFocusView.setStandardPreviewTranslationOffset(-verticalTranslateOffset);
                break;
            case 90:
                // phone landscape: translate the preview left
                mCameraUI.getTextureView().setX(-horizontalTranslateOffset);
                mFaceView.setStandardPreviewTranslationOffset(-horizontalTranslateOffset);
                mFocusView.setStandardPreviewTranslationOffset(-horizontalTranslateOffset);
                break;
            case 180:
                // phone upside down: translate the preview bottom
                mCameraUI.getTextureView().setY(verticalTranslateOffset);
                mFaceView.setStandardPreviewTranslationOffset(verticalTranslateOffset);
                mFocusView.setStandardPreviewTranslationOffset(verticalTranslateOffset);
                break;
            case 270:
                // reverse landscape: translate the preview right
                mCameraUI.getTextureView().setX(horizontalTranslateOffset);
                mFaceView.setStandardPreviewTranslationOffset(horizontalTranslateOffset);
                mFocusView.setStandardPreviewTranslationOffset(horizontalTranslateOffset);
                break;
        }

    } else {
        Log.e(TAG, "aspect ratio full");
        mFaceView.setStandardPreviewTranslationOffset(0);
        mFocusView.setStandardPreviewTranslationOffset(0);
    }

    mRenderOverlay.updateLayout();
    mCameraUI.getTextureView().setTransform(mMatrix);

    RectF previewRect = new RectF(0, 0, width, height);

    mController.onPreviewRectChanged(CameraUtil.rectFToRect(previewRect));

}

onMeasure

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);

    Log.d(TAG, "onMeasure PREVIOUS. Width x Height [" + widthMeasureSpec + " = " + width + "x" + heightMeasureSpec + " = " + height + "]");

    int rotation = ((Activity) getContext()).getWindowManager().getDefaultDisplay().getRotation();
    boolean isInHorizontal = Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation;

    int newWidth;
    int newHeight;

    if (isInHorizontal) {

        newHeight = getMeasuredHeight();
        newWidth = (int) (newHeight * mAspectRatio);
    } else {

        newWidth = getMeasuredWidth();
        newHeight = (int) (newWidth * mAspectRatio);
    }

    setMeasuredDimension(newWidth, newHeight);
    Log.d(TAG, "onMeasure. Width x Height [" + newWidth + "x" + newHeight + "]");

}

Solution

  • Solved!

    I had a default values for the BufferSize of my texture, which just are restarted when I inicializated a new Session or after change the after ratio. But the Width and Height values for the texture were not updated with the ratio, so it becomes streched again and again.

    I solved it changing my defaultbufferSize with the PreviewSizes which I update always that i change the ratio.

       public void createCameraPreviewSession(Size previewsize, Surface recordingSurface) {
        try {
            if (mCaptureSession != null) {
                mCaptureSession.stopRepeating();
                mCaptureSession.close();
                mCaptureSession = null;
            }
    
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            assert texture != null;
    
            List<Surface> surfaces = new ArrayList<Surface>();
    
            // We configure the size of default buffer to be the size of camera preview we want.
            texture.setDefaultBufferSize(previewsize.getWidth(),previewsize.getHeight());
            ;
            // This is the output Surface we need to start preview.
            Surface surface = new Surface(texture);
    .......