@carviz/capacitor-camera-preview 7.0.0

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.
Files changed (56) hide show
  1. package/CarvizCapacitorCameraPreview.podspec +17 -0
  2. package/LICENSE +22 -0
  3. package/README.md +229 -0
  4. package/android/.project +17 -0
  5. package/android/build.gradle +57 -0
  6. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  7. package/android/gradle/wrapper/gradle-wrapper.properties +8 -0
  8. package/android/gradle.properties +18 -0
  9. package/android/gradlew +248 -0
  10. package/android/gradlew.bat +92 -0
  11. package/android/proguard-rules.pro +21 -0
  12. package/android/settings.gradle +2 -0
  13. package/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +26 -0
  14. package/android/src/main/AndroidManifest.xml +5 -0
  15. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +831 -0
  16. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +396 -0
  17. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +23 -0
  18. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +29 -0
  19. package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +384 -0
  20. package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +24 -0
  21. package/android/src/main/res/layout/bridge_layout_main.xml +15 -0
  22. package/android/src/main/res/layout/camera_activity.xml +68 -0
  23. package/android/src/main/res/values/camera_ids.xml +4 -0
  24. package/android/src/main/res/values/camera_theme.xml +9 -0
  25. package/android/src/main/res/values/colors.xml +3 -0
  26. package/android/src/main/res/values/strings.xml +3 -0
  27. package/android/src/main/res/values/styles.xml +3 -0
  28. package/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +18 -0
  29. package/dist/esm/definitions.d.ts +86 -0
  30. package/dist/esm/definitions.js +2 -0
  31. package/dist/esm/definitions.js.map +1 -0
  32. package/dist/esm/index.d.ts +4 -0
  33. package/dist/esm/index.js +7 -0
  34. package/dist/esm/index.js.map +1 -0
  35. package/dist/esm/web.d.ts +24 -0
  36. package/dist/esm/web.js +120 -0
  37. package/dist/esm/web.js.map +1 -0
  38. package/dist/plugin.cjs.js +134 -0
  39. package/dist/plugin.cjs.js.map +1 -0
  40. package/dist/plugin.js +137 -0
  41. package/dist/plugin.js.map +1 -0
  42. package/ios/Plugin/CameraController.swift +483 -0
  43. package/ios/Plugin/Info.plist +24 -0
  44. package/ios/Plugin/Plugin.h +10 -0
  45. package/ios/Plugin/Plugin.m +16 -0
  46. package/ios/Plugin/Plugin.swift +324 -0
  47. package/ios/Plugin/UIImage.swift +56 -0
  48. package/ios/Plugin.xcodeproj/project.pbxproj +599 -0
  49. package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  50. package/ios/Plugin.xcworkspace/contents.xcworkspacedata +10 -0
  51. package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  52. package/ios/PluginTests/Info.plist +22 -0
  53. package/ios/PluginTests/PluginTests.swift +16 -0
  54. package/ios/Podfile +16 -0
  55. package/ios/Podfile.lock +22 -0
  56. package/package.json +85 -0
