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

Added zoom control #559

Merged
merged 13 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ jobs:
uses: actions/checkout@v2
- name: Install modules
run: yarn
- name: Bootstrap
run: yarn bootstrap
- name: Build
run: cd example/ios && xcodebuild -workspace CameraKitExample.xcworkspace -configuration Debug -scheme CameraKitExample -sdk iphoneos build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
build-example-android:
Expand All @@ -23,7 +21,5 @@ jobs:
uses: gradle/wrapper-validation-action@v1
- name: Install modules
run: yarn
- name: Bootstrap
run: yarn bootstrap
- name: Build
run: cd example/android && ./gradlew assembleDebug
2 changes: 1 addition & 1 deletion .github/workflows/linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Install modules
run: yarn
run: yarn --ignore-scripts
- name: Lint
run: yarn lint
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ example-android/
android/build/
img/
ios/lib/DerivedData/
images/
docs/
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,22 @@ Additionally, the Camera can be used for barcode scanning
| `style` | StyleProp\<ViewStyle> | Style to apply on the camera view |
| `flashMode` | `'on'`/`'off'`/`'auto'` | Camera flash mode. Default: `auto` |
| `focusMode` | `'on'`/`'off'` | Camera focus mode. Default: `on` |
| `zoomMode` | `'on'`/`'off'` | Enable pinch to zoom camera. Default: `on` |
| `zoomMode` | `'on'`/`'off'` | Enable the pinch to zoom gesture. Default: `on` |
| `zoom` | `number` | Control the zoom. Default: `1.0` |
| `maxZoom` | `number` | Maximum zoom allowed (but not beyond what camera allows). Default: `undefined` (camera default max) |
| `onZoom` | Function | Callback when user makes a pinch gesture, regardless of what the `zoom` prop was set to. Returned event contains `zoom`. Ex: `onZoom={(e) => console.log(e.nativeEvent.zoom)}`. |
| `torchMode` | `'on'`/`'off'` | Toggle flash light when camera is active. Default: `off` |
| `cameraType` | CameraType.Back/CameraType.Front | Choose what camera to use. Default: `CameraType.Back` |
| `onOrientationChange` | Function | Callback when physical device orientation changes. Returned event contains `orientation`. Ex: `onOrientationChange={(event) => console.log(event.nativeEvent.orientation)}`. Use `import { Orientation } from 'react-native-camera-kit'; if (event.nativeEvent.orientation === Orientation.PORTRAIT) { ... }` to understand the new value |
| **iOS only** |
| `ratioOverlay` | `'int:int'` | Show a guiding overlay in the camera preview for the selected ratio. Does not crop image as of v9.0. Example: `'16:9'` |
| `ratioOverlayColor` | Color | Any color with alpha. Default: `'#ffffff77'` |
| `resetFocusTimeout` | Number | Dismiss tap to focus after this many milliseconds. Default `0` (disabled). Example: `5000` is 5 seconds. |
| `resetFocusTimeout` | `number` | Dismiss tap to focus after this many milliseconds. Default `0` (disabled). Example: `5000` is 5 seconds. |
| `resetFocusWhenMotionDetected` | Boolean | Dismiss tap to focus when focus area content changes. Native iOS feature, see documentation: https://developer.apple.com/documentation/avfoundation/avcapturedevice/1624644-subjectareachangemonitoringenabl?language=objc). Default `true`. |
| `scanThrottleDelay` | Number | Duration between scan detection in milliseconds. Default 2000 (2s) |
| `scanThrottleDelay` | `number` | Duration between scan detection in milliseconds. Default 2000 (2s) |
| **Barcode only** |
| `scanBarcode` | Boolean | Enable barcode scanner. Default: `false` |
| `showFrame` | Boolean | Show frame in barcode scanner. Default: `false` |
| `scanBarcode` | `boolean` | Enable barcode scanner. Default: `false` |
| `showFrame` | `boolean` | Show frame in barcode scanner. Default: `false` |
| `laserColor` | Color | Color of barcode scanner laser visualization. Default: `red` |
| `frameColor` | Color | Color of barcode scanner frame visualization. Default: `yellow` |
| `onReadCode` | Function | Callback when scanner successfully reads barcode. Returned event contains `codeStringValue`. Default: `null`. Ex: `onReadCode={(event) => console.log(event.nativeEvent.codeStringValue)}` |
Expand Down
111 changes: 102 additions & 9 deletions android/src/main/java/com/rncamerakit/CKCamera.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
private var lensType = CameraSelector.LENS_FACING_BACK
private var autoFocus = "on"
private var zoomMode = "on"
private var lastOnZoom = 0.0
private var zoom: Double? = null
private var maxZoom: Double? = null
private var zoomStartedAt = 1.0f
private var pinchGestureStartedAt = 0.0f

