Is it possible to crop camera preview?

2019-04-08 04:12发布

问题:

I have not found any way to crop camera ppreview and then display it on the SurfaceView.

Android - Is it possible to crop camera preview?

回答1:

Not directly. Camera API does now allow for offsets, and will squeeze image into surface holder. But you can work around by placing overlays (other views) over it.



回答2:

You can do this without overlay views (which won't work in all situations).

Subclass ViewGroup, add the SurfaceView as the only child, then:

  1. in onMeasure supply the cropped dimensions you want.
  2. in onLayout, layout the SurfaceView with the un-cropped dimensions.

basically,

public class CroppedCameraPreview extends ViewGroup {
  private SurfaceView cameraPreview;
  public CroppedCameraPreview( Context context ) {
    super( context );
    // i'd probably create and add the SurfaceView here, but it doesn't matter
  }
  @Override
  protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec ) {
    setMeasuredDimension( croppedWidth, croppedHeight );
  }
  @Override
  protected void onLayout( boolean changed, int l, int t, int r, int b ) {
    if ( cameraPreview != null ) {
      cameraPreview.layout( 0, 0, actualPreviewWidth, actualPreviewHeight );
    }
  }
}


回答3:

You could put the camera preview (SurfaceView) inside a LinearLayout that is inside a ScrollView. When the camera output is bigger than the LinearLayout you set you can programmatically scroll it and disable user scroll. This way you can emulate camera cropping in an easy way:

<ScrollView 
                     android:id="@+id/scrollView"
                     android:layout_width="640dip"
                     android:layout_height="282dip"
                     android:scrollbars="none"
                     android:fillViewport="true">

                        <LinearLayout
                                android:id="@+id/linearLayoutBeautyContent"
                                android:layout_width="fill_parent"
                                android:layout_height="fill_parent"
                                android:orientation="vertical">

                                <SurfaceView
                                            android:layout_width="match_parent"
                                            android:layout_height="match_parent"
                                            android:id="@+id/surfaceViewBeautyCamera"/>
                      </LinearLayout>
</ScrollView>


回答4:

The code to programmatically scroll the image would be something like this:

public void setCameraOrientationOnOpen() 
    {   
        mCamera.stopPreview();
        int rotation = getRotation();
        Camera.Parameters currentCameraParameters = mCamera.getParameters();
        List<Camera.Size> previewSizes = currentCameraParameters.getSupportedPreviewSizes();

        mOptimalCameraSize = getOptimaPreviewCameraSize(previewSizes, (double)9/16);            
        currentCameraParameters.setPreviewSize(mOptimalCameraSize.width, mOptimalCameraSize.height);
        mCamera.setParameters(currentCameraParameters);
        float ratio = 100;
        int ratio1 = (mSurfaceView.getLayoutParams().height * 100) / mOptimalCameraSize.width; //height
        int ratio2 = (mSurfaceView.getLayoutParams().width * 100) / mOptimalCameraSize.height; //width 
        ratio = Math.max(ratio1, ratio2);
        mSurfaceView.getLayoutParams().height = (int) ((mOptimalCameraSize.width * ratio) / 100);
        mSurfaceView.getLayoutParams().width = (int) ((mOptimalCameraSize.height * ratio) / 100);
        if(ratio > 100)
        {
            int offset = (mSurfaceView.getLayoutParams().height - mBoxHeight)/2;
            mScrollView.scrollTo(0, offset); //center the image
        }
        mCamera.setDisplayOrientation(rotation);
        mOptimalCameraSize = mCamera.getParameters().getPreviewSize();
    }

I calculate the best preview size from the camera for my camera content box (ratio 16:9), then apply the calculated ratio to the image in order to keep the same ratio and finally calculate the needed scroll (the image would be on the middle)



回答5:

Create a centered Frame Layout that will store the Camera Preview and overlay it with Views to "crop" it. When you create your view, dynamically stretch a transparent view that is centered as well.

XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000" >

<FrameLayout 
    android:id="@+id/camera_preview_frame"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_centerVertical="true" />

