相机尺寸和4:3宽高比不匹配

时间:2016-04-01 06:33:39

标签: android camera android-camera

我正在使用相机的Surface View来显示相机并拍照 我需要相机预览4:3,Instagram是一个正方形,我的是一个矩形。

如果您查看Instagram应用程序,相机预览不会拉伸或压缩,但在我的压缩中。

这是我的相机预览类:

class CustomCam extends SurfaceView implements SurfaceHolder.Callback {

    private final String TAG = "PIC-FRAME";
    private static final double ASPECT_RATIO = 4.0 / 3.0;
    private static final int PICTURE_SIZE_MAX_WIDTH = 1280;
    private static final int PREVIEW_SIZE_MAX_WIDTH = 640;

    private SurfaceHolder mHolder;
    private Camera mCamera;
    private Display display;
    public List<Camera.Size> mSupportedPreviewSizes;
    private Camera.Size mPreviewSize;

    public CustomCam(Activity context, Camera camera) {
        super(context);
        mCamera = camera;
        display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

        mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        for (Camera.Size str : mSupportedPreviewSizes)
            Log.e(TAG, str.width + "/" + str.height);
        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        setKeepScreenOn(true);
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
        this.getHolder().removeCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    private Camera.Size getBestPreviewSize(int width, int height) {
        Camera.Size result = null;
        Camera.Parameters p = mCamera.getParameters();
        for (Camera.Size size : p.getSupportedPreviewSizes()) {
            if (size.width <= width && size.height <= height) {
                if (result == null) {
                    result = size;
                } else {
                    int resultArea = result.width * result.height;
                    int newArea = size.width * size.height;

                    if (newArea > resultArea) {
                        result = size;
                    }
                }
            }
        }
        return result;

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //This line helped me set the preview Display Orientation to Portrait
        //Only works API Level 8 and higher unfortunately.

        try {
            Camera.Parameters parameters = mCamera.getParameters();
//        Camera.Size size = getBestPreviewSize(width, height);
//            Camera.Size size = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
//            parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
//            initialCameraPictureSize(parameters);
//            parameters.setPreviewSize(size.width, size.height);

            Camera.Size bestPreviewSize = determineBestPreviewSize(parameters);
            Camera.Size bestPictureSize = determineBestPictureSize(parameters);

            parameters.setPreviewSize(bestPreviewSize.width, bestPreviewSize.height);
            parameters.setPictureSize(bestPictureSize.width, bestPictureSize.height);

            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            mCamera.setDisplayOrientation(90);
            mCamera.getParameters().setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            mCamera.setPreviewDisplay(mHolder);
            mCamera.setParameters(parameters);
            mCamera.startPreview();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void initialCameraPictureSize(Camera.Parameters parameters) {

        List list = parameters.getSupportedPictureSizes();
        if (list != null) {
            Camera.Size size = null;
            Iterator iterator = list.iterator();
            do {
                if (!iterator.hasNext())
                    break;
                Camera.Size size1 = (Camera.Size) iterator.next();
                if (Math.abs(3F * ((float) size1.width / 4F) - (float) size1.height) < 0.1F * (float) size1.width && (size == null || size1.height > size.height && size1.width < 3000))
                    size = size1;
            } while (true);
            if (size != null)
                parameters.setPictureSize(size.width, size.height);
            else
                Log.e("CameraSettings", "No supported picture size found");
        }
    }

    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) h / w;

        if (sizes == null) return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        for (Camera.Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }

    /**
     * Measure the view and its content to determine the measured width and the
     * measured height.
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);

        if (width > height * ASPECT_RATIO) {
            width = (int) (height * ASPECT_RATIO + 0.5);
        } else {
            height = (int) (width / ASPECT_RATIO + 0.5);
        }

        setMeasuredDimension(width, height);
    }

    protected Camera.Size determineBestSize(List<Camera.Size> sizes, int widthThreshold) {
        Camera.Size bestSize = null;

        for (Camera.Size currentSize : sizes) {
            boolean isDesiredRatio = (currentSize.width / 4) == (currentSize.height / 3);
            boolean isBetterSize = (bestSize == null || currentSize.width > bestSize.width);
            boolean isInBounds = currentSize.width <= PICTURE_SIZE_MAX_WIDTH;

            if (isDesiredRatio && isInBounds && isBetterSize) {
                bestSize = currentSize;
            }
        }

        if (bestSize == null) {
            return sizes.get(0);
        }

        return bestSize;
    }

    private Camera.Size determineBestPreviewSize(Camera.Parameters parameters) {
        List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();

        return determineBestSize(sizes, PREVIEW_SIZE_MAX_WIDTH);
    }

    private Camera.Size determineBestPictureSize(Camera.Parameters parameters) {
        List<Camera.Size> sizes = parameters.getSupportedPictureSizes();

        return determineBestSize(sizes, PICTURE_SIZE_MAX_WIDTH);
    }
}

我的自定义框架布局:

CustomFrameLayout extends FrameLayout {

    private static final float RATIO = 4f / 3f;

    public CustomFrameLayout(Context context) {
        super(context);
    }

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

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public CustomFrameLayout(Context context, AttributeSet attrs, int defStyleAttr,
                            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight();
        int heigthWithoutPadding = height - getPaddingTop() - getPaddingBottom();

        int maxWidth = (int) (heigthWithoutPadding * RATIO);
        int maxHeight = (int) (widthWithoutPadding / RATIO);

        if (widthWithoutPadding > maxWidth) {
            width = maxWidth + getPaddingLeft() + getPaddingRight();
        } else {
            height = maxHeight + getPaddingTop() + getPaddingBottom();
        }

        setMeasuredDimension(width, height);
    }

但是在框架布局内压缩凸轮预览我该如何解决?问题?

更新

好了,经过一些研究后才知道它是因为onMeasure ASPECT_RATIO = 4:3

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);

        if (width > height * ASPECT_RATIO) {
            width = (int) (height * ASPECT_RATIO + 0.5);
        } else {
            height = (int) (width / ASPECT_RATIO + 0.5);
        }

        setMeasuredDimension(width, height);
    }

解决方案

所以我想到一个解决方案,可能(就像Instagram那样)让你的相机全尺寸然后隐藏布局的某些区域只是为了使它看起来像4:3的比例。然后通过使用一些裁剪机制必须剪切图像以使图像看起来像4:3。

说我总是以4:3的比例从顶部显示预览,下面部分的其余部分被隐藏,所以现在我拍摄照片后我想要将图像从顶部裁剪为4:3并保存。< / p>

我如何实现这一目标,这是一个可行的解决方案吗?

1 个答案:

答案 0 :(得分:2)

据我所知,您当前的问题是如何裁剪您收到的图像并进行显示。这是一个小例子:

@OnClick(R.id.btn_record_start)
    public void takePhoto() {
        if (null != actions) {
            EasyCamera.PictureCallback callback = new EasyCamera.PictureCallback() {
                public void onPictureTaken(byte[] data, EasyCamera.CameraActions actions) {
                    // store picture
                    Bitmap bitmap = ImageUtils.getExifOrientedBitmap(data);
                    if ((portrait && bitmap.getHeight() < bitmap.getWidth()) ||
                        (!portrait && bitmap.getHeight() > bitmap.getWidth())) {
                        Matrix matrix = new Matrix();
                        matrix.postRotate(90);
                        bitmap =
                            Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
                                matrix, true);
                    }

                    Camera.CameraInfo info = new Camera.CameraInfo();
                    Camera.getCameraInfo(cameraId, info);
                    if (Camera.CameraInfo.CAMERA_FACING_FRONT == info.facing) {
                        Matrix matrix = new Matrix();
                        matrix.postRotate(180);
                        bitmap =
                            Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
                                matrix, true);
                    }
                    showPhoto(bitmap);
                }
            };
            actions.takePicture(EasyCamera.Callbacks.create()
                .withJpegCallback(callback));
        }
    }

这是我用来处理照片后处理图像方向的方法。 它可以很容易地修改,以处理裁剪。要实现这一点,您必须指定图像的目标宽度和高度(目前我正在发送整个位图的大小)。一种可能的解决方案是获取图像的高度并删除过大的宽度 - 因此您发送到createBitmap方法的参数将是bitmap.getHeight() * 4.0 / 3.0bitmap.getHeight()。以下是修改后的示例:

@OnClick(R.id.btn_record_start)
public void takePhoto() {
    if (null != actions) {
        EasyCamera.PictureCallback callback = new EasyCamera.PictureCallback() {
            public void onPictureTaken(byte[] data, EasyCamera.CameraActions actions) {
                // store picture
                Bitmap bitmap = ImageUtils.getExifOrientedBitmap(data);
                if ((portrait && bitmap.getHeight() < bitmap.getWidth()) ||
                    (!portrait && bitmap.getHeight() > bitmap.getWidth())) {
                    Matrix matrix = new Matrix();
                    matrix.postRotate(90);
                    bitmap =
                        Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
                            matrix, true);
                }

                Camera.CameraInfo info = new Camera.CameraInfo();
                Camera.getCameraInfo(cameraId, info);
                if (Camera.CameraInfo.CAMERA_FACING_FRONT == info.facing) {
                    Matrix matrix = new Matrix();
                    matrix.postRotate(180);
                    bitmap =
                        Bitmap.createBitmap(bitmap, 0, 0, (int) (bitmap.getHeight() * 4.0 / 3.0), bitmap.getHeight(),
                            matrix, true);
                }
                showPhoto(bitmap);
            }
        };
        actions.takePicture(EasyCamera.Callbacks.create()
            .withJpegCallback(callback));
    }
}

有几点需要注意:

  • 您可以使用4.0 / 3.0变量
  • 替换ASPECT_RATIO部分
  • 我的示例是进行图像旋转,因此它看起来就像在预览期间一样,在您的情况下,所需的UI可能会有所不同。
  • 我正在使用EasyCamera库来简化相机管理

以下是我正在使用的其他ImageUtils方法:

<强> getExifOrientedBitmap

public static Bitmap getExifOrientedBitmap(byte[] data) {
    File newPhotoFile = writeToFile(data);
    if (newPhotoFile == null) {
        return null;
    }

    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
    bitmap = fixOrientationIfNeeded(newPhotoFile, bitmap);
    newPhotoFile.delete();
    return bitmap;
}

<强>将writeToFile

@Nullable
public static File writeToFile(byte[] data) {
    File dir = PhotoMessageComposer.getPhotoDir();
    if (!dir.exists()) {
        dir.mkdir();
    }
    File newPhotoFile = new File(dir, ImageUtils.getRandomFilename());
    FileOutputStream fos = null;
    try
    {
        fos = new FileOutputStream(newPhotoFile);
        fos.write(data);
        fos.close();
    } catch (Exception error) {
        return null;
    } finally {
        try {
            if (fos != null) {
                fos.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return newPhotoFile;
}

<强> getPhotoDir

@NonNull
public static File getPhotoDir() {
    return new File(
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) +
            PICTURES_DIR);
}

<强> getRandomFileName

public static String getRandomFilename() {
    return UUID.randomUUID().toString() + IMAGE_EXTENSION;
}

<强> fixOrientationIfNeeded

public static Bitmap fixOrientationIfNeeded(File sourceFile, Bitmap source) {
    ExifInterface exif;
    try {
        exif = new ExifInterface(sourceFile.getAbsolutePath());
        int exifOrientation = exif.getAttributeInt(
                ExifInterface.TAG_ORIENTATION,
                ExifInterface.ORIENTATION_NORMAL);

        if (exifOrientation != ExifInterface.ORIENTATION_NORMAL) {
            Matrix matrix = new Matrix();
            int angle = findRotationAngle(exifOrientation);
            matrix.postRotate(angle);
            source = Bitmap.createBitmap(source, 0, 0, source.getWidth(),
                    source.getHeight(), matrix, true);
            return source;
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return source;
}

<强> findRotationAngle

protected static int findRotationAngle(int exifOrientation) {
    switch (exifOrientation) {
    case ExifInterface.ORIENTATION_ROTATE_270:
        return 270;
    case ExifInterface.ORIENTATION_ROTATE_180:
        return 180;
    case ExifInterface.ORIENTATION_ROTATE_90:
        return 90;
    default:
        return 0;
    }
}

P.S。自ImageUtils类实现以来已经有几年了,所以可能有更好的方法来处理这些操作。尽管如此,它们应该足够好了。