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?
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?
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.
You can do this without overlay views (which won't work in all situations).
Subclass ViewGroup, add the SurfaceView as the only child, then:
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 );
}
}
}
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>
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)
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
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>
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;
}
}
}
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);
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);