<View
    android:id="@+id/transparent_window"
    android:layout_width="fill_parent"
    android:layout_height="50dip"
    android:layout_centerVertical="true"
    android:background="@android:color/transparent" />

<View 
    android:id="@+id/black_top_box"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_above="@id/transparent_window"
    android:background="#000"/>

<View 
    android:id="@+id/black_bottom_box"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_below="@id/transparent_window"
    android:background="#000"/>  
</RelativeLayout>

Then in the OnCreate() method of your activity class you can stretch the transparent view like this.

CameraActivity.java

final View transView = findViewById(R.id.transparent_window);

transView.post(new Runnable() {

    @Override
    public void run() {
        RelativeLayout.LayoutParams params;
        params = (RelativeLayout.LayoutParams) transView.getLayoutParams();
        params.height = transView.getWidth();
        transView.setLayoutParams(params);
        transView.postInvalidate();
    }
});

This is a screenshot from my phone of this. The gray blob in the middle is the camera's view of the floor through the transparent View



回答6:

Here is a solution for orientation = landscape to complete @drees' excellent answer.

Just add layout-land folder in res folder, duplicate your entire layout xml and change the layout part of @drees' code to be like this:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/background_dark" >

    <FrameLayout
        android:id="@+id/frameSurface"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:background="@android:color/background_light"/>

    <View
        android:id="@+id/transparent_window"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_centerInParent="true"
        android:background="@android:color/transparent" />

    <View
        android:id="@+id/black_top_box"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_toLeftOf="@id/transparent_window"
        android:background="@android:color/background_dark"/>

    <View
        android:id="@+id/black_bottom_box"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_toRightOf="@id/transparent_window"
        android:background="@android:color/background_dark"/>
</RelativeLayout>


回答7:

This solution is one of more of work arounds for your situation. Some code is deprecated and not recommended to use in enterprise projects, but if you need just show a camera preview without squeeze it's enough.
If you need handle image before preview then you should look SurfaceTexture

