Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix Preview stretching on Android #2377

Merged
merged 13 commits into from
Jan 11, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import com.mrousavy.camera.core.outputs.BarcodeScannerOutput
import com.mrousavy.camera.core.outputs.PhotoOutput
import com.mrousavy.camera.core.outputs.SurfaceOutput
import com.mrousavy.camera.core.outputs.VideoPipelineOutput
import com.mrousavy.camera.extensions.bigger
import com.mrousavy.camera.extensions.capture
import com.mrousavy.camera.extensions.closestToOrMax
import com.mrousavy.camera.extensions.createCaptureSession
Expand All @@ -38,7 +37,6 @@ import com.mrousavy.camera.extensions.getPreviewTargetSize
import com.mrousavy.camera.extensions.getVideoSizes
import com.mrousavy.camera.extensions.openCamera
import com.mrousavy.camera.extensions.setZoom
import com.mrousavy.camera.extensions.smaller
import com.mrousavy.camera.frameprocessor.FrameProcessor
import com.mrousavy.camera.types.Flash
import com.mrousavy.camera.types.Orientation
Expand All @@ -48,6 +46,7 @@ import com.mrousavy.camera.types.Torch
import com.mrousavy.camera.types.VideoStabilizationMode
import com.mrousavy.camera.utils.ImageFormatUtils
import java.io.Closeable
import java.lang.IllegalStateException
import java.util.concurrent.CancellationException
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -246,6 +245,8 @@ class CameraSession(private val context: Context, private val cameraManager: Cam

private fun destroyPreviewOutputSync() {
Log.i(TAG, "Destroying Preview Output...")
// This needs to run synchronously because after this method returns, the Preview Surface is no longer valid,
// and trying to use it will crash. This might result in a short UI Thread freeze though.
runBlocking {
configure { config ->
config.preview = CameraConfiguration.Output.Disabled.create()
Expand Down Expand Up @@ -379,12 +380,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
if (preview != null) {
// Compute Preview Size based on chosen video size
val videoSize = videoOutput?.size ?: format?.videoSize
val size = if (videoSize != null) {
val formatAspectRatio = videoSize.bigger.toDouble() / videoSize.smaller
characteristics.getPreviewTargetSize(formatAspectRatio)
} else {
characteristics.getPreviewTargetSize(null)
}
val size = characteristics.getPreviewTargetSize(videoSize)

val enableHdr = video?.config?.enableHdr ?: false

Expand All @@ -396,7 +392,8 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
enableHdr
)
outputs.add(output)
previewView?.size = size
// Size is usually landscape, so we flip it here
previewView?.size = Size(size.height, size.width)
}

// CodeScanner Output
Expand Down Expand Up @@ -520,7 +517,11 @@ class CameraSession(private val context: Context, private val cameraManager: Cam

if (!config.isActive) {
isRunning = false
captureSession?.stopRepeating()
try {
captureSession?.stopRepeating()
} catch (e: IllegalStateException) {
// ignore - captureSession is already closed.
}
return
}
if (captureSession == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
set(value) {
field = value
UiThreadUtil.runOnUiThread {
Log.i(TAG, "Resizing PreviewView to ${value.width} x ${value.height}...")
holder.setFixedSize(value.width, value.height)
Log.i(TAG, "Setting PreviewView Surface Size to $width x $height...")
holder.setFixedSize(value.height, value.width)
requestLayout()
invalidate()
}
Expand All @@ -44,20 +44,10 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
holder.addCallback(callback)
}

/*fun resizeToInputCamera(cameraId: String, cameraManager: CameraManager, format: CameraDeviceFormat?) {
val characteristics = cameraManager.getCameraCharacteristics(cameraId)

val targetPreviewSize = format?.videoSize
val formatAspectRatio = if (targetPreviewSize != null) targetPreviewSize.bigger.toDouble() / targetPreviewSize.smaller else null
size = characteristics.getPreviewTargetSize(formatAspectRatio)
}*/

private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size {
val contentAspectRatio = contentSize.height.toDouble() / contentSize.width
val contentAspectRatio = contentSize.width.toDouble() / contentSize.height
val containerAspectRatio = containerSize.width.toDouble() / containerSize.height

Log.i(TAG, "Content Size: $contentSize ($contentAspectRatio) | Container Size: $containerSize ($containerAspectRatio)")

val widthOverHeight = when (resizeMode) {
ResizeMode.COVER -> contentAspectRatio > containerAspectRatio
ResizeMode.CONTAIN -> contentAspectRatio < containerAspectRatio
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import android.content.res.Resources
import android.hardware.camera2.CameraCharacteristics
import android.util.Size
import android.view.SurfaceHolder
import kotlin.math.abs

fun getMaximumPreviewSize(): Size {
// See https://developer.android.com/reference/android/hardware/camera2/params/StreamConfigurationMap
// According to the Android Developer documentation, PREVIEW streams can have a resolution
// of up to the phone's display's resolution, with a maximum of 1920x1080.
val display1080p = Size(1920, 1080)
val display1080p = Size(1080, 1920)
val displaySize = Size(
Resources.getSystem().displayMetrics.widthPixels,
Resources.getSystem().displayMetrics.heightPixels
Expand All @@ -20,28 +19,11 @@ fun getMaximumPreviewSize(): Size {
return if (isHighResScreen) display1080p else displaySize
}

fun CameraCharacteristics.getPreviewSizeFromAspectRatio(aspectRatio: Double): Size {
fun CameraCharacteristics.getPreviewTargetSize(targetSize: Size?): Size {
val config = this.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
val maximumPreviewSize = getMaximumPreviewSize()
val outputSizes = config.getOutputSizes(SurfaceHolder::class.java)
.sortedByDescending { it.width * it.height }
.sortedBy { abs(aspectRatio - (it.bigger.toDouble() / it.smaller)) }
.filter { it.bigger <= maximumPreviewSize.bigger && it.smaller <= maximumPreviewSize.smaller }

return outputSizes.first { it.bigger <= maximumPreviewSize.bigger && it.smaller <= maximumPreviewSize.smaller }
return outputSizes.closestToOrMax(targetSize)
}

fun CameraCharacteristics.getAutomaticPreviewSize(): Size {
val config = this.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
val maximumPreviewSize = getMaximumPreviewSize()
val outputSizes = config.getOutputSizes(SurfaceHolder::class.java)
.sortedByDescending { it.width * it.height }

return outputSizes.first { it.bigger <= maximumPreviewSize.bigger && it.smaller <= maximumPreviewSize.smaller }
}

fun CameraCharacteristics.getPreviewTargetSize(aspectRatio: Double?): Size =
if (aspectRatio != null) {
getPreviewSizeFromAspectRatio(aspectRatio)
} else {
getAutomaticPreviewSize()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import kotlin.math.min

fun List<Size>.closestToOrMax(size: Size?): Size =
if (size != null) {
this.minBy { abs(it.width - size.width) + abs(it.height - size.height) }
this.minBy { abs((it.width * it.height) - (size.width * size.height)) }
} else {
this.maxBy { it.width * it.height }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
/**
* Represents a JS Frame Processor
*/
@SuppressWarnings("JavaJniMissingFunction") // we're using fbjni.
public final class FrameProcessor {
/**
* Call the JS Frame Processor function with the given Frame
Expand Down
2 changes: 1 addition & 1 deletion package/example/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ hermesEnabled=true
# Can be set to true to disable the build setup
#VisionCamera_disableFrameProcessors=true
# Can be set to true to include the full 2.4 MB MLKit dependency
#VisionCamera_enableCodeScanner=true
VisionCamera_enableCodeScanner=true
Loading