16

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?

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Anderson
  • 1,011
  • 2
  • 11
  • 24
  • you can change or crop the size of surfaceView instead of fullscreen view, that thing you want else you need to crop the preview of video itself. – Karthi Dec 22 '11 at 09:44
  • @Anderson are you talking about image or video? – Nikunj Patel Dec 22 '11 at 09:46
  • 2
    you can capture image and then crop. – Richa Dec 22 '11 at 09:55
  • Preview is a preview.... regardless if still or video. Capture and then crop and display would be way to slow for each frame. Besides that.... android does not offer any functionality to scale images. – Anderson Dec 22 '11 at 10:39
  • 2
    possible duplicate of [How to crop camera preview?](http://stackoverflow.com/questions/8593100/how-to-crop-camera-preview) – Sergey Glotov Jan 18 '12 at 09:41
  • possible duplicate of [Make a SurfaceView larger than the screen (Fitting a camera preview to a SurfaceView larger than the display)](http://stackoverflow.com/questions/17348614/make-a-surfaceview-larger-than-the-screen-fitting-a-camera-preview-to-a-surface) – Sam Apr 16 '14 at 11:05
  • For anyone finding this, there is a way to do this with TextureView. see Ruslan's answer here: http://stackoverflow.com/questions/17019588/crop-camera-preview-for-textureview – Sam Apr 28 '14 at 10:12

9 Answers9

19

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 );
    }
  }
}
momo
  • 3,885
  • 4
  • 33
  • 54
  • How to get `actualPreviewWidth` and `actualPreviewHeight`? getWidth() and getWidth() returns 0. My CroppedCameraPreview instance inflates from XML file and uses match_parent for both width and height. – lordmegamax Nov 21 '14 at 15:50
  • @lordmegamax these are supplied by you (the author). generally you'll find the best of a set of sizes that most closely match the available space with `Camera.getParameters().getSupportedPreviewSizes()` – momo Nov 23 '14 at 01:21
  • Thanks @momo. I also used the left and right parameters of the layout call to grab the center of the camera preview screen rather than the left side, i.e., cameraPreview.layout(offset, 0, actualPreviewWidth+offset, actualPreviewHeight) – jacob Mar 31 '15 at 02:43
  • @momo Mind to take a look at http://stackoverflow.com/questions/36943772/camera-draws-outside-the-view? – AndreKR Apr 29 '16 at 16:56
9

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>
steve_patrick
  • 735
  • 2
  • 9
  • 19
  • 2
    That's a nice approach. But shouldn't the SurfaceView have a fixed height that matches the camera's aspect ratio and is larger than the surrounding layout? Using match_parent will make it fill the fixed height of the ScrollView resulting in a clinched preview. – Father Stack Jul 18 '13 at 13:36
  • 1
    Yes, I'm doing that programmatically, as the SurfaceView should have the same ratio as the preview image and for that I have to ask the Camera. – steve_patrick Jul 18 '13 at 14:19
  • I could not get it to work, a few lines of code would have been nice. In my case horicontal scrolling was not possible. And on top, if the activity with the surfaceview is a Dialog theme scrolling the surfaceview caused the camera image to appear outside the dialog borders. (so it results in quite a graphical glitch for non fullscreen activities) – John Mar 22 '14 at 03:45
  • @steve_patrick I'm trying to do the same with TextureView, but it is not working and the camera preview is still squeezed , can you help me with this. I'm using textureView because I also need to set alpha value. – Bilal Rabbani Dec 11 '14 at 10:24
7

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.

Konstantin Pribluda
  • 12,329
  • 1
  • 30
  • 35
  • One can also force the size and position of the preview surface, to the effect of having it cropped. – Alex Cohn Dec 02 '12 at 06:58
  • Completely alternative approach is to use setPreviewTexture() instead of setPreviewDisplay() and control the cropping through OpenGL. – Alex Cohn Dec 02 '12 at 07:05
  • Another approach is to hide the preview surface and use setPreviewCallbackWithBuffer() and process the preview images on your own (see e.g. Google Hangouts app). – Alex Cohn Dec 02 '12 at 07:09
  • @AlexCohn Can you point me to some sample code showcasing one of the methods you talked about in your previous comments? I want to crop the center of the camera preview and show it in a fixed size SurfaceView. – Catalin Morosan Jun 06 '13 at 13:28
  • http://stackoverflow.com/questions/8601982/is-it-possible-to-crop-camera-preview/17702981#17702981 – steve_patrick Jul 17 '13 at 14:47
  • this is wrong, you can do this:, see: http://stackoverflow.com/questions/17348614/make-a-surfaceview-larger-than-the-screen-fitting-a-camera-preview-to-a-surface – Sam Apr 16 '14 at 11:05
  • Indirectly you are working on image taken from setPreviewCallback and then cropping that image using bitmap crop or any other algorithm. But the question can we crop the camera preview itself to fit in any surface. Life I want just 512x512 center portion of camera view? – Rohit Patil Mar 16 '21 at 09:14
0

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)

steve_patrick
  • 735
  • 2
  • 9
  • 19
0

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

dsrees
  • 6,116
  • 2
  • 26
  • 26
0

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>
Matan Dahan
  • 390
  • 5
  • 10
0

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;
        }
    }
}
TarikW
  • 359
  • 4
  • 12
0

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);
gmetax
  • 3,853
  • 2
  • 31
  • 45
0

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);
auslander
  • 470
  • 5
  • 17