expo-camera 15.0.2 → 15.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 15.0.3 — 2024-04-29
14
+
15
+ ### 🎉 New features
16
+
17
+ - Support scanning barcodes from a provided image URL. ([#28445](https://github.com/expo/expo/pull/28445) by [@alanjhughes](https://github.com/alanjhughes))
18
+
13
19
  ## 15.0.2 — 2024-04-24
14
20
 
15
21
  ### 🐛 Bug fixes
@@ -1,7 +1,7 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'host.exp.exponent'
4
- version = '15.0.2'
4
+ version = '15.0.3'
5
5
 
6
6
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
7
  apply from: expoModulesCorePlugin
@@ -14,7 +14,7 @@ android {
14
14
  namespace "expo.modules.camera"
15
15
  defaultConfig {
16
16
  versionCode 32
17
- versionName "15.0.2"
17
+ versionName "15.0.3"
18
18
  }
19
19
  }
20
20
 
@@ -6,4 +6,7 @@ class CameraExceptions {
6
6
  class ImageCaptureFailed : CodedException(message = "Failed to capture image")
7
7
 
8
8
  class VideoRecordingFailed(cause: String?) : CodedException("Video recording failed: $cause")
9
+
10
+ class ImageRetrievalException(url: String) :
11
+ CodedException("Could not get the image from given url: '$url'")
9
12
  }
@@ -1,8 +1,12 @@
1
1
  package expo.modules.camera
2
2
 
3
3
  import android.Manifest
4
+ import android.graphics.Bitmap
4
5
  import android.util.Log
6
+ import expo.modules.camera.analyzers.BarCodeScannerResultSerializer
7
+ import expo.modules.camera.analyzers.MLKitBarCodeScanner
5
8
  import expo.modules.camera.records.BarcodeSettings
9
+ import expo.modules.camera.records.BarcodeType
6
10
  import expo.modules.camera.records.CameraMode
7
11
  import expo.modules.camera.records.CameraType
8
12
  import expo.modules.camera.records.FlashMode
@@ -10,6 +14,7 @@ import expo.modules.camera.records.VideoQuality
10
14
  import expo.modules.camera.tasks.ResolveTakenPicture
11
15
  import expo.modules.core.errors.ModuleDestroyedException
12
16
  import expo.modules.core.utilities.EmulatorUtilities
17
+ import expo.modules.interfaces.imageloader.ImageLoaderInterface
13
18
  import expo.modules.interfaces.permissions.Permissions
14
19
  import expo.modules.kotlin.Promise
15
20
  import expo.modules.kotlin.exception.Exceptions
@@ -33,6 +38,7 @@ val cameraEvents = arrayOf(
33
38
 
34
39
  class CameraViewModule : Module() {
35
40
  private val moduleScope = CoroutineScope(Dispatchers.Main)
41
+
36
42
  override fun definition() = ModuleDefinition {
37
43
  Name("ExpoCamera")
38
44
 
@@ -70,6 +76,30 @@ class CameraViewModule : Module() {
70
76
  )
71
77
  }
72
78
 
79
+ AsyncFunction("scanFromURLAsync") { url: String, barcodeTypes: List<BarcodeType>, promise: Promise ->
80
+ appContext.imageLoader?.loadImageForManipulationFromURL(
81
+ url,
82
+ object : ImageLoaderInterface.ResultListener {
83
+ override fun onSuccess(bitmap: Bitmap) {
84
+ val scanner = MLKitBarCodeScanner()
85
+ val formats = barcodeTypes.map { it.mapToBarcode() }
86
+ scanner.setSettings(formats)
87
+
88
+ moduleScope.launch {
89
+ val barcodes = scanner.scan(bitmap)
90
+ .filter { formats.contains(it.type) }
91
+ .map { BarCodeScannerResultSerializer.toBundle(it, 1.0f) }
92
+ promise.resolve(barcodes)
93
+ }
94
+ }
95
+
96
+ override fun onFailure(cause: Throwable?) {
97
+ promise.reject(CameraExceptions.ImageRetrievalException(url))
98
+ }
99
+ }
100
+ )
101
+ }
102
+
73
103
  OnDestroy {
74
104
  try {
75
105
  moduleScope.cancel(ModuleDestroyedException())
@@ -0,0 +1,48 @@
1
+ package expo.modules.camera.analyzers
2
+
3
+ import android.os.Bundle
4
+ import android.util.Pair
5
+ import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
6
+
7
+ object BarCodeScannerResultSerializer {
8
+ fun toBundle(result: BarCodeScannerResult, density: Float) =
9
+ Bundle().apply {
10
+ putString("data", result.value)
11
+ putString("raw", result.raw)
12
+ putInt("type", result.type)
13
+ val cornerPointsAndBoundingBox = getCornerPointsAndBoundingBox(result.cornerPoints, result.boundingBox, density)
14
+ putParcelableArrayList("cornerPoints", cornerPointsAndBoundingBox.first)
15
+ putBundle("bounds", cornerPointsAndBoundingBox.second)
16
+ }
17
+
18
+ private fun getCornerPointsAndBoundingBox(
19
+ cornerPoints: List<Int>,
20
+ boundingBox: BarCodeScannerResult.BoundingBox,
21
+ density: Float
22
+ ): Pair<ArrayList<Bundle>, Bundle> {
23
+ val convertedCornerPoints = ArrayList<Bundle>()
24
+ for (i in cornerPoints.indices step 2) {
25
+ val x = cornerPoints[i].toFloat() / density
26
+ val y = cornerPoints[i + 1].toFloat() / density
27
+
28
+ convertedCornerPoints.add(getPoint(x, y))
29
+ }
30
+ val boundingBoxBundle = Bundle().apply {
31
+ putParcelable("origin", getPoint(boundingBox.x.toFloat() / density, boundingBox.y.toFloat() / density))
32
+ putParcelable("size", getSize(boundingBox.width.toFloat() / density, boundingBox.height.toFloat() / density))
33
+ }
34
+ return Pair(convertedCornerPoints, boundingBoxBundle)
35
+ }
36
+
37
+ private fun getSize(width: Float, height: Float) =
38
+ Bundle().apply {
39
+ putFloat("width", width)
40
+ putFloat("height", height)
41
+ }
42
+
43
+ private fun getPoint(x: Float, y: Float) =
44
+ Bundle().apply {
45
+ putFloat("x", x)
46
+ putFloat("y", y)
47
+ }
48
+ }
@@ -0,0 +1,102 @@
1
+ package expo.modules.camera.analyzers
2
+
3
+ import android.graphics.Bitmap
4
+ import android.util.Log
5
+ import com.google.android.gms.tasks.Task
6
+ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
7
+ import com.google.mlkit.vision.barcode.BarcodeScanning
8
+ import com.google.mlkit.vision.barcode.common.Barcode
9
+ import com.google.mlkit.vision.common.InputImage
10
+ import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
11
+ import kotlinx.coroutines.Dispatchers
12
+ import kotlinx.coroutines.suspendCancellableCoroutine
13
+ import kotlinx.coroutines.withContext
14
+ import kotlin.coroutines.resume
15
+ import kotlin.coroutines.resumeWithException
16
+
17
+ class MLKitBarCodeScanner {
18
+ private var barCodeTypes: List<Int>? = null
19
+ private var barcodeScannerOptions =
20
+ BarcodeScannerOptions.Builder()
21
+ .setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
22
+ .build()
23
+ private var barcodeScanner = BarcodeScanning.getClient(barcodeScannerOptions)
24
+
25
+ suspend fun scan(bitmap: Bitmap): List<BarCodeScannerResult> = withContext(Dispatchers.IO) {
26
+ val inputImage = InputImage.fromBitmap(bitmap, 0)
27
+ try {
28
+ val result: List<Barcode> = barcodeScanner.process(inputImage).await()
29
+ val results = mutableListOf<BarCodeScannerResult>()
30
+ if (result.isEmpty()) {
31
+ return@withContext results
32
+ }
33
+ for (barcode in result) {
34
+ val raw = barcode.rawValue ?: barcode.rawBytes?.let { String(it) }
35
+ val value = if (barcode.valueType == Barcode.TYPE_CONTACT_INFO) {
36
+ raw
37
+ } else {
38
+ barcode.displayValue
39
+ }
40
+ val cornerPoints = mutableListOf<Int>()
41
+ barcode.cornerPoints?.let { points ->
42
+ for (point in points) {
43
+ cornerPoints.addAll(listOf(point.x, point.y))
44
+ }
45
+ }
46
+
47
+ results.add(BarCodeScannerResult(barcode.format, value, raw, cornerPoints, inputImage.height, inputImage.width))
48
+ }
49
+ return@withContext results
50
+ } catch (e: Exception) {
51
+ Log.e(TAG, "Failed to detect barcode: " + e.message)
52
+ return@withContext emptyList()
53
+ }
54
+ }
55
+
56
+ fun setSettings(formats: List<Int>) {
57
+ if (areNewAndOldBarCodeTypesEqual(formats)) {
58
+ return
59
+ }
60
+ val barcodeFormats = formats.reduce { acc, it ->
61
+ acc or it
62
+ }
63
+
64
+ barCodeTypes = formats
65
+ barcodeScannerOptions = BarcodeScannerOptions.Builder()
66
+ .setBarcodeFormats(barcodeFormats)
67
+ .build()
68
+ barcodeScanner = BarcodeScanning.getClient(barcodeScannerOptions)
69
+ }
70
+
71
+ private fun areNewAndOldBarCodeTypesEqual(newBarCodeTypes: List<Int>): Boolean {
72
+ barCodeTypes?.run {
73
+ // create distinct-values sets
74
+ val prevTypesSet = toHashSet()
75
+ val nextTypesSet = newBarCodeTypes.toHashSet()
76
+
77
+ // sets sizes are equal -> possible content equality
78
+ if (prevTypesSet.size == nextTypesSet.size) {
79
+ prevTypesSet.removeAll(nextTypesSet)
80
+ // every element from new set was in previous one -> sets are equal
81
+ return prevTypesSet.isEmpty()
82
+ }
83
+ }
84
+ return false
85
+ }
86
+
87
+ companion object {
88
+ private val TAG = MLKitBarCodeScanner::class.java.simpleName
89
+ }
90
+ }
91
+
92
+ suspend fun <T> Task<T>.await(): T = suspendCancellableCoroutine { continuation ->
93
+ addOnSuccessListener { result ->
94
+ continuation.resume(result)
95
+ }
96
+ addOnFailureListener { exception ->
97
+ continuation.resumeWithException(exception)
98
+ }
99
+ addOnCanceledListener {
100
+ continuation.cancel()
101
+ }
102
+ }
package/build/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { BarcodeScanningResult, BarcodeType } from './Camera.types';
1
2
  import { PermissionResponse } from './legacy/Camera.types';
2
3
  export { default as CameraView } from './CameraView';
3
4
  /**
@@ -42,6 +43,17 @@ declare function requestMicrophonePermissionsAsync(): Promise<PermissionResponse
42
43
  * ```
43
44
  */
44
45
  export declare const useMicrophonePermissions: (options?: import("expo-modules-core").PermissionHookOptions<object> | undefined) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>];
46
+ /**
47
+ * Scan bar codes from the image at the given URL.
48
+ * @param url URL to get the image from.
49
+ * @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on
50
+ * the platform.
51
+ * > __Note:__ Only QR codes are supported on iOS.
52
+ * On android, the barcode should take up the majority of the image for best results.
53
+ * @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type
54
+ * refers to the barcode type that was scanned and the data is the information encoded in the barcode.
55
+ */
56
+ export declare function scanFromURLAsync(url: string, barcodeTypes?: BarcodeType[]): Promise<BarcodeScanningResult>;
45
57
  export * from './Camera.types';
46
58
  /**
47
59
  * @hidden
@@ -51,5 +63,6 @@ export declare const Camera: {
51
63
  requestCameraPermissionsAsync: typeof requestCameraPermissionsAsync;
52
64
  getMicrophonePermissionsAsync: typeof getMicrophonePermissionsAsync;
53
65
  requestMicrophonePermissionsAsync: typeof requestMicrophonePermissionsAsync;
66
+ scanFromURLAsync: typeof scanFromURLAsync;
54
67
  };
55
68
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAGrD;;;GAGG;AACH,iBAAe,yBAAyB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEtE;AAGD;;;;GAIG;AACH,iBAAe,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE1E;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,wLAG/B,CAAC;AAGH;;;GAGG;AACH,iBAAe,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE1E;AAGD;;;;GAIG;AACH,iBAAe,iCAAiC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE9E;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,wLAGnC,CAAC;AAEH,cAAc,gBAAgB,CAAC;AAE/B;;GAEG;AACH,eAAO,MAAM,MAAM;;;;;CAKlB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAGrD;;;GAGG;AACH,iBAAe,yBAAyB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEtE;AAGD;;;;GAIG;AACH,iBAAe,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE1E;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,wLAG/B,CAAC;AAGH;;;GAGG;AACH,iBAAe,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE1E;AAGD;;;;GAIG;AACH,iBAAe,iCAAiC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE9E;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,wLAGnC,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,YAAY,GAAE,WAAW,EAAW,GACnC,OAAO,CAAC,qBAAqB,CAAC,CAEhC;AAED,cAAc,gBAAgB,CAAC;AAE/B;;GAEG;AACH,eAAO,MAAM,MAAM;;;;;;CAMlB,CAAC"}
package/build/index.js CHANGED
@@ -63,6 +63,19 @@ export const useMicrophonePermissions = createPermissionHook({
63
63
  getMethod: getMicrophonePermissionsAsync,
64
64
  requestMethod: requestMicrophonePermissionsAsync,
65
65
  });
66
+ /**
67
+ * Scan bar codes from the image at the given URL.
68
+ * @param url URL to get the image from.
69
+ * @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on
70
+ * the platform.
71
+ * > __Note:__ Only QR codes are supported on iOS.
72
+ * On android, the barcode should take up the majority of the image for best results.
73
+ * @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type
74
+ * refers to the barcode type that was scanned and the data is the information encoded in the barcode.
75
+ */
76
+ export async function scanFromURLAsync(url, barcodeTypes = ['qr']) {
77
+ return CameraManager.scanFromURLAsync(url, barcodeTypes);
78
+ }
66
79
  export * from './Camera.types';
67
80
  /**
68
81
  * @hidden
@@ -72,5 +85,6 @@ export const Camera = {
72
85
  requestCameraPermissionsAsync,
73
86
  getMicrophonePermissionsAsync,
74
87
  requestMicrophonePermissionsAsync,
88
+ scanFromURLAsync,
75
89
  };
76
90
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAErD,cAAc;AACd;;;GAGG;AACH,KAAK,UAAU,yBAAyB;IACtC,OAAO,aAAa,CAAC,yBAAyB,EAAE,CAAC;AACnD,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,KAAK,UAAU,6BAA6B;IAC1C,OAAO,aAAa,CAAC,6BAA6B,EAAE,CAAC;AACvD,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;IACvD,SAAS,EAAE,yBAAyB;IACpC,aAAa,EAAE,6BAA6B;CAC7C,CAAC,CAAC;AAEH,cAAc;AACd;;;GAGG;AACH,KAAK,UAAU,6BAA6B;IAC1C,OAAO,aAAa,CAAC,6BAA6B,EAAE,CAAC;AACvD,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,KAAK,UAAU,iCAAiC;IAC9C,OAAO,aAAa,CAAC,iCAAiC,EAAE,CAAC;AAC3D,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;IAC3D,SAAS,EAAE,6BAA6B;IACxC,aAAa,EAAE,iCAAiC;CACjD,CAAC,CAAC;AAEH,cAAc,gBAAgB,CAAC;AAE/B;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,yBAAyB;IACzB,6BAA6B;IAC7B,6BAA6B;IAC7B,iCAAiC;CAClC,CAAC","sourcesContent":["import { createPermissionHook } from 'expo-modules-core';\n\nimport CameraManager from './ExpoCameraManager';\nimport { PermissionResponse } from './legacy/Camera.types';\n\nexport { default as CameraView } from './CameraView';\n\n// @needsAudit\n/**\n * Checks user's permissions for accessing camera.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function getCameraPermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.getCameraPermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Asks the user to grant permissions for accessing camera.\n * On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function requestCameraPermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.requestCameraPermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Check or request permissions to access the camera.\n * This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = useCameraPermissions();\n * ```\n */\nexport const useCameraPermissions = createPermissionHook({\n getMethod: getCameraPermissionsAsync,\n requestMethod: requestCameraPermissionsAsync,\n});\n\n// @needsAudit\n/**\n * Checks user's permissions for accessing microphone.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function getMicrophonePermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.getMicrophonePermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Asks the user to grant permissions for accessing the microphone.\n * On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function requestMicrophonePermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.requestMicrophonePermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Check or request permissions to access the microphone.\n * This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = Camera.useMicrophonePermissions();\n * ```\n */\nexport const useMicrophonePermissions = createPermissionHook({\n getMethod: getMicrophonePermissionsAsync,\n requestMethod: requestMicrophonePermissionsAsync,\n});\n\nexport * from './Camera.types';\n\n/**\n * @hidden\n */\nexport const Camera = {\n getCameraPermissionsAsync,\n requestCameraPermissionsAsync,\n getMicrophonePermissionsAsync,\n requestMicrophonePermissionsAsync,\n};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAErD,cAAc;AACd;;;GAGG;AACH,KAAK,UAAU,yBAAyB;IACtC,OAAO,aAAa,CAAC,yBAAyB,EAAE,CAAC;AACnD,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,KAAK,UAAU,6BAA6B;IAC1C,OAAO,aAAa,CAAC,6BAA6B,EAAE,CAAC;AACvD,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;IACvD,SAAS,EAAE,yBAAyB;IACpC,aAAa,EAAE,6BAA6B;CAC7C,CAAC,CAAC;AAEH,cAAc;AACd;;;GAGG;AACH,KAAK,UAAU,6BAA6B;IAC1C,OAAO,aAAa,CAAC,6BAA6B,EAAE,CAAC;AACvD,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,KAAK,UAAU,iCAAiC;IAC9C,OAAO,aAAa,CAAC,iCAAiC,EAAE,CAAC;AAC3D,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;IAC3D,SAAS,EAAE,6BAA6B;IACxC,aAAa,EAAE,iCAAiC;CACjD,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAW,EACX,eAA8B,CAAC,IAAI,CAAC;IAEpC,OAAO,aAAa,CAAC,gBAAgB,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;AAC3D,CAAC;AAED,cAAc,gBAAgB,CAAC;AAE/B;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,yBAAyB;IACzB,6BAA6B;IAC7B,6BAA6B;IAC7B,iCAAiC;IACjC,gBAAgB;CACjB,CAAC","sourcesContent":["import { createPermissionHook } from 'expo-modules-core';\n\nimport { BarcodeScanningResult, BarcodeType } from './Camera.types';\nimport CameraManager from './ExpoCameraManager';\nimport { PermissionResponse } from './legacy/Camera.types';\n\nexport { default as CameraView } from './CameraView';\n\n// @needsAudit\n/**\n * Checks user's permissions for accessing camera.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function getCameraPermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.getCameraPermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Asks the user to grant permissions for accessing camera.\n * On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function requestCameraPermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.requestCameraPermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Check or request permissions to access the camera.\n * This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = useCameraPermissions();\n * ```\n */\nexport const useCameraPermissions = createPermissionHook({\n getMethod: getCameraPermissionsAsync,\n requestMethod: requestCameraPermissionsAsync,\n});\n\n// @needsAudit\n/**\n * Checks user's permissions for accessing microphone.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function getMicrophonePermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.getMicrophonePermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Asks the user to grant permissions for accessing the microphone.\n * On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function requestMicrophonePermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.requestMicrophonePermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Check or request permissions to access the microphone.\n * This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = Camera.useMicrophonePermissions();\n * ```\n */\nexport const useMicrophonePermissions = createPermissionHook({\n getMethod: getMicrophonePermissionsAsync,\n requestMethod: requestMicrophonePermissionsAsync,\n});\n\n/**\n * Scan bar codes from the image at the given URL.\n * @param url URL to get the image from.\n * @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on\n * the platform.\n * > __Note:__ Only QR codes are supported on iOS.\n * On android, the barcode should take up the majority of the image for best results.\n * @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type\n * refers to the barcode type that was scanned and the data is the information encoded in the barcode.\n */\nexport async function scanFromURLAsync(\n url: string,\n barcodeTypes: BarcodeType[] = ['qr']\n): Promise<BarcodeScanningResult> {\n return CameraManager.scanFromURLAsync(url, barcodeTypes);\n}\n\nexport * from './Camera.types';\n\n/**\n * @hidden\n */\nexport const Camera = {\n getCameraPermissionsAsync,\n requestCameraPermissionsAsync,\n getMicrophonePermissionsAsync,\n requestMicrophonePermissionsAsync,\n scanFromURLAsync,\n};\n"]}
@@ -37,6 +37,37 @@ public final class CameraViewModule: Module, ScannerResultHandler {
37
37
  return false
38
38
  }
39
39
 
40
+ AsyncFunction("scanFromURLAsync") { (url: URL, _: [BarcodeType], promise: Promise) in
41
+ guard let imageLoader = appContext?.imageLoader else {
42
+ throw ImageLoaderNotFound()
43
+ }
44
+
45
+ imageLoader.loadImage(for: url) { error, image in
46
+ if error != nil {
47
+ promise.reject(FailedToLoadImage())
48
+ return
49
+ }
50
+
51
+ guard let cgImage = image?.cgImage else {
52
+ promise.reject(FailedToLoadImage())
53
+ return
54
+ }
55
+
56
+ guard let detector = CIDetector(
57
+ ofType: CIDetectorTypeQRCode,
58
+ context: nil,
59
+ options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]
60
+ ) else {
61
+ promise.reject(InitScannerFailed())
62
+ return
63
+ }
64
+
65
+ let ciImage = CIImage(cgImage: cgImage)
66
+ let features = detector.features(in: ciImage)
67
+ promise.resolve(BarcodeUtils.getResultFrom(features))
68
+ }
69
+ }
70
+
40
71
  // swiftlint:disable:next closure_body_length
41
72
  View(CameraView.self) {
42
73
  Events(cameraNextEvents)
@@ -0,0 +1,19 @@
1
+ import ExpoModulesCore
2
+
3
+ internal class ImageLoaderNotFound: Exception {
4
+ override var reason: String {
5
+ "Image Loader module not found"
6
+ }
7
+ }
8
+
9
+ internal class FailedToLoadImage: Exception {
10
+ override var reason: String {
11
+ "Could not get the image"
12
+ }
13
+ }
14
+
15
+ internal class InitScannerFailed: Exception {
16
+ override var reason: String {
17
+ "Could not initialize the barcode scanner"
18
+ }
19
+ }
@@ -0,0 +1,70 @@
1
+ import AVFoundation
2
+
3
+ struct BarcodeUtils {
4
+ static func getResultFrom(_ features: [CIFeature]) -> [[AnyHashable: Any]?] {
5
+ var result = [[AnyHashable: Any]?]()
6
+
7
+ for feature in features {
8
+ if let qrCodeFeature = feature as? CIQRCodeFeature {
9
+ let item = ciQRCodeFeature(
10
+ codeFeature: qrCodeFeature
11
+ )
12
+ result.append(item)
13
+ }
14
+ }
15
+
16
+ return result
17
+ }
18
+
19
+ static func ciQRCodeFeature(codeFeature: CIQRCodeFeature) -> [String: Any] {
20
+ var result: [String: Any] = [:]
21
+ result["type"] = "qr"
22
+ result["data"] = codeFeature.messageString
23
+
24
+ if !codeFeature.bounds.isEmpty {
25
+ result["cornerPoints"] = [
26
+ codeFeature.topLeft,
27
+ codeFeature.topRight,
28
+ codeFeature.bottomRight,
29
+ codeFeature.bottomLeft
30
+ ].map { point in
31
+ [
32
+ "x": point.x,
33
+ "y": point.y
34
+ ]
35
+ }
36
+
37
+ let origin = codeFeature.bounds.origin
38
+ let size = codeFeature.bounds.size
39
+
40
+ result["bounds"] = [
41
+ "origin": [
42
+ "x": origin.x,
43
+ "y": origin.y
44
+ ],
45
+ "size": [
46
+ "width": size.width,
47
+ "height": size.height
48
+ ]
49
+ ]
50
+ } else {
51
+ addEmptyCornerPoints(to: &result)
52
+ }
53
+
54
+ return result
55
+ }
56
+
57
+ static func addEmptyCornerPoints(to result: inout [String: Any]) {
58
+ result["cornerPoints"] = []
59
+ result["bounds"] = [
60
+ "origin": [
61
+ "x": 0,
62
+ "y": 0
63
+ ],
64
+ "size": [
65
+ "width": 0,
66
+ "height": 0
67
+ ]
68
+ ]
69
+ }
70
+ }
@@ -24,7 +24,7 @@ struct ExpoCameraUtils {
24
24
  }
25
25
 
26
26
  return orientation
27
- }
27
+ }
28
28
 
29
29
  static func videoOrientation(for interfaceOrientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation {
30
30
  switch interfaceOrientation {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-camera",
3
- "version": "15.0.2",
3
+ "version": "15.0.3",
4
4
  "description": "A React component that renders a preview for the device's either front or back camera. Camera's parameters like zoom, auto focus, white balance and flash mode are adjustable. With expo-camera, one can also take photos and record videos that are saved to the app's cache. Morever, the component is also capable of detecting faces and bar codes appearing on the preview.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -42,5 +42,5 @@
42
42
  "peerDependencies": {
43
43
  "expo": "*"
44
44
  },
45
- "gitHead": "8b4cb45563b85c2ec91b1b249d136661bf6e8981"
45
+ "gitHead": "4a7cf0d0baf6dfc595d93f604945d2142e705a36"
46
46
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createPermissionHook } from 'expo-modules-core';
2
2
 
3
+ import { BarcodeScanningResult, BarcodeType } from './Camera.types';
3
4
  import CameraManager from './ExpoCameraManager';
4
5
  import { PermissionResponse } from './legacy/Camera.types';
5
6
 
@@ -73,6 +74,23 @@ export const useMicrophonePermissions = createPermissionHook({
73
74
  requestMethod: requestMicrophonePermissionsAsync,
74
75
  });
75
76
 
77
+ /**
78
+ * Scan bar codes from the image at the given URL.
79
+ * @param url URL to get the image from.
80
+ * @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on
81
+ * the platform.
82
+ * > __Note:__ Only QR codes are supported on iOS.
83
+ * On android, the barcode should take up the majority of the image for best results.
84
+ * @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type
85
+ * refers to the barcode type that was scanned and the data is the information encoded in the barcode.
86
+ */
87
+ export async function scanFromURLAsync(
88
+ url: string,
89
+ barcodeTypes: BarcodeType[] = ['qr']
90
+ ): Promise<BarcodeScanningResult> {
91
+ return CameraManager.scanFromURLAsync(url, barcodeTypes);
92
+ }
93
+
76
94
  export * from './Camera.types';
77
95
 
78
96
  /**
@@ -83,4 +101,5 @@ export const Camera = {
83
101
  requestCameraPermissionsAsync,
84
102
  getMicrophonePermissionsAsync,
85
103
  requestMicrophonePermissionsAsync,
104
+ scanFromURLAsync,
86
105
  };