@@ -0,0 +1,324 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import AVFoundation
4
+ /**
5
+ * Please read the Capacitor iOS Plugin Development Guide
6
+ * here: https://capacitor.ionicframework.com/docs/plugins/ios
7
+ */
8
+ @objc(CameraPreview)
9
+ public class CameraPreview: CAPPlugin {
10
+ let cameraController = CameraController()
11
+ var cameraPosition: CameraPosition = .rear
12
+ var x: CGFloat = 0.0
13
+ var y: CGFloat = 0.0
14
+ var previewView: UIView?
15
+ var previewWidth: CGFloat = UIScreen.main.bounds.size.width
16
+ var previewHeight: CGFloat = UIScreen.main.bounds.size.height
17
+ var paddingBottom: CGFloat = 0
18
+ var rotateWhenOrientationChanged = true
19
+ var toBack = false
20
+ var storeToFile = false
21
+ var enableZoom = false
22
+ var enableHighResolution = false
23
+
24
+ /**
25
+ Start the camera preview in a new UIView
26
+ */
27
+ @objc public func start(_ call: CAPPluginCall) {
28
+ AVCaptureDevice.requestAccess(for: AVMediaType.video) { [weak self] granted in
29
+ guard granted else {
30
+ call.reject("camera access not granted")
31
+ return
32
+ }
33
+
34
+ // Initialize settings provided via API call
35
+ self?.initializePluginSettings(call: call)
36
+
37
+ if let captureSession = self?.cameraController.captureSession, captureSession.isRunning {
38
+ call.reject("camera already started")
39
+ return
40
+ }
41
+
42
+ self?.cameraController.prepare(cameraPosition: self?.cameraPosition, enableHighResolution: self?.enableHighResolution ?? false) { error in
43
+ if let error = error {
44
+ call.reject(error.localizedDescription)
45
+ return
46
+ }
47
+
48
+ DispatchQueue.main.async {
49
+ self?.displayCameraPreviewView()
50
+ call.resolve()
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ Stops any currently running capture session
58
+ */
59
+ @objc public func stop(_ call: CAPPluginCall) {
60
+ guard self.cameraController.captureSession?.isRunning ?? false else {
61
+ call.reject("camera already stopped")
62
+ return;
63
+ }
64
+
65
+ DispatchQueue.main.async {
66
+ self.cameraController.stop()
67
+ self.previewView?.removeFromSuperview()
68
+ self.webView?.isOpaque = true
69
+ call.resolve()
70
+ }
71
+ }
72
+
73
+ /**
74
+ Capture a photo with the currently active capture device
75
+ */
76
+ @objc func capture(_ call: CAPPluginCall) {
77
+ DispatchQueue.main.async {
78
+ let quality: Int = call.getInt("quality", 85)
79
+
80
+ self.cameraController.captureImage { (image, error) in
81
+ guard let image = image else {
82
+ print(error ?? "Image capture error")
83
+ guard let error = error else {
84
+ call.reject("Image capture error")
85
+ return
86
+ }
87
+
88
+ call.reject(error.localizedDescription)
89
+ return
90
+ }
91
+
92
+ let imageData = image.jpegData(compressionQuality: CGFloat(quality / 100))
93
+
94
+ if self.storeToFile == false {
95
+ let imageBase64 = imageData?.base64EncodedString()
96
+ call.resolve(["value": imageBase64!])
97
+ } else {
98
+ do {
99
+ let fileUrl = self.getTempFilePath()
100
+ try imageData?.write(to: fileUrl)
101
+ call.resolve(["value": fileUrl.absoluteString])
102
+ } catch {
103
+ call.reject("error writing image to file")
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ @objc public func flip(_ call: CAPPluginCall) {
111
+ do {
112
+ try self.cameraController.switchCameras()
113
+ call.resolve()
114
+ } catch {
115
+ call.reject("failed to flip camera")
116
+ }
117
+ }
118
+
119
+ /**
120
+ Captures a sample image from the video stream.
121
+ */
122
+ @objc func captureSample(_ call: CAPPluginCall) {
123
+ DispatchQueue.main.async {
124
+ let quality: Int = call.getInt("quality", 85)
125
+
126
+ self.cameraController.captureSample { image, error in
127
+ guard let image = image else {
128
+ print("Image capture error: \(String(describing: error))")
129
+ call.reject("Image capture error: \(String(describing: error))")
130
+ return
131
+ }
132
+
133
+ let imageData: Data?
134
+ if self.cameraPosition == .front {
135
+ let flippedImage = image.withHorizontallyFlippedOrientation()
136
+ imageData = flippedImage.jpegData(compressionQuality: CGFloat(quality / 100))
137
+ } else {
138
+ imageData = image.jpegData(compressionQuality: CGFloat(quality / 100))
139
+ }
140
+
141
+ if self.storeToFile == false {
142
+ let imageBase64 = imageData?.base64EncodedString()
143
+ call.resolve(["value": imageBase64!])
144
+ } else {
145
+ do {
146
+ let fileUrl = self.getTempFilePath()
147
+ try imageData?.write(to: fileUrl)
148
+ call.resolve(["value": fileUrl.absoluteString])
149
+ } catch {
150
+ call.reject("Error writing image to file")
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ Return an array of supported flash modes of the currently active capture device
159
+ */
160
+ @objc func getSupportedFlashModes(_ call: CAPPluginCall) {
161
+ do {
162
+ let supportedFlashModes = try self.cameraController.getSupportedFlashModes()
163
+ call.resolve(["result": supportedFlashModes])
164
+ } catch {
165
+ call.reject("failed to get supported flash modes")
166
+ }
167
+ }
168
+
169
+ /**
170
+ Set the flash mode for the currently active capture device
171
+ */
172
+ @objc func setFlashMode(_ call: CAPPluginCall) {
173
+ guard let flashMode = call.getString("flashMode") else {
174
+ call.reject("failed to set flash mode. required parameter flashMode is missing")
175
+ return
176
+ }
177
+
178
+ var flashModeAsEnum: AVCaptureDevice.FlashMode?
179
+ switch flashMode {
180
+ case "off":
181
+ flashModeAsEnum = AVCaptureDevice.FlashMode.off
182
+ case "on":
183
+ flashModeAsEnum = AVCaptureDevice.FlashMode.on
184
+ case "auto":
185
+ flashModeAsEnum = AVCaptureDevice.FlashMode.auto
186
+ default: break
187
+ }
188
+
189
+ do {
190
+ if flashModeAsEnum != nil {
191
+ try self.cameraController.setFlashMode(flashMode: flashModeAsEnum!)
192
+ } else if flashMode == "torch" {
193
+ try self.cameraController.setTorchMode()
194
+ } else {
195
+ call.reject("Flash Mode not supported")
196
+ return
197
+ }
198
+
199
+ call.resolve()
200
+ } catch {
201
+ call.reject("failed to set flash mode")
202
+ }
203
+ }
204
+
205
+ /**
206
+ Helper method for initializing the plugin settings based on the Capacitor call
207
+ */
208
+ private func initializePluginSettings(call: CAPPluginCall) {
209
+ self.cameraPosition = call.getString("position") == "front" ? .front : .rear
210
+
211
+ if let previewWidth = call.getInt("width") {
212
+ self.previewWidth = CGFloat(previewWidth)
213
+ } else {
214
+ self.previewWidth = UIScreen.main.bounds.size.width
215
+ }
216
+
217
+ if let previewHeight = call.getInt("height") {
218
+ self.previewHeight = CGFloat(previewHeight)
219
+ } else {
220
+ self.previewHeight = UIScreen.main.bounds.size.height
221
+ }
222
+
223
+ self.x = CGFloat(call.getInt("x", 0)) / 2
224
+ self.y = CGFloat(call.getInt("y", 0)) / 2
225
+
226
+ self.paddingBottom = CGFloat(call.getInt("paddingBottom", 0))
227
+
228
+ self.rotateWhenOrientationChanged = call.getBool("rotateWhenOrientationChanged") ?? true
229
+
230
+ self.toBack = call.getBool("toBack") ?? false
231
+
232
+ self.storeToFile = call.getBool("storeToFile") ?? false
233
+
234
+ self.enableZoom = call.getBool("enableZoom") ?? false
235
+
236
+ self.enableHighResolution = call.getBool("enableHighResolution", false)
237
+ }
238
+
239
+ @objc override public func checkPermissions(_ call: CAPPluginCall) {
240
+ let cameraState: String
241
+
242
+ switch AVCaptureDevice.authorizationStatus(for: .video) {
243
+ case .notDetermined:
244
+ cameraState = "prompt"
245
+ case .restricted, .denied:
246
+ cameraState = "denied"
247
+ case .authorized:
248
+ cameraState = "granted"
249
+ @unknown default:
250
+ cameraState = "prompt"
251
+ }
252
+
253
+ call.resolve(["camera": cameraState])
254
+ }
255
+
256
+ @objc public override func requestPermissions(_ call: CAPPluginCall) {
257
+ AVCaptureDevice.requestAccess(for: .video) { [weak self] _ in
258
+ self?.checkPermissions(call)
259
+ }
260
+ }
261
+
262
+ /**
263
+ Displays the camera preview view in the UI
264
+ */
265
+ private func displayCameraPreviewView() {
266
+ self.previewView = UIView(frame: CGRect(x: self.x, y: self.y, width: self.previewWidth, height: self.previewHeight - self.paddingBottom))
267
+ self.webView?.isOpaque = false
268
+ self.webView?.backgroundColor = UIColor.clear
269
+ self.webView?.scrollView.backgroundColor = UIColor.clear
270
+ self.webView?.superview?.addSubview(self.previewView!)
271
+
272
+ if self.toBack {
273
+ self.webView?.superview?.bringSubviewToFront(self.webView!)
274
+ }
275
+
276
+ if self.rotateWhenOrientationChanged {
277
+ NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
278
+ }
279
+
280
+ self.cameraController.displayPreview(on: self.previewView!)
281
+
282
+ let frontView = self.toBack ? self.webView : self.previewView!
283
+ self.cameraController.setupGestures(target: frontView ?? self.previewView!, enableZoom: self.enableZoom)
284
+ }
285
+
286
+ /**
287
+ Handler funciton for updating the previewLayer frame based on plugin settings and the current device orientation
288
+ */
289
+ @objc private func rotated() {
290
+ guard let previewView = self.previewView else {
291
+ return
292
+ }
293
+
294
+ guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
295
+ return
296
+ }
297
+
298
+ let interfaceOrientation = windowScene.interfaceOrientation
299
+ let height = self.previewHeight - self.paddingBottom
300
+
301
+ if interfaceOrientation.isLandscape {
302
+ previewView.frame = CGRect(x: self.y, y: self.x, width: max(height, self.previewWidth), height: min(height, self.previewWidth))
303
+ cameraController.previewLayer.frame = previewView.frame
304
+ } else if interfaceOrientation.isPortrait {
305
+ previewView.frame = CGRect(x: self.x, y: self.y, width: min(height, self.previewWidth), height: max(height, self.previewWidth))
306
+ cameraController.previewLayer.frame = previewView.frame
307
+ }
308
+
309
+ cameraController.updateVideoOrientation()
310
+ }
311
+
312
+ /**
313
+ Get user's cache directory path
314
+ */
315
+ private func getTempFilePath() -> URL {
316
+ let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
317
+ let randomIdentifier = UUID().uuidString.replacingOccurrences(of: "-", with: "")
318
+ let finalIdentifier = String(randomIdentifier.prefix(8))
319
+ let fileName="cpcp_capture_"+finalIdentifier+".jpg"
320
+ let fileUrl=path.appendingPathComponent(fileName)
321
+
322
+ return fileUrl
323
+ }
324
+ }
@@ -0,0 +1,56 @@
1
+ extension UIImage {
2
+ func fixedOrientation() -> UIImage? {
3
+ guard imageOrientation != UIImage.Orientation.up else {
4
+ // This is default orientation, don't need to do anything
5
+ return self.copy() as? UIImage
6
+ }
7
+
8
+ guard let cgImage = self.cgImage else {
9
+ // CGImage is not available
10
+ return nil
11
+ }
12
+
13
+ guard let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
14
+ // Not able to create CGContext
15
+ return nil
16
+ }
17
+
18
+ var transform: CGAffineTransform = CGAffineTransform.identity
19
+
20
+ switch imageOrientation {
21
+ case .down, .downMirrored:
22
+ transform = transform.translatedBy(x: size.width, y: size.height)
23
+ transform = transform.rotated(by: CGFloat.pi)
24
+ print("down")
25
+ break
26
+ case .left, .leftMirrored:
27
+ transform = transform.translatedBy(x: size.width, y: 0)
28
+ transform = transform.rotated(by: CGFloat.pi / 2.0)
29
+ print("left")
30
+ break
31
+ case .right, .rightMirrored:
32
+ transform = transform.translatedBy(x: 0, y: size.height)
33
+ transform = transform.rotated(by: CGFloat.pi / -2.0)
34
+ print("right")
35
+ break
36
+ case .up, .upMirrored:
37
+ break
38
+ @unknown default:
39
+ break
40
+ }
41
+
42
+ ctx.concatenate(transform)
43
+
44
+ switch imageOrientation {
45
+ case .left, .leftMirrored, .right, .rightMirrored:
46
+ ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
47
+ default:
48
+ ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
49
+ break
50
+ }
51
+
52
+ guard let newCGImage = ctx.makeImage() else { return nil }
53
+
54
+ return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
55
+ }
56
+ }