// Barcode Props
private var scanBarcode: Boolean = false
Expand Down Expand Up @@ -180,13 +185,30 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
orientationListener!!.enable()

val scaleDetector = ScaleGestureDetector(context, object: ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
val cameraZoom = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: return false
detector ?: return false
zoomStartedAt = cameraZoom
pinchGestureStartedAt = detector.currentSpan
return true
}
override fun onScale(detector: ScaleGestureDetector?): Boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this may have reverted a change made in https://github.com/teslamotors/react-native-camera-kit/pull/551/files?w=1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @elliottkember - This was re-fixed and released in v14.0.0-beta13.

if (zoomMode == "off") return true
val cameraControl = camera?.cameraControl ?: return true
val zoom = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: return true
val scaleFactor = detector.scaleFactor
val scale = zoom * scaleFactor
cameraControl.setZoomRatio(scale)
if (detector == null) return true
val videoDevice = camera ?: return true
val pinchScale = detector.currentSpan / pinchGestureStartedAt

val desiredZoomFactor = zoomStartedAt * pinchScale
val zoomForDevice = getValidZoom(videoDevice, desiredZoomFactor.toDouble())

if (zoomForDevice != (videoDevice.cameraInfo.zoomState.value?.zoomRatio ?: -1)) {
// Only trigger zoom changes if it's an uncontrolled component (zoom isn't manually set)
// otherwise it's likely to cause issues inf. loops
if (zoom == null) {
videoDevice.cameraControl.setZoomRatio(zoomForDevice.toFloat())
}
onZoom(zoomForDevice)
}
return true
}
})
Expand All @@ -204,6 +226,37 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
}, ContextCompat.getMainExecutor(getActivity()))
}

private fun setZoomFor(videoDevice: Camera, zoom: Double) {
videoDevice.cameraControl.setZoomRatio(zoom.toFloat())
}

private fun resetZoom(videoDevice: Camera) {
var zoomForDevice = getValidZoom(videoDevice, 1.0)
val zoomPropValue = this.zoom
if (zoomPropValue != null) {
zoomForDevice = getValidZoom(videoDevice, zoomPropValue)
}
setZoomFor(videoDevice, zoomForDevice)
this.onZoom(zoomForDevice)
}

private fun getValidZoom(videoDevice: Camera?, zoom: Double): Double {
var zoomOrDefault = zoom
val minZoomFactor = videoDevice?.cameraInfo?.zoomState?.value?.minZoomRatio?.toDouble()
var maxZoomFactor: Double? = videoDevice?.cameraInfo?.zoomState?.value?.maxZoomRatio?.toDouble()
val maxZoom = this.maxZoom
if (maxZoom != null) {
maxZoomFactor = min(maxZoomFactor ?: maxZoom, maxZoom)
}
if (maxZoomFactor != null) {
zoomOrDefault = min(zoomOrDefault, maxZoomFactor)
}
if (minZoomFactor != null) {
zoomOrDefault = max(zoomOrDefault, minZoomFactor)
}
return zoomOrDefault
}

private fun bindCameraUseCases() {
if (viewFinder.display == null) return
// Get screen metrics used to setup camera for full screen resolution
Expand Down Expand Up @@ -264,7 +317,10 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
try {
// A variable number of use-cases can be passed here -
// camera provides access to CameraControl & CameraInfo
camera = cameraProvider.bindToLifecycle(getActivity() as AppCompatActivity, cameraSelector, *useCases.toTypedArray())
val newCamera = cameraProvider.bindToLifecycle(getActivity() as AppCompatActivity, cameraSelector, *useCases.toTypedArray())
camera = newCamera

resetZoom(newCamera)

// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(viewFinder.surfaceProvider)
Expand Down Expand Up @@ -469,8 +525,45 @@ class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObs
}
}

