feat: Implement resizeMode prop for iOS (#1838)

* feat: Implement `resizeMode` prop for iOS

- `"cover"`: Keep aspect ratio, but fill entire parent view (centered).
- `"contain"`: Keep aspect ratio, but make sure the entire content is visible even if it introduces additional blank areas (centered).

* chore: Update prop docs

* Update CameraProps.ts

* Lint & Format
This commit is contained in:
Marc Rousavy
2023-09-23 10:14:27 +02:00
committed by GitHub
parent c0b80b342b
commit 3169444697
14 changed files with 120 additions and 38 deletions

View File

@@ -16,17 +16,17 @@ import com.facebook.react.bridge.ReadableMap
import com.mrousavy.camera.core.CameraSession
import com.mrousavy.camera.core.PreviewView
import com.mrousavy.camera.core.outputs.CameraOutputs
import com.mrousavy.camera.extensions.bigger
import com.mrousavy.camera.extensions.containsAny
import com.mrousavy.camera.extensions.getPreviewTargetSize
import com.mrousavy.camera.extensions.installHierarchyFitter
import com.mrousavy.camera.extensions.smaller
import com.mrousavy.camera.frameprocessor.FrameProcessor
import com.mrousavy.camera.parsers.Orientation
import com.mrousavy.camera.parsers.PixelFormat
import com.mrousavy.camera.parsers.ResizeMode
import com.mrousavy.camera.parsers.Torch
import com.mrousavy.camera.parsers.VideoStabilizationMode
import com.mrousavy.camera.extensions.bigger
import com.mrousavy.camera.extensions.getPreviewTargetSize
import com.mrousavy.camera.extensions.smaller
import com.mrousavy.camera.parsers.ResizeMode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -141,9 +141,10 @@ class CameraView(context: Context) : FrameLayout(context) {
configureSession()
}
previewView.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT,
Gravity.CENTER)
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT,
Gravity.CENTER
)
addView(previewView)
this.previewView = previewView
}

View File

