Skip to content

Commit

Permalink
fix(core): when settings zoom ratio, crop sensor region must be full …
Browse files Browse the repository at this point in the history
…sensor array

See #97
  • Loading branch information
ThibaultBee committed Feb 6, 2024
1 parent 0d62e55 commit 4a544b6
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import android.hardware.camera2.params.MeteringRectangle
import android.os.Build
import android.util.Range
import android.util.Rational
import androidx.annotation.RequiresApi
import io.github.thibaultbee.streampack.internal.sources.camera.CameraController
import io.github.thibaultbee.streampack.internal.utils.*
import io.github.thibaultbee.streampack.internal.utils.extensions.clamp
Expand Down Expand Up @@ -68,7 +69,7 @@ class CameraSettings(context: Context, cameraController: CameraController) {
/**
* Current camera zoom API.
*/
val zoom = Zoom(context, cameraController)
val zoom = Zoom.build(context, cameraController)

/**
* Current focus API.
Expand Down Expand Up @@ -339,104 +340,151 @@ class Exposure(private val context: Context, private val cameraController: Camer
}
}

sealed class Zoom(
protected val context: Context,
protected val cameraController: CameraController
) {
abstract val availableRatioRange: Range<Float>
internal abstract val cropSensorRegion: Rect

class Zoom(private val context: Context, private val cameraController: CameraController) {
// Keep the zoomRation for Android version < R
private var persistentZoomRatio = 1f
abstract var zoomRatio: Float

/**
* Get current camera zoom ratio range.
*
* @return zoom ratio range.
* Sets the zoom on pinch scale gesture.
*
* @see [zoomRatio]
* @param scale the scale factor
*/
val availableRatioRange: Range<Float>
get() = cameraController.cameraId?.let { context.getZoomRatioRange(it) }
?: DEFAULT_ZOOM_RATIO_RANGE
fun onPinch(scale: Float) {
val scaledRatio: Float = zoomRatio * speedUpZoomByX(scale, 2)
// Clamp the ratio with the zoom range.
zoomRatio = scaledRatio.clamp(availableRatioRange.lower, availableRatioRange.upper)
}

/**
* Set or get the current zoom ratio.
*
* @see [availableRatioRange]
*/
var zoomRatio: Float
/**
* Get the zoom ratio.
*
* @return the current zoom ratio
*/
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
cameraController.getSetting(CaptureRequest.CONTROL_ZOOM_RATIO) ?: DEFAULT_ZOOM_RATIO
private fun speedUpZoomByX(scaleFactor: Float, ratio: Int): Float {
return if (scaleFactor > 1f) {
1.0f + (scaleFactor - 1.0f) * ratio
} else {
synchronized(this) {
persistentZoomRatio
}
1.0f - (1.0f - scaleFactor) * ratio
}
/**
* Set the zoom ratio.
*
* @param value zoom ratio
*/
set(value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
cameraController.setRepeatingSetting(
CaptureRequest.CONTROL_ZOOM_RATIO,
value.clamp(availableRatioRange)
}

class CropScalerRegionZoom(context: Context, cameraController: CameraController) :
Zoom(context, cameraController) {
// Keep the zoomRatio
private var persistentZoomRatio = 1f
private var currentCropRect: Rect? = null

override val availableRatioRange: Range<Float>
get() = cameraController.cameraId?.let {
Range(
DEFAULT_ZOOM_RATIO,
context.getScalerMaxZoom(it)
)
} else {
}
?: DEFAULT_ZOOM_RATIO_RANGE

override var zoomRatio: Float
get() = synchronized(this) {
persistentZoomRatio
}
set(value) {
synchronized(this) {
val clampedValue = value.clamp(availableRatioRange)
cameraController.cameraId?.let { cameraId ->
currentCropRect = getCropRegion(
context.getCameraCharacteristics(cameraId),
clampedValue
)
cameraController.setRepeatingSetting(
CaptureRequest.SCALER_CROP_REGION,
getCropRegion(
context.getCameraCharacteristics(cameraId),
clampedValue
)
currentCropRect
)
}
persistentZoomRatio = clampedValue
}
}
}

/**
* Sets the zoom on pinch scale gesture.
*
* @param scale the scale factor
*/
fun onPinch(scale: Float) {
val scaledRatio: Float = zoomRatio * speedUpZoomByX(scale, 2)
// Clamp the ratio with the zoom range.
zoomRatio = scaledRatio.clamp(availableRatioRange.lower, availableRatioRange.upper)
}
override val cropSensorRegion: Rect
get() {
synchronized(this) {
return if (currentCropRect != null) {
currentCropRect!!
} else {
val cameraId = cameraController.cameraId
?: throw IllegalStateException("Camera ID is null")
return context.getCameraCharacteristics(cameraId)
.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)!!
}
}
}

private fun speedUpZoomByX(scaleFactor: Float, ratio: Int): Float {
return if (scaleFactor > 1f) {
1.0f + (scaleFactor - 1.0f) * ratio
} else {
1.0f - (1.0f - scaleFactor) * ratio
companion object {
/**
* Calculates sensor crop region for a zoom ratio (zoom >= 1.0).
*
* @return the crop region.
*/
private fun getCropRegion(sensorRect: Rect, zoomRatio: Float): Rect {
val xCenter: Int = sensorRect.width() / 2
val yCenter: Int = sensorRect.height() / 2
val xDelta = (0.5f * sensorRect.width() / zoomRatio).toInt()
val yDelta = (0.5f * sensorRect.height() / zoomRatio).toInt()
return Rect(xCenter - xDelta, yCenter - yDelta, xCenter + xDelta, yCenter + yDelta)
}

/**
* Calculates sensor crop region for a zoom ratio (zoom >= 1.0).
*
* @return the crop region.
*/
private fun getCropRegion(
characteristics: CameraCharacteristics,
zoomRatio: Float
): Rect {
val sensorRect =
characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)
?: throw IllegalStateException("Sensor rect is null")
return getCropRegion(sensorRect, zoomRatio)
}
}
}

@RequiresApi(Build.VERSION_CODES.R)
class RZoom(context: Context, cameraController: CameraController) :
Zoom(context, cameraController) {
override val availableRatioRange: Range<Float>
get() = cameraController.cameraId?.let { context.getZoomRatioRange(it) }
?: DEFAULT_ZOOM_RATIO_RANGE

override var zoomRatio: Float
get() = cameraController.getSetting(CaptureRequest.CONTROL_ZOOM_RATIO)
?: DEFAULT_ZOOM_RATIO
set(value) {
cameraController.setRepeatingSetting(
CaptureRequest.CONTROL_ZOOM_RATIO,
value.clamp(availableRatioRange)
)
}

override val cropSensorRegion: Rect
get() {
val cameraId = cameraController.cameraId
?: throw IllegalStateException("Camera ID is null")
return context.getCameraCharacteristics(cameraId)
.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)!!
}
}

companion object {
val DEFAULT_ZOOM_RATIO = 1f
const val DEFAULT_ZOOM_RATIO = 1f
val DEFAULT_ZOOM_RATIO_RANGE = Range(DEFAULT_ZOOM_RATIO, DEFAULT_ZOOM_RATIO)

/**
* Calculates sensor crop region for a zoom ratio (zoom >= 1.0).
*
* @return the crop region.
*/
internal fun getCropRegion(characteristics: CameraCharacteristics, zoomRatio: Float): Rect {
val sensorRect =
characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)!!
val xCenter: Int = sensorRect.width() / 2
val yCenter: Int = sensorRect.height() / 2
val xDelta = (0.5f * sensorRect.width() / zoomRatio).toInt()
val yDelta = (0.5f * sensorRect.height() / zoomRatio).toInt()
return Rect(xCenter - xDelta, yCenter - yDelta, xCenter + xDelta, yCenter + yDelta)
fun build(context: Context, cameraController: CameraController): Zoom {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
RZoom(context, cameraController)
} else {
CropScalerRegionZoom(context, cameraController)
}
}
}
}
Expand Down Expand Up @@ -534,10 +582,10 @@ class Focus(private val context: Context, private val cameraController: CameraCo
}

companion object {
val DEFAULT_LENS_DISTANCE = 0f
const val DEFAULT_LENS_DISTANCE = 0f
val DEFAULT_LENS_DISTANCE_RANGE = Range(DEFAULT_LENS_DISTANCE, DEFAULT_LENS_DISTANCE)

val DEFAULT_MAX_NUM_OF_METERING_REGION = 0
const val DEFAULT_MAX_NUM_OF_METERING_REGION = 0
}
}

Expand Down Expand Up @@ -699,7 +747,7 @@ class FocusMetering(
return
}

val cameraId = cameraController.cameraId ?: throw IllegalStateException("Camera ID is null")
val cropRegion = zoom.cropSensorRegion

disableAutoCancel()

Expand All @@ -712,9 +760,6 @@ class FocusMetering(
return
}

val cropRegion =
Zoom.getCropRegion(context.getCameraCharacteristics(cameraId), zoom.zoomRatio)

val afRectangles =
getMeteringRectangles(
afPoints,
Expand Down Expand Up @@ -981,22 +1026,28 @@ class FocusMetering(
adjustedPoint: PointF,
cropRegion: Rect
): MeteringRectangle {
Logger.e(">>TEST", "cropRegion: $cropRegion")
Logger.e(">>TEST", "adjustedPoint: $adjustedPoint")
Logger.e(">>TEST", "size: $size")
val centerX = (cropRegion.left + adjustedPoint.x * cropRegion.width()).toInt()
val centerY = (cropRegion.top + adjustedPoint.y * cropRegion.height()).toInt()
val width = (size * cropRegion.width())
val height = (size * cropRegion.height())

Logger.e(">>TEST", "centerX: $centerX - centerY: $centerY")
Logger.e(">>TEST", "width: $width - height: $height")

val focusRect = Rect(
(centerX - width / 2).toInt(), (centerY - height / 2).toInt(),
(centerX + width / 2).toInt(),
(centerY + height / 2).toInt()
)

Logger.e(">>TEST", "focusRect1: $focusRect")
focusRect.left = focusRect.left.clamp(cropRegion.right, cropRegion.left)
focusRect.right = focusRect.right.clamp(cropRegion.right, cropRegion.left)
focusRect.top = focusRect.top.clamp(cropRegion.bottom, cropRegion.top)
focusRect.bottom = focusRect.bottom.clamp(cropRegion.bottom, cropRegion.top)

Logger.e(">>TEST", "focusRect2: $focusRect")
return MeteringRectangle(focusRect, DEFAULT_METERING_WEIGHT_MAX)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import android.hardware.camera2.params.DynamicRangeProfiles.STANDARD
import android.os.Build
import android.util.Range
import android.util.Size
import androidx.annotation.RequiresApi

/**
* Get camera characteristics.
Expand Down Expand Up @@ -246,13 +247,20 @@ fun Context.getExposureMaxMeteringRegionsSupported(cameraId: String) =
* @param cameraId camera id
* @return zoom ratio range.
*/
@RequiresApi(Build.VERSION_CODES.R)
fun Context.getZoomRatioRange(cameraId: String): Range<Float>? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
getCameraCharacteristics(cameraId).get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)
} else {
getCameraCharacteristics(cameraId).get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)
?.let { maxZoom -> Range(1f, maxZoom) }
}
return getCameraCharacteristics(cameraId).get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)
}

/**
* Gets max scaler zoom.
*
* @param cameraId camera id
* @return max scaler zoom.
*/
fun Context.getScalerMaxZoom(cameraId: String): Float {
return getCameraCharacteristics(cameraId).get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)
?: 1.0f
}

/**
Expand Down

0 comments on commit 4a544b6

Please sign in to comment.