如何获得像Instagram一样1:1比例的Android Camera2?

15

我的问题非常简单:

如何获得一个Android android.hardware.Camera2,它具有1:1的比例且没有像Instagram那样的变形?

我已经尝试使用GoogeSamples项目android-Camera2Basic进行测试。但是当我将预览更改为1:1比例时,图像会变形。有人对此有想法吗?

enter image description here


1
"但是当我将预览比例更改为1:1时,图像会变形"--除非您提供了一个最小、完整和可验证的示例,否则我们无法帮助您调试代码。一般来说,答案是让SurfaceViewTextureView与相机拍摄的图像具有相同的纵横比,然后使用负边距或等效的渲染技巧,仅显示从相机中获取的正方形子集。 - CommonsWare
谢谢@CommonsWare。使用负边距修正了我的问题。我在回答中发布了我的代码。 - lopez.mikhael
@lopez.mikhael 你怎么改变了宽高比?看一下这个链接:https://dev59.com/D53ha4cB1Zd3GeqPVHkH - Ralph Bergmann
@lopez.mikhael:我想以类似的方式实现它,你是如何将纵横比设置为1:1的? - user2234
4个回答

6

感谢@CommonsWare。

我按照您的建议使用负边距(上下)并且它有效。

为了做到这一点,我只需更新AutoFitTextureView在GoogeSamples项目android-Camera2Basic中,就像这样:

public class AutoFitTextureView extends TextureView {

    //...
    private boolean mWithMargin = false;

    //...

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int margin = (height - width) / 2;

        if(!mWithMargin) {
            mWithMargin = true;
            ViewGroup.MarginLayoutParams margins = ViewGroup.MarginLayoutParams.class.cast(getLayoutParams());
            margins.topMargin = -margin;
            margins.bottomMargin = -margin;
            margins.leftMargin = 0;
            margins.rightMargin = 0;
            setLayoutParams(margins);
        }

        if (0 == mRatioWidth || 0 == mRatioHeight) {
            setMeasuredDimension(width, height);
        } else {
            if (width < height) {
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            } else {
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }
    }
}

enter image description here


4
您好!我有同样的问题。我添加了您实现的AutoFitTextureView,但仍然无法正常工作。能否请您提供更多代码,以便在我的应用中将相机预览和图片呈现为正方形?谢谢! - Stan Malcolm
当我这样做时,在预览底部会出现黑色条纹.. 有什么想法吗? - Dave
@lopez.mikhael 在你的代码中 super.onMeasure(widthMeasureSpec, widthMeasureSpec); 这一行是故意这样写的吗?还是第二个参数应该是高度?即使我将其更改,你的解决方案仍然无效。请提供一个更新后的答案。谢谢。 - A S M Sayem

1

对于任何想要这个的人,我尝试了上面的答案。添加一个边距来隐藏部分textureview以使其看起来是正方形,在预览中看起来很好。但是在保存图像时,您应该从输出图像中删除隐藏的区域。

更简单的解决方案是显示完整的textureview,并在其上覆盖一些其他布局以使其看起来是正方形。您可以轻松地从输出中裁剪图像。

您可以在此处找到示例代码。


1
我用布局实现了它,这样,Google代码可以保持原样,并自动根据UI设置一个1:1的预览。
    <android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_above="@id/footer"
    android:layout_below="@id/header">

    <com.example.android.camera2video.AutoFitTextureView
        android:id="@+id/texture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintDimensionRatio="w,1:1"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />

</android.support.constraint.ConstraintLayout>

只需将AutoFitTextureView放置在ConstraintLayout中,然后使用以下代码即可完成自适应预览:previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, videoSize);

这里的videoSize是什么? - Dipayan Ray

1
创建自定义纹理视图,如下所示:

public class AutoFitTextureView extends TextureView {

    private int mCameraWidth = 0;
    private int mCameraHeight = 0;
    private boolean mSquarePreview = false;

    public AutoFitTextureView(Context context) {
        this(context, null);
    }

    public AutoFitTextureView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setAspectRatio(int width, int height, boolean squarePreview) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Size cannot be negative.");
        }
        mCameraWidth = width;
        mCameraHeight = height;
        mSquarePreview = squarePreview;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (0 == mCameraWidth || 0 == mCameraHeight) {
            setMeasuredDimension(width, height);
        } else {
            /**
             * Vertical orientation
             */
            if (width < height) {
                if (mSquarePreview) {
                    setTransform(squareTransform(width, height));
                    setMeasuredDimension(width, width);
                } else {
                    setMeasuredDimension(width, width * mCameraHeight / mCameraWidth);
                }
            }
            /**
             * Horizontal orientation
             */
            else {
                if (mSquarePreview) {
                    setTransform(squareTransform(width, height));
                    setMeasuredDimension(height, height);
                } else {
                    setMeasuredDimension(height * mCameraWidth / mCameraHeight, height);
                }
            }
        }
    }

    private Matrix setupTransform(int sw, int sh, int dw, int dh) {
        Matrix matrix = new Matrix();
        RectF src = new RectF(0, 0, sw, sh);
        RectF dst = new RectF(0, 0, dw, dh);
        RectF screen = new RectF(0, 0, dw, dh);

        matrix.postRotate(-90, screen.centerX(), screen.centerY());
        matrix.mapRect(dst);

        matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
        matrix.mapRect(src);

        matrix.setRectToRect(screen, src, Matrix.ScaleToFit.FILL);
        matrix.postRotate(-90, screen.centerX(), screen.centerY());

        return matrix;
    }

    private Matrix squareTransform(int viewWidth, int viewHeight) {
        Matrix matrix = new Matrix();

        if (viewWidth < viewHeight) {
            MyLogger.log(AutoFitTextureView.class, "Horizontal");
            matrix.setScale(1, (float) mCameraHeight / (float) mCameraWidth, viewWidth / 2, viewHeight / 2);
        } else {
            MyLogger.log(AutoFitTextureView.class, "Vertical");
            matrix.setScale((float) mCameraHeight / (float) mCameraWidth, 1, viewWidth / 2, viewHeight / 2);
        }

        return matrix;
    }
}

在活动/片段中为您的纹理视图调用setAspectRatio。

if (mVideoSize.width > mVideoSize.height) {
    mTextureView.setAspectRatio(mVideoSize.height, mVideoSize.width, true);
} else {
    mTextureView.setAspectRatio(mVideoSize.width, mVideoSize.height, true);
}
mCamera.setPreviewTexture(mTextureView.getSurfaceTexture());
mCamera.startPreview();

1
嗨,Mikhael!我尝试实现你的解决方案,但是不起作用。你能否发布或发送给我完整的实现呢?那将太棒了! - anthony

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接