public class CameraPreview
        extends SurfaceView
        implements SurfaceHolder.Callback, Camera.PreviewCallback {

    public static final String TAG = CameraPreview.class.getSimpleName();

    private static final int PICTURE_SIZE_MAX_WIDTH = 1280;
    private static final int PREVIEW_SIZE_MAX_WIDTH = 640;
    private static final double ASPECT_RATIO = 3.0 / 4.0;

    private Camera mCamera;
    private SurfaceHolder mHolder;
    private boolean mIsLive;
    private boolean mIsPreviewing;

    public CameraPreview(Context context) {
        super(context);
        init(context);
    }

    public CameraPreview(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = (int) (width / ASPECT_RATIO + 0.5);
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        //L.g().d(TAG, "onVisibilityChanged: visibility=" + visibility);
        if (mIsLive) {
            if (visibility == VISIBLE && !mIsPreviewing) {
                startCameraPreview();
            } else {
                stopCameraPreview();
            }
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        startCamera();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        stopCamera();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        //L.g().d(TAG, "surfaceChanged: format=" + format + ", width=" + w + ", height=" + h);
        if (mHolder.getSurface() == null || mCamera == null) return;
        mHolder = holder;
        try {
            mCamera.stopPreview();
        } catch (Exception ignored) {}
        try {
            mCamera.setPreviewDisplay(mHolder);
            if (mIsLive && mIsPreviewing) mCamera.startPreview();
        } catch (Exception ignored) {}
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {

        //work with camera preview

        if (mIsPreviewing) camera.setOneShotPreviewCallback(this);
    }

    private Camera.Size determineBestPreviewSize(Camera.Parameters parameters) {
        return determineBestSize(parameters.getSupportedPreviewSizes(), PREVIEW_SIZE_MAX_WIDTH);
    }

    private Camera.Size determineBestPictureSize(Camera.Parameters parameters) {
        return determineBestSize(parameters.getSupportedPictureSizes(), PICTURE_SIZE_MAX_WIDTH);
    }

    /**
     * This code I found in this repository
     * https://github.com/boxme/SquareCamera/blob/master/squarecamera/src/main/java/com/desmond/squarecamera/CameraFragment.java#L368
     */
    private Camera.Size determineBestSize(List<Camera.Size> sizes, int widthThreshold) {
        Camera.Size bestSize = null;
        Camera.Size size;
        int numOfSizes = sizes.size();
        for (int i = 0; i < numOfSizes; i++) {
            size = sizes.get(i);
            boolean isDesireRatio = (size.width / 4) == (size.height / 3);
            boolean isBetterSize = (bestSize == null) || size.width > bestSize.width;

            if (isDesireRatio && isBetterSize) {
                bestSize = size;
            }
        }
        if (bestSize == null) {
            return sizes.get(sizes.size() - 1);
        }
        return bestSize;
    }

    private void init(Context context) {
        mHolder = getHolder();
        mHolder.addCallback(this);
    }

    public void startCamera() {
        if (!mIsLive) {
            //L.g().d(TAG, "startCamera");
            mIsPreviewing = false;
            mCamera = Camera.open();
            if (mCamera != null) {
                try {
                    Camera.Parameters param = mCamera.getParameters();
                    Camera.Size bestPreviewSize = determineBestPreviewSize(param);
                    Camera.Size bestPictureSize = determineBestPictureSize(param);
                    param.setPreviewSize(bestPreviewSize.width, bestPreviewSize.height);
                    param.setPictureSize(bestPictureSize.width, bestPictureSize.height);
                    mCamera.setParameters(param);
                } catch (RuntimeException ignored) {}
                try {
                    mCamera.setDisplayOrientation(90);
                    mCamera.setPreviewCallback(this);
                    mCamera.setPreviewDisplay(mHolder);
                    mIsLive = true;
                } catch (Exception ignored) {}
            }
            //else L.g().d(TAG, "startCamera: error launching the camera");
        }
    }

    public void stopCamera() {
        if (mCamera != null && mIsLive) {
            //L.g().d(TAG, "stopCamera");
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
            mIsPreviewing = false;
            mIsLive = false;
        }
    }

    public void startCameraPreview() {
        if (mCamera != null && mIsLive && !mIsPreviewing) {
            //L.g().d(TAG, "startCameraPreview");
            mCamera.setPreviewCallback(this);
            mCamera.startPreview();
            mIsPreviewing = true;
        }
    }

    public void stopCameraPreview() {
        if (mCamera != null && mIsLive && mIsPreviewing) {
            //L.g().d("stopCameraPreview");
            mCamera.stopPreview();
            mIsPreviewing = false;
        }
    }
}


回答8:

after a lot of looking last few days I believe that I need to post my solution here.

the only that I have managed to do and it working good is to add a scale.

I wanted to create a textureview with a part of the camera output, but I couldn't do that without letting the preview to get scalled.

so after I decide what is the best resolution for the camera/screen ratio to start capturing I get the scale ratio between the camera capture height and the height I want to show.

mPreviewSize = chooseOptimalSize(...);

int viewHeight = getDP(this, R.dimen.videoCaptureHeight);
float scaleY = mPreviewSize.getHeight() / viewHeight;
setScaleY(scaleY);


回答9:

Assume you have your Rect or RecF here is your calculation

float imageWidth = bitmap.Width;
float imageHeight = bitmap.Height;
float width = yourscreenWidth;
float heigth =  yourscreenHeight ;           

var W= width / heigth / (imageWidth / imageHeight);
var W2 = rect.Width() / widt * W;
var H = rect.Height() / heigth;
var cropImageWidth = imageWidth * W2 ;
var cropImageHeight = imageHeight * H ;
var cropImageX = (imageWidth - cropImageWidth) / 2;
var cropImageY = (imageHeight - cropImageHeight) / 2;
Bitmap imageCropped = Bitmap.CreateBitmap(bitmap, (int)cropImageX, (int)cropImageY, (int)cropImageWidth, (int)cropImageHeight);