Skip to content

Commit

Permalink
Merge pull request #559 from teslamotors/feature/zoom
Browse files Browse the repository at this point in the history
Added zoom control
  • Loading branch information
scarlac authored Jul 19, 2023
2 parents 0358c56 + 40cc209 commit b2bdc44
Show file tree
Hide file tree
Showing 29 changed files with 496 additions and 120 deletions.
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 {
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

0 comments on commit b2bdc44

Please sign in to comment.