简体   繁体   中英

How to match PreviewView aspect ratio to captured image using CameraX

I have a PreviewView that occupies the whole screen except for the toolbar. The preview of the camera works great, but when I capture the image, the aspect ratio is complately different.

I would like to show the image to the user after it is successfully captured so it is the same size as the PreviewView so I don't have to crop or stretch it.

Is it possible to change the aspect ratio so on every device it is the size of the PreviewView or do I have to set it to a fixed value?

You can set the aspect ratio of the Preview and ImageCapture use cases while building them. If you set the same aspect ratio to both use cases, you should end up with a captured image that matches the camera preview output.

Example: Setting Preview and ImageCapture 's aspect ratios to 4:3

Preview preview = new Preview.Builder()
            .setTargetAspectRatio(AspectRatio.RATIO_4_3)
            .build();
ImageCapture imageCapture = new ImageCapture.Builder()
            .setTargetAspectRatio(AspectRatio.RATIO_4_3)
            .build();

By doing this, you'll most likely still end up with a captured image that doesn't match what PreviewView is displaying. Assuming you don't change the default scale type of PreviewView , it'll be equal to ScaleType.FILL_CENTER , meaning that unless the camera preview output has an aspect ratio that matches that of PreviewView , PreviewView will crop parts of the preview (the top and bottom, or the right and left sides), resulting in the captured image not matching what PreviewView displays. To solve this issue, you should set PreviewView 's aspect ratio to the same aspect ratio as the Preview and ImageCapture use cases.

Example: Setting PreviewView 's aspect ratio to 4:3

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.camera.view.PreviewView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="3:4"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

I was working with CameraX and faced the same issue. I followed the excellent answer given by @Husayn Hakeen . However, my app needs the previewView to match the device height width precisely. So I made some changes. In kotlin ( or java), I added

Preview preview = new Preview.Builder()
            .setTargetAspectRatio(AspectRatio.RATIO_16_9)
            .build();
ImageCapture imageCapture = new ImageCapture.Builder()
            .setTargetAspectRatio(AspectRatio.RATIO_16_9)
            .build();

And my xml has:

    <androidx.camera.view.PreviewView
      android:id="@+id/viewFinder"
      android:layout_width="0dp"
      android:layout_height="0dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      tools:background="@color/colorLight"
      />

From kotlin, I measured the device actual height and width in pixel:

val metrics: DisplayMetrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
    val deviceWidthPx = metrics.widthPixels
 val deviceHeightPx = metrics.heightPixels  

Using these data, I used some basic coordinate geometry to draw some lines on the captured photo: 演示照片

As you can see, the yellow rectangle indicate the device preview, so this is what you see on the preview. I had a requirement to perform a center crop, that's why I drew the blue square.

After that, I cropped the preview:


  @JvmStatic
  fun cropPreviewBitmap(previewBitmap: Bitmap, deviceWidthPx: Int, deviceHeightPx: Int): Bitmap {

    // crop the image
    var cropHeightPx = 0f
    var cropWidthPx = 0f
    if(deviceHeightPx > deviceWidthPx) {
      cropHeightPx = 1.0f *previewBitmap.height
      cropWidthPx = 1.0f * deviceWidthPx / deviceHeightPx * cropHeightPx
    }else {
      cropWidthPx = 1.0f *previewBitmap.width
      cropHeightPx = 1.0f * deviceHeightPx / deviceWidthPx * cropWidthPx
    }

    val cx = previewBitmap.width / 2
    val cy = previewBitmap.height / 2

    val minimusPx = Math.min(cropHeightPx, cropWidthPx)
    val left2 = cx - minimusPx / 2
    val top2 = cy - minimusPx  /2

    val croppedBitmap = Bitmap.createBitmap(previewBitmap, left2.toInt(), top2.toInt(), minimusPx.toInt(), minimusPx.toInt())
    return croppedBitmap
  }

This worked for me.

Easiest solution is to use the useCaseGroup, where you add both preview and image capture use cases under the same group + set the same view port.

Beware that with this solution, you will need to start the camera in onCreate method when it's ready (otherwise your app will crash):

viewFinder.post {
    startCamera()
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM