Get sizes per ImageFormat/PixelFormat (#119)
* Get sizes per ImageFormat/PixelFormat * Fix `getOutputMinFrameDuration` crashing * Fix `isHighestPhotoQualitySupported` * Add TS type explanations * Update CameraDevice.ts * Update CameraViewModule.kt * Add link to AVCaptureColorSpace apple docs
This commit is contained in:
parent
584cd682db
commit
9320e356e3
@ -5,8 +5,6 @@ import android.content.Context
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.hardware.camera2.CameraCharacteristics
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
import android.hardware.camera2.CameraManager
|
import android.hardware.camera2.CameraManager
|
||||||
import android.media.ImageReader
|
|
||||||
import android.media.MediaRecorder
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.camera.core.CameraSelector
|
||||||
@ -117,8 +115,8 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|||||||
GlobalScope.launch(Dispatchers.Main) {
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
withPromise(promise) {
|
withPromise(promise) {
|
||||||
// I need to init those because the HDR/Night Mode Extension expects them to be initialized
|
// I need to init those because the HDR/Night Mode Extension expects them to be initialized
|
||||||
val extensionsManager = ExtensionsManager.init(reactApplicationContext).await()
|
ExtensionsManager.init(reactApplicationContext).await()
|
||||||
val processCameraProvider = ProcessCameraProvider.getInstance(reactApplicationContext).await()
|
ProcessCameraProvider.getInstance(reactApplicationContext).await()
|
||||||
|
|
||||||
val manager = reactApplicationContext.getSystemService(Context.CAMERA_SERVICE) as? CameraManager
|
val manager = reactApplicationContext.getSystemService(Context.CAMERA_SERVICE) as? CameraManager
|
||||||
?: throw CameraManagerUnavailableError()
|
?: throw CameraManagerUnavailableError()
|
||||||
@ -173,7 +171,6 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|||||||
val fieldOfView = characteristics.getFieldOfView()
|
val fieldOfView = characteristics.getFieldOfView()
|
||||||
|
|
||||||
val map = Arguments.createMap()
|
val map = Arguments.createMap()
|
||||||
val formats = Arguments.createArray()
|
|
||||||
map.putString("id", id)
|
map.putString("id", id)
|
||||||
map.putArray("devices", deviceTypes)
|
map.putArray("devices", deviceTypes)
|
||||||
map.putString("position", parseLensFacing(lensFacing))
|
map.putString("position", parseLensFacing(lensFacing))
|
||||||
@ -194,61 +191,72 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
|||||||
}
|
}
|
||||||
map.putDouble("neutralZoom", characteristics.neutralZoomPercent.toDouble())
|
map.putDouble("neutralZoom", characteristics.neutralZoomPercent.toDouble())
|
||||||
|
|
||||||
val maxImageOutputSize = cameraConfig.getOutputSizes(ImageReader::class.java).maxByOrNull { it.width * it.height }!!
|
// TODO: Optimize?
|
||||||
|
val maxImageOutputSize = cameraConfig.outputFormats
|
||||||
|
.flatMap { cameraConfig.getOutputSizes(it).toList() }
|
||||||
|
.maxByOrNull { it.width * it.height }!!
|
||||||
|
|
||||||
// TODO: Should I really check MediaRecorder::class instead of SurfaceView::class?
|
val formats = Arguments.createArray()
|
||||||
// Recording should always be done in the most efficient format, which is the format native to the camera framework
|
|
||||||
cameraConfig.getOutputSizes(MediaRecorder::class.java).forEach { size ->
|
|
||||||
val isHighestPhotoQualitySupported = areUltimatelyEqual(size, maxImageOutputSize)
|
|
||||||
|
|
||||||
// Get the number of seconds that each frame will take to process
|
cameraConfig.outputFormats.forEach { formatId ->
|
||||||
val secondsPerFrame = cameraConfig.getOutputMinFrameDuration(MediaRecorder::class.java, size) / 1_000_000_000.0
|
val formatName = parseImageFormat(formatId)
|
||||||
|
|
||||||
val frameRateRanges = Arguments.createArray()
|
cameraConfig.getOutputSizes(formatId).forEach { size ->
|
||||||
if (secondsPerFrame > 0) {
|
val isHighestPhotoQualitySupported = areUltimatelyEqual(size, maxImageOutputSize)
|
||||||
val fps = (1.0 / secondsPerFrame).toInt()
|
|
||||||
val frameRateRange = Arguments.createMap()
|
// Get the number of seconds that each frame will take to process
|
||||||
frameRateRange.putInt("minFrameRate", 1)
|
val secondsPerFrame = try {
|
||||||
frameRateRange.putInt("maxFrameRate", fps)
|
cameraConfig.getOutputMinFrameDuration(formatId, size) / 1_000_000_000.0
|
||||||
frameRateRanges.pushMap(frameRateRange)
|
} catch (error: Throwable) {
|
||||||
|
Log.e(REACT_CLASS, "Minimum Frame Duration for MediaRecorder Output cannot be calculated, format \"$formatName\" is not supported.")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val frameRateRanges = Arguments.createArray()
|
||||||
|
if (secondsPerFrame != null && secondsPerFrame > 0) {
|
||||||
|
val fps = (1.0 / secondsPerFrame).toInt()
|
||||||
|
val frameRateRange = Arguments.createMap()
|
||||||
|
frameRateRange.putInt("minFrameRate", 1)
|
||||||
|
frameRateRange.putInt("maxFrameRate", fps)
|
||||||
|
frameRateRanges.pushMap(frameRateRange)
|
||||||
|
}
|
||||||
|
fpsRanges.forEach { range ->
|
||||||
|
val frameRateRange = Arguments.createMap()
|
||||||
|
frameRateRange.putInt("minFrameRate", range.lower)
|
||||||
|
frameRateRange.putInt("maxFrameRate", range.upper)
|
||||||
|
frameRateRanges.pushMap(frameRateRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorSpaces = Arguments.createArray()
|
||||||
|
colorSpaces.pushString(formatName)
|
||||||
|
|
||||||
|
val videoStabilizationModes = Arguments.createArray()
|
||||||
|
if (stabilizationModes.contains(CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_OFF)) {
|
||||||
|
videoStabilizationModes.pushString("off")
|
||||||
|
}
|
||||||
|
if (stabilizationModes.contains(CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_ON)) {
|
||||||
|
videoStabilizationModes.pushString("auto")
|
||||||
|
videoStabilizationModes.pushString("standard")
|
||||||
|
}
|
||||||
|
|
||||||
|
val format = Arguments.createMap()
|
||||||
|
format.putDouble("photoHeight", size.height.toDouble())
|
||||||
|
format.putDouble("photoWidth", size.width.toDouble())
|
||||||
|
format.putDouble("videoHeight", size.height.toDouble()) // TODO: Revisit getAvailableCameraDevices (videoHeight == photoHeight?)
|
||||||
|
format.putDouble("videoWidth", size.width.toDouble()) // TODO: Revisit getAvailableCameraDevices (videoWidth == photoWidth?)
|
||||||
|
format.putBoolean("isHighestPhotoQualitySupported", isHighestPhotoQualitySupported)
|
||||||
|
format.putInt("maxISO", isoRange?.upper)
|
||||||
|
format.putInt("minISO", isoRange?.lower)
|
||||||
|
format.putDouble("fieldOfView", fieldOfView) // TODO: Revisit getAvailableCameraDevices (is fieldOfView accurate?)
|
||||||
|
format.putDouble("maxZoom", (zoomRange?.upper ?: maxScalerZoom).toDouble())
|
||||||
|
format.putArray("colorSpaces", colorSpaces)
|
||||||
|
format.putBoolean("supportsVideoHDR", false) // TODO: supportsVideoHDR
|
||||||
|
format.putBoolean("supportsPhotoHDR", supportsHdr)
|
||||||
|
format.putArray("frameRateRanges", frameRateRanges)
|
||||||
|
format.putString("autoFocusSystem", "none") // TODO: Revisit getAvailableCameraDevices (autoFocusSystem) (CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES or CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION)
|
||||||
|
format.putArray("videoStabilizationModes", videoStabilizationModes)
|
||||||
|
formats.pushMap(format)
|
||||||
}
|
}
|
||||||
fpsRanges.forEach { range ->
|
|
||||||
val frameRateRange = Arguments.createMap()
|
|
||||||
frameRateRange.putInt("minFrameRate", range.lower)
|
|
||||||
frameRateRange.putInt("maxFrameRate", range.upper)
|
|
||||||
frameRateRanges.pushMap(frameRateRange)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Revisit getAvailableCameraDevices (colorSpaces, more than YUV?)
|
|
||||||
val colorSpaces = Arguments.createArray()
|
|
||||||
colorSpaces.pushString("yuv")
|
|
||||||
|
|
||||||
val videoStabilizationModes = Arguments.createArray()
|
|
||||||
if (stabilizationModes.contains(CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_OFF)) {
|
|
||||||
videoStabilizationModes.pushString("off")
|
|
||||||
}
|
|
||||||
if (stabilizationModes.contains(CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_ON)) {
|
|
||||||
videoStabilizationModes.pushString("auto")
|
|
||||||
videoStabilizationModes.pushString("standard")
|
|
||||||
}
|
|
||||||
|
|
||||||
val format = Arguments.createMap()
|
|
||||||
format.putDouble("photoHeight", size.height.toDouble())
|
|
||||||
format.putDouble("photoWidth", size.width.toDouble())
|
|
||||||
format.putDouble("videoHeight", size.height.toDouble()) // TODO: Revisit getAvailableCameraDevices (videoHeight == photoHeight?)
|
|
||||||
format.putDouble("videoWidth", size.width.toDouble()) // TODO: Revisit getAvailableCameraDevices (videoWidth == photoWidth?)
|
|
||||||
format.putBoolean("isHighestPhotoQualitySupported", isHighestPhotoQualitySupported)
|
|
||||||
format.putInt("maxISO", isoRange?.upper)
|
|
||||||
format.putInt("minISO", isoRange?.lower)
|
|
||||||
format.putDouble("fieldOfView", fieldOfView) // TODO: Revisit getAvailableCameraDevices (is fieldOfView accurate?)
|
|
||||||
format.putDouble("maxZoom", (zoomRange?.upper ?: maxScalerZoom).toDouble())
|
|
||||||
format.putArray("colorSpaces", colorSpaces)
|
|
||||||
format.putBoolean("supportsVideoHDR", false) // TODO: supportsVideoHDR
|
|
||||||
format.putBoolean("supportsPhotoHDR", supportsHdr)
|
|
||||||
format.putArray("frameRateRanges", frameRateRanges)
|
|
||||||
format.putString("autoFocusSystem", "none") // TODO: Revisit getAvailableCameraDevices (autoFocusSystem) (CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES or CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION)
|
|
||||||
format.putArray("videoStabilizationModes", videoStabilizationModes)
|
|
||||||
formats.pushMap(format)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
map.putArray("formats", formats)
|
map.putArray("formats", formats)
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.mrousavy.camera.parsers
|
||||||
|
|
||||||
|
import android.graphics.ImageFormat
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses ImageFormat/PixelFormat int to a string representation useable for the TypeScript types.
|
||||||
|
*/
|
||||||
|
fun parseImageFormat(imageFormat: Int): String {
|
||||||
|
return when (imageFormat) {
|
||||||
|
ImageFormat.YUV_420_888 -> "yuv"
|
||||||
|
ImageFormat.YUV_422_888 -> "yuv"
|
||||||
|
ImageFormat.YUV_444_888 -> "yuv"
|
||||||
|
ImageFormat.JPEG -> "jpeg"
|
||||||
|
ImageFormat.DEPTH_JPEG -> "jpeg-depth"
|
||||||
|
ImageFormat.RAW_SENSOR -> "raw"
|
||||||
|
ImageFormat.RAW_PRIVATE -> "raw"
|
||||||
|
ImageFormat.HEIC -> "heic"
|
||||||
|
ImageFormat.PRIVATE -> "private"
|
||||||
|
ImageFormat.DEPTH16 -> "depth-16"
|
||||||
|
else -> "unknown"
|
||||||
|
/*
|
||||||
|
ImageFormat.UNKNOWN -> "TODOFILL"
|
||||||
|
ImageFormat.RGB_565 -> "TODOFILL"
|
||||||
|
ImageFormat.YV12 -> "TODOFILL"
|
||||||
|
ImageFormat.Y8 -> "TODOFILL"
|
||||||
|
ImageFormat.NV16 -> "TODOFILL"
|
||||||
|
ImageFormat.NV21 -> "TODOFILL"
|
||||||
|
ImageFormat.YUY2 -> "TODOFILL"
|
||||||
|
ImageFormat.FLEX_RGB_888 -> "TODOFILL"
|
||||||
|
ImageFormat.FLEX_RGBA_8888 -> "TODOFILL"
|
||||||
|
ImageFormat.RAW10 -> "TODOFILL"
|
||||||
|
ImageFormat.RAW12 -> "TODOFILL"
|
||||||
|
ImageFormat.DEPTH_POINT_CLOUD -> "TODOFILL"
|
||||||
|
@Suppress("DUPLICATE_LABEL_IN_WHEN")
|
||||||
|
PixelFormat.UNKNOWN -> "TODOFILL"
|
||||||
|
PixelFormat.TRANSPARENT -> "TODOFILL"
|
||||||
|
PixelFormat.TRANSLUCENT -> "TODOFILL"
|
||||||
|
PixelFormat.RGBX_8888 -> "TODOFILL"
|
||||||
|
PixelFormat.RGBA_F16 -> "TODOFILL"
|
||||||
|
PixelFormat.RGBA_8888 -> "TODOFILL"
|
||||||
|
PixelFormat.RGBA_1010102 -> "TODOFILL"
|
||||||
|
PixelFormat.OPAQUE -> "TODOFILL"
|
||||||
|
@Suppress("DUPLICATE_LABEL_IN_WHEN")
|
||||||
|
PixelFormat.RGB_565 -> "TODOFILL"
|
||||||
|
PixelFormat.RGB_888 -> "TODOFILL"
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
@ -46,14 +46,38 @@ export const parsePhysicalDeviceTypes = (
|
|||||||
* Indicates a format's color space.
|
* Indicates a format's color space.
|
||||||
*
|
*
|
||||||
* #### The following colorspaces are available on iOS:
|
* #### The following colorspaces are available on iOS:
|
||||||
* * `"srgb"`: The sGRB color space (https://www.w3.org/Graphics/Color/srgb)
|
* * `"srgb"`: The sGRB color space.
|
||||||
* * `"p3-d65"`: The P3 D65 wide color space which uses Illuminant D65 as the white point
|
* * `"p3-d65"`: The P3 D65 wide color space which uses Illuminant D65 as the white point
|
||||||
* * `"hlg-bt2020"`: The BT2020 wide color space which uses Illuminant D65 as the white point and Hybrid Log-Gamma as the transfer function
|
* * `"hlg-bt2020"`: The BT2020 wide color space which uses Illuminant D65 as the white point and Hybrid Log-Gamma as the transfer function
|
||||||
*
|
*
|
||||||
|
* > See ["AVCaptureColorSpace"](https://developer.apple.com/documentation/avfoundation/avcapturecolorspace) for more information.
|
||||||
|
*
|
||||||
* #### The following colorspaces are available on Android:
|
* #### The following colorspaces are available on Android:
|
||||||
* * `"yuv"`: The YCbCr color space.
|
* * `"yuv"`: The Multi-plane Android YCbCr color space. (YUV 420_888, 422_888 or 444_888)
|
||||||
|
* * `"jpeg"`: The compressed JPEG color space.
|
||||||
|
* * `"jpeg-depth"`: The compressed JPEG color space including depth data.
|
||||||
|
* * `"raw"`: The Camera's RAW sensor color space. (Single-channel Bayer-mosaic image, usually 16 bit)
|
||||||
|
* * `"heic"`: The compressed HEIC color space.
|
||||||
|
* * `"private"`: The Android private opaque image format. (The choices of the actual format and pixel data layout are entirely up to the device-specific and framework internal implementations, and may vary depending on use cases even for the same device. These buffers are not directly accessible to the application)
|
||||||
|
* * `"depth-16"`: The Android dense depth image format (16 bit)
|
||||||
|
* * `"unknown"`: Placeholder for an unknown image/pixel format. [Edit this file](https://github.com/cuvent/react-native-vision-camera/edit/main/android/src/main/java/com/mrousavy/camera/parsers/ImageFormat+String.kt) to add a name for the unknown format.
|
||||||
|
*
|
||||||
|
* > See ["Android Color Formats"](https://jbit.net/Android_Colors/) for more information.
|
||||||
*/
|
*/
|
||||||
export type ColorSpace = 'hlg-bt2020' | 'p3-d65' | 'srgb' | 'yuv';
|
export type ColorSpace =
|
||||||
|
// ios
|
||||||
|
| 'hlg-bt2020'
|
||||||
|
| 'p3-d65'
|
||||||
|
| 'srgb'
|
||||||
|
// android
|
||||||
|
| 'yuv'
|
||||||
|
| 'jpeg'
|
||||||
|
| 'jpeg-depth'
|
||||||
|
| 'raw'
|
||||||
|
| 'heic'
|
||||||
|
| 'private'
|
||||||
|
| 'depth-16'
|
||||||
|
| 'unknown';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates a format's autofocus system.
|
* Indicates a format's autofocus system.
|
||||||
|
Loading…
Reference in New Issue
Block a user