是否可以裁剪相机预览?

Posted

技术标签:

【中文标题】是否可以裁剪相机预览?【英文标题】:Is it possible to crop camera preview? 【发布时间】:2012-01-25 23:35:16 【问题描述】:

我还没有找到任何方法来裁剪相机 ppreview,然后将其显示在 SurfaceView 上。

android -

【问题讨论】:

您可以更改或裁剪surfaceView的大小而不是全屏视图,否则您需要裁剪视频本身的预览。 @Anderson 你说的是图片还是视频? 您可以捕获图像然后裁剪。 Preview 是一个预览......不管是静态还是视频。捕获然后裁剪和显示将是每帧减慢的方式。除此之外.... android 不提供任何缩放图像的功能。 How to crop camera preview? 的可能重复项 【参考方案1】:

您可以在没有覆盖视图的情况下执行此操作(并非在所有情况下都有效)。

子类ViewGroup,添加SurfaceView为唯一子,则:

    在 onMeasure 中提供您想要的裁剪尺寸。 在 onLayout 中,使用未裁剪的尺寸布局 SurfaceView。

基本上,

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

【讨论】:

如何获取actualPreviewWidthactualPreviewHeight? getWidth() 和 getWidth() 返回 0。我的 CroppedCameraPreview 实例从 XML 文件膨胀,并使用 match_parent 作为宽度和高度。 @lordmegamax 这些由您(作者)提供。通常,您会在一组尺寸中找到与Camera.getParameters().getSupportedPreviewSizes()最接近的可用空间@ 谢谢@momo。我还使用了layout调用的left和right参数来抓取相机预览屏幕的中心而不是左侧,即cameraPreview.layout(offset, 0, actualPreviewWidth+offset, actualPreviewHeight) @momo 介意看看***.com/questions/36943772/…?【参考方案2】:

您可以将相机预览 (SurfaceView) 放在 ScrollView 内的 LinearLayout 中。当相机输出大于您设置的 LinearLayout 时,您可以以编程方式滚动它并禁用用户滚动。通过这种方式,您可以轻松模拟相机裁剪:

<ScrollView 
                     android:id="@+id/scrollView"
                     android:layout_
                     android:layout_
                     android:scrollbars="none"
                     android:fillViewport="true">

                        <LinearLayout
                                android:id="@+id/linearLayoutBeautyContent"
                                android:layout_
                                android:layout_
                                android:orientation="vertical">

                                <SurfaceView
                                            android:layout_
                                            android:layout_
                                            android:id="@+id/surfaceViewBeautyCamera"/>
                      </LinearLayout>
</ScrollView>

【讨论】:

这是一个不错的方法。但是 SurfaceView 不应该有一个与相机的纵横比相匹配并且大于周围布局的固定高度吗?使用 match_parent 将使其填充 ScrollView 的固定高度,从而获得固定的预览。 是的,我正在以编程方式执行此操作,因为 SurfaceView 应该与预览图像具有相同的比例,为此我必须询问相机。 我无法让它工作,几行代码会很好。在我的情况下,水平滚动是不可能的。最重要的是,如果表面视图的活动是一个对话框主题,滚动表面视图会导致相机图像出现在对话框边界之外。 (因此它会导致非全屏活动出现相当大的图形故障) @steve_patrick 我正在尝试对 TextureView 做同样的事情,但它不起作用并且相机预览仍然被挤压,你能帮我解决这个问题吗?我使用的是 textureView,因为我还需要设置 alpha 值。【参考方案3】:

不直接。 Camera API 现在允许偏移,并将图像压缩到表面 持有者。但是您可以通过在其上放置叠加层(其他视图)来解决问题。

【讨论】:

还可以强制调整预览表面的大小和位置,以达到裁剪的效果。 完全替代方法是使用 setPreviewTexture() 代替 setPreviewDisplay() 并通过 OpenGL 控制裁剪。 另一种方法是隐藏预览表面并使用 setPreviewCallbackWithBuffer() 并自行处理预览图像(参见例如 Google Hangouts 应用程序)。 @AlexCohn 您能否指出一些示例代码,展示您在以前的 cmets 中谈到的一种方法?我想裁剪相机预览的中心并将其显示在固定大小的 SurfaceView 中。 ***.com/questions/8601982/…【参考方案4】:

以编程方式滚动图像的代码如下所示:

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();
    

我从相机中为我的相机内容框计算最佳预览尺寸(比例 16:9),然后将计算的比例应用于图像以保持相同的比例,最后计算所需的滚动(图像将是中间)

【讨论】:

【参考方案5】:

创建一个居中的框架布局,它将存储相机预览并将其与视图叠加以“裁剪”它。创建视图时,动态拉伸一个居中的透明视图。

XML:

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

<FrameLayout 
    android:id="@+id/camera_preview_frame"
    android:layout_
    android:layout_
    android:layout_centerVertical="true" />

<View
    android:id="@+id/transparent_window"
    android:layout_
    android:layout_
    android:layout_centerVertical="true"
    android:background="@android:color/transparent" />

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

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

然后在您的活动类的 OnCreate() 方法中,您可以像这样拉伸透明视图。

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();
    
);

这是我手机上的screenshot。中间的灰色斑点是相机通过透明 View 看到的地板

【讨论】:

适用于纵向,在方向改变时失败..无论如何都要投票!【参考方案6】:

这是一个方向 = 横向的解决方案,以完成 @drees 的出色回答。

只需在 res 文件夹中添加 layout-land 文件夹,复制整个布局 xml 并将 @drees 代码的布局部分更改为如下所示:

<RelativeLayout
    android:layout_
    android:layout_
    android:background="@android:color/background_dark" >

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

    <View
        android:id="@+id/transparent_window"
        android:layout_
        android:layout_
        android:layout_centerInParent="true"
        android:background="@android:color/transparent" />

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

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

【讨论】:

【参考方案7】:

此解决方案是针对您的情况的更多解决方法之一。一些代码已被弃用并且不推荐在企业项目中使用,但如果您只需要在不挤压的情况下显示相机预览就足够了。 如果您需要在预览之前处理图像,那么您应该查看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 + ", , 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】:

在过去几天看了很多之后,我相信我需要在这里发布我的解决方案。

我唯一能做到并且效果很好的就是添加一个比例。

我想用相机输出的一部分创建一个纹理视图,但是如果不让预览被缩放,我就无法做到。

所以在我决定了相机/屏幕比例的最佳分辨率以开始捕捉后,我得到了相机捕捉高度和我想要显示的高度之间的比例。

mPreviewSize = chooseOptimalSize(...);

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

【讨论】:

【参考方案9】:

假设你有你的 Rect 或 RecF 这是你的计算

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

【讨论】:

以上是关于是否可以裁剪相机预览?的主要内容,如果未能解决你的问题,请参考以下文章

如何方形裁剪 Flutter 相机预览

裁剪/剪辑相机预览以仅显示图像的上半部分

如何在不使用内部相机裁剪的情况下裁剪图像

即时裁剪图像

Android:拍摄的照片比预览大

Android 相机预览帧时间戳