fun setZoomMode(mode: String = "on") {
zoomMode = mode
fun setZoomMode(mode: String?) {
zoomMode = mode ?: "off"
}

fun setZoom(factor: Double?) {
zoom = factor
var zoomOrDefault = zoom ?: return
val videoDevice = camera ?: return

val zoomForDevice = this.getValidZoom(camera, zoomOrDefault)
this.setZoomFor(videoDevice, zoomForDevice)
}

private fun onZoom(desiredZoom: Double?) {
val cameraZoom = camera?.cameraInfo?.zoomState?.value?.zoomRatio?.toDouble() ?: return
val desiredOrCameraZoom = desiredZoom ?: cameraZoom
// ignore duplicate events when zooming to min/max
// but always notify if a desiredZoom wasn't given,
// since that means they wanted to reset setZoom(1.0)
// so we should tell them what zoom it really is
if (desiredZoom != null && desiredOrCameraZoom == lastOnZoom) {
return
}

lastOnZoom = desiredOrCameraZoom
val event: WritableMap = Arguments.createMap()
event.putDouble("zoom", desiredOrCameraZoom)
currentContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
id,
"onZoom",
event
)
}

fun setMaxZoom(factor: Double?) {
maxZoom = factor

// Re-update zoom value in case the max was increased
setZoom(zoom)
}

fun setScanBarcode(enabled: Boolean) {
Expand Down
15 changes: 13 additions & 2 deletions android/src/main/java/com/rncamerakit/CKCameraManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class CKCameraManager : SimpleViewManager<CKCamera>() {
return MapBuilder.of(
"onOrientationChange", MapBuilder.of("registrationName", "onOrientationChange"),
"onReadCode", MapBuilder.of("registrationName", "onReadCode"),
"onPictureTaken", MapBuilder.of("registrationName", "onPictureTaken")
"onPictureTaken", MapBuilder.of("registrationName", "onPictureTaken"),
"onZoom", MapBuilder.of("registrationName", "onZoom")
)
}

Expand All @@ -71,10 +72,20 @@ class CKCameraManager : SimpleViewManager<CKCamera>() {
}

@ReactProp(name = "zoomMode")
fun setZoomMode(view: CKCamera, mode: String) {
fun setZoomMode(view: CKCamera, mode: String?) {
view.setZoomMode(mode)
}

@ReactProp(name = "zoom", defaultDouble = -1.0)
fun setZoom(view: CKCamera, factor: Double) {
view.setZoom(if (factor == -1.0) null else factor)
}

@ReactProp(name = "maxZoom", defaultDouble = 420.0)
fun setMaxZoom(view: CKCamera, factor: Double) {
view.setMaxZoom(factor)
}

@ReactProp(name = "scanBarcode")
fun setScanBarcode(view: CKCamera, enabled: Boolean) {
view.setScanBarcode(enabled)
Expand Down
Binary file removed example/images/cameraButton@2x.png
Binary file not shown.
Binary file added example/images/cameraFlipIcon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed example/images/cameraFlipIcon@2x.png
Binary file not shown.
Binary file removed example/images/hugging.png
Binary file not shown.
Binary file removed example/images/openCamera.png
Binary file not shown.
Binary file removed example/images/openCamera@1.5x.png
Binary file not shown.
Binary file removed example/images/openCamera@2x.png
Binary file not shown.
Binary file removed example/images/openCamera@3x.png
Binary file not shown.
Binary file removed example/images/openCamera@4x.png
Binary file not shown.
Binary file removed example/images/selected.png
Binary file not shown.
Binary file removed example/images/unsupportedImage.png
Binary file not shown.
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost: 57d2868c099736d80fcd648bf211b4431e51a558
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
DoubleConversion: cde416483dac037923206447da6e1454df403714
FBLazyVector: f637f31eacba90d4fdeff3fa41608b8f361c173b
FBReactNativeSpec: 0d9a4f4de7ab614c49e98c00aedfd3bfbda33d59
Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0
Expand All @@ -588,7 +588,7 @@ SPEC CHECKSUMS:
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
hermes-engine: 47986d26692ae75ee7a17ab049caee8864f855de
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
Expand Down
Loading
Loading