@@ -132,8 +132,9 @@ class CameraViewManager : ViewGroupManager<CameraView>() {
@ReactProp(name = "resizeMode")
fun setResizeMode(view: CameraView, resizeMode: String) {
val newMode = ResizeMode.fromUnionValue(resizeMode)
if (view.resizeMode != newMode)
if (view.resizeMode != newMode) {
addChangedPropToTransaction(view, "resizeMode")
}
view.resizeMode = newMode
}

View File

@@ -7,8 +7,6 @@ import android.util.Size
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import com.mrousavy.camera.extensions.bigger
import com.mrousavy.camera.extensions.smaller
import com.mrousavy.camera.parsers.ResizeMode
import kotlin.math.roundToInt
@@ -17,7 +15,8 @@ class PreviewView(
context: Context,
val targetSize: Size,
private val resizeMode: ResizeMode,
private val onSurfaceChanged: (surface: Surface?) -> Unit): SurfaceView(context) {
private val onSurfaceChanged: (surface: Surface?) -> Unit
) : SurfaceView(context) {
init {
Log.i(TAG, "Using Preview Size ${targetSize.width} x ${targetSize.height}.")
@@ -43,7 +42,7 @@ class PreviewView(
val contentAspectRatio = contentSize.height.toDouble() / contentSize.width
val containerAspectRatio = containerWidth.toDouble() / containerHeight
Log.d(TAG, "coverSize :: $contentSize ($contentAspectRatio), ${containerWidth}x${containerHeight} ($containerAspectRatio)")
Log.d(TAG, "coverSize :: $contentSize ($contentAspectRatio), ${containerWidth}x$containerHeight ($containerAspectRatio)")
return if (contentAspectRatio > containerAspectRatio) {
// Scale by width to cover height
@@ -60,7 +59,7 @@ class PreviewView(
val contentAspectRatio = contentSize.height.toDouble() / contentSize.width
val containerAspectRatio = containerWidth.toDouble() / containerHeight
Log.d(TAG, "containSize :: $contentSize ($contentAspectRatio), ${containerWidth}x${containerHeight} ($containerAspectRatio)")
Log.d(TAG, "containSize :: $contentSize ($contentAspectRatio), ${containerWidth}x$containerHeight ($containerAspectRatio)")
return if (contentAspectRatio > containerAspectRatio) {
// Scale by height to fit within width
@@ -81,8 +80,8 @@ class PreviewView(
Log.d(TAG, "onMeasure($viewWidth, $viewHeight)")
val fittedSize = when (resizeMode) {
ResizeMode.COVER -> this.coverSize(targetSize, viewWidth, viewHeight)
ResizeMode.CONTAIN -> this.containSize(targetSize, viewWidth, viewHeight)
ResizeMode.COVER -> this.coverSize(targetSize, viewWidth, viewHeight)
ResizeMode.CONTAIN -> this.containSize(targetSize, viewWidth, viewHeight)
}
Log.d(TAG, "Fitted dimensions set: $fittedSize")

View File

@@ -10,11 +10,11 @@ import android.util.Size
import android.view.Surface
import com.mrousavy.camera.CameraQueues
import com.mrousavy.camera.core.VideoPipeline
import com.mrousavy.camera.extensions.bigger
import com.mrousavy.camera.extensions.closestToOrMax
import com.mrousavy.camera.extensions.getPhotoSizes
import com.mrousavy.camera.extensions.getPreviewTargetSize
import com.mrousavy.camera.extensions.getVideoSizes
import com.mrousavy.camera.extensions.bigger
import com.mrousavy.camera.extensions.smaller
import java.io.Closeable
@@ -63,12 +63,12 @@ class CameraOutputs(
override fun equals(other: Any?): Boolean {
if (other !is CameraOutputs) return false
return this.cameraId == other.cameraId &&
this.preview?.surface == other.preview?.surface &&
this.preview?.targetSize == other.preview?.targetSize &&
this.photo?.targetSize == other.photo?.targetSize &&
this.photo?.format == other.photo?.format &&
this.video?.enableRecording == other.video?.enableRecording &&
return this.cameraId == other.cameraId &&
this.preview?.surface == other.preview?.surface &&
this.preview?.targetSize == other.preview?.targetSize &&
this.photo?.targetSize == other.photo?.targetSize &&
this.photo?.format == other.photo?.format &&
this.video?.enableRecording == other.video?.enableRecording &&
this.video?.targetSize == other.video?.targetSize &&
this.video?.format == other.video?.format &&
this.enableHdr == other.enableHdr
@@ -104,11 +104,18 @@ class CameraOutputs(
// Preview output: Low resolution repeating images (SurfaceView)
if (preview != null) {
Log.i(TAG, "Adding native preview view output.")
val previewSizeAspectRatio = if (preview.targetSize != null) preview.targetSize.bigger.toDouble() / preview.targetSize.smaller else null
val previewSizeAspectRatio = if (preview.targetSize !=
null
) {
preview.targetSize.bigger.toDouble() / preview.targetSize.smaller
} else {
null
}
previewOutput = SurfaceOutput(
preview.surface,
characteristics.getPreviewTargetSize(previewSizeAspectRatio),
SurfaceOutput.OutputType.PREVIEW)
SurfaceOutput.OutputType.PREVIEW
)
}
// Photo output: High quality still images (takePhoto())

View File

@@ -2,7 +2,6 @@ package com.mrousavy.camera.extensions
import android.content.res.Resources
import android.hardware.camera2.CameraCharacteristics
import android.util.Log
import android.util.Size
import android.view.SurfaceHolder
import kotlin.math.abs
@@ -12,8 +11,10 @@ private fun getMaximumPreviewSize(): Size {
// 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 displaySize = Size(Resources.getSystem().displayMetrics.widthPixels,
Resources.getSystem().displayMetrics.heightPixels)
val displaySize = Size(
Resources.getSystem().displayMetrics.widthPixels,
Resources.getSystem().displayMetrics.heightPixels
)
val isHighResScreen = displaySize.bigger >= display1080p.bigger || displaySize.smaller >= display1080p.smaller
return if (isHighResScreen) display1080p else displaySize
@@ -38,10 +39,9 @@ fun CameraCharacteristics.getAutomaticPreviewSize(): Size {
return outputSizes.first { it.bigger <= maximumPreviewSize.bigger && it.smaller <= maximumPreviewSize.smaller }
}
fun CameraCharacteristics.getPreviewTargetSize(aspectRatio: Double?): Size {
return if (aspectRatio != null) {
fun CameraCharacteristics.getPreviewTargetSize(aspectRatio: Double?): Size =
if (aspectRatio != null) {
getPreviewSizeFromAspectRatio(aspectRatio)
} else {
getAutomaticPreviewSize()
}
}

View File

@@ -1,16 +1,15 @@
package com.mrousavy.camera.parsers
enum class ResizeMode(override val unionValue: String): JSUnionValue {
enum class ResizeMode(override val unionValue: String) : JSUnionValue {
COVER("cover"),
CONTAIN("contain");
companion object: JSUnionValue.Companion<ResizeMode> {
override fun fromUnionValue(unionValue: String?): ResizeMode {
return when (unionValue) {
companion object : JSUnionValue.Companion<ResizeMode> {
override fun fromUnionValue(unionValue: String?): ResizeMode =
when (unionValue) {
"cover" -> COVER
"contain" -> CONTAIN
else -> COVER
}
}
}
}