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:
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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")
|
||||
|
@@ -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())
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user