@capgo/camera-preview 8.8.1 → 8.10.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.
- package/README.md +63 -3
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java +47 -2
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java +176 -12
- package/dist/docs.json +112 -0
- package/dist/esm/definitions.d.ts +48 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +10 -1
- package/dist/esm/web.js +52 -4
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +51 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +51 -4
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +115 -2
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +78 -11
- package/package.json +1 -1
|
@@ -623,7 +623,10 @@ extension CameraController {
|
|
|
623
623
|
self.photoOutput?.isHighResolutionCaptureEnabled = true
|
|
624
624
|
|
|
625
625
|
// Create video output
|
|
626
|
-
|
|
626
|
+
let movieFileOutput = AVCaptureMovieFileOutput()
|
|
627
|
+
// MP4 does not support movie fragments; the default 10s interval drops the final partial segment on stop.
|
|
628
|
+
movieFileOutput.movieFragmentInterval = .invalid
|
|
629
|
+
self.fileVideoOutput = movieFileOutput
|
|
627
630
|
|
|
628
631
|
// Create data output for preview
|
|
629
632
|
self.dataOutput = AVCaptureVideoDataOutput()
|
|
@@ -1004,6 +1007,10 @@ extension CameraController {
|
|
|
1004
1007
|
finalDevice.exposurePointOfInterest = CGPoint(x: 0.5, y: 0.5)
|
|
1005
1008
|
}
|
|
1006
1009
|
}
|
|
1010
|
+
// Default white balance to continuous auto (prevents a warm/yellow cast)
|
|
1011
|
+
if finalDevice.isWhiteBalanceModeSupported(.continuousAutoWhiteBalance) {
|
|
1012
|
+
finalDevice.whiteBalanceMode = .continuousAutoWhiteBalance
|
|
1013
|
+
}
|
|
1007
1014
|
// Reset exposure compensation so sessions start neutral
|
|
1008
1015
|
let minBias = finalDevice.minExposureTargetBias
|
|
1009
1016
|
let maxBias = finalDevice.maxExposureTargetBias
|
|
@@ -1441,6 +1448,19 @@ extension CameraController {
|
|
|
1441
1448
|
photoOutput.capturePhoto(with: settings, delegate: self)
|
|
1442
1449
|
}
|
|
1443
1450
|
|
|
1451
|
+
/// Horizontally mirrors image pixels (selfie-style). Prefer this over orientation-only flips.
|
|
1452
|
+
func mirrorImageHorizontally(_ image: UIImage) -> UIImage {
|
|
1453
|
+
let base = image.fixedOrientation() ?? image
|
|
1454
|
+
let format = UIGraphicsImageRendererFormat.default()
|
|
1455
|
+
format.scale = base.scale
|
|
1456
|
+
format.opaque = false
|
|
1457
|
+
return UIGraphicsImageRenderer(size: base.size, format: format).image { ctx in
|
|
1458
|
+
ctx.cgContext.translateBy(x: base.size.width, y: 0)
|
|
1459
|
+
ctx.cgContext.scaleBy(x: -1, y: 1)
|
|
1460
|
+
base.draw(in: CGRect(origin: .zero, size: base.size))
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1444
1464
|
/// Draws timestamp and/or location pills at the top-right. Pass nil to skip either line.
|
|
1445
1465
|
func drawTimestampAndLocation(on image: UIImage, when: String?, where whereStr: String?) -> UIImage {
|
|
1446
1466
|
let base = image.fixedOrientation() ?? image
|
|
@@ -2184,7 +2204,6 @@ extension CameraController {
|
|
|
2184
2204
|
}
|
|
2185
2205
|
}
|
|
2186
2206
|
|
|
2187
|
-
|
|
2188
2207
|
private func cancelPendingFocusExposureRestore() {
|
|
2189
2208
|
self.focusExposureRestoreGeneration &+= 1
|
|
2190
2209
|
self.focusExposureRestoreTimeoutWorkItem?.cancel()
|
|
@@ -2721,6 +2740,100 @@ extension CameraController {
|
|
|
2721
2740
|
}
|
|
2722
2741
|
}
|
|
2723
2742
|
|
|
2743
|
+
// MARK: - White Balance Controls
|
|
2744
|
+
|
|
2745
|
+
func getWhiteBalanceModes() throws -> [String] {
|
|
2746
|
+
var currentCamera: AVCaptureDevice?
|
|
2747
|
+
switch currentCameraPosition {
|
|
2748
|
+
case .front:
|
|
2749
|
+
currentCamera = self.frontCamera
|
|
2750
|
+
case .rear:
|
|
2751
|
+
currentCamera = self.rearCamera
|
|
2752
|
+
default:
|
|
2753
|
+
break
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
guard let device = currentCamera else {
|
|
2757
|
+
throw CameraControllerError.noCamerasAvailable
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
var modes: [String] = []
|
|
2761
|
+
if device.isWhiteBalanceModeSupported(.locked) { modes.append("LOCK") }
|
|
2762
|
+
if device.isWhiteBalanceModeSupported(.autoWhiteBalance) { modes.append("AUTO") }
|
|
2763
|
+
if device.isWhiteBalanceModeSupported(.continuousAutoWhiteBalance) { modes.append("CONTINUOUS") }
|
|
2764
|
+
return modes
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
func getWhiteBalanceMode() throws -> String {
|
|
2768
|
+
var currentCamera: AVCaptureDevice?
|
|
2769
|
+
switch currentCameraPosition {
|
|
2770
|
+
case .front:
|
|
2771
|
+
currentCamera = self.frontCamera
|
|
2772
|
+
case .rear:
|
|
2773
|
+
currentCamera = self.rearCamera
|
|
2774
|
+
default:
|
|
2775
|
+
break
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
guard let device = currentCamera else {
|
|
2779
|
+
throw CameraControllerError.noCamerasAvailable
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
switch device.whiteBalanceMode {
|
|
2783
|
+
case .locked:
|
|
2784
|
+
return "LOCK"
|
|
2785
|
+
case .autoWhiteBalance:
|
|
2786
|
+
return "AUTO"
|
|
2787
|
+
case .continuousAutoWhiteBalance:
|
|
2788
|
+
return "CONTINUOUS"
|
|
2789
|
+
@unknown default:
|
|
2790
|
+
return "CONTINUOUS"
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
func setWhiteBalanceMode(mode: String) throws {
|
|
2795
|
+
var currentCamera: AVCaptureDevice?
|
|
2796
|
+
switch currentCameraPosition {
|
|
2797
|
+
case .front:
|
|
2798
|
+
currentCamera = self.frontCamera
|
|
2799
|
+
case .rear:
|
|
2800
|
+
currentCamera = self.rearCamera
|
|
2801
|
+
default:
|
|
2802
|
+
break
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
guard let device = currentCamera else {
|
|
2806
|
+
throw CameraControllerError.noCamerasAvailable
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
let normalized = mode.uppercased()
|
|
2810
|
+
let desiredMode: AVCaptureDevice.WhiteBalanceMode?
|
|
2811
|
+
switch normalized {
|
|
2812
|
+
case "LOCK":
|
|
2813
|
+
desiredMode = .locked
|
|
2814
|
+
case "AUTO":
|
|
2815
|
+
desiredMode = .autoWhiteBalance
|
|
2816
|
+
case "CONTINUOUS":
|
|
2817
|
+
desiredMode = .continuousAutoWhiteBalance
|
|
2818
|
+
case "CUSTOM":
|
|
2819
|
+
throw CameraControllerError.invalidOperation
|
|
2820
|
+
default:
|
|
2821
|
+
throw CameraControllerError.invalidOperation
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
guard let finalMode = desiredMode, device.isWhiteBalanceModeSupported(finalMode) else {
|
|
2825
|
+
throw CameraControllerError.invalidOperation
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
do {
|
|
2829
|
+
try device.lockForConfiguration()
|
|
2830
|
+
device.whiteBalanceMode = finalMode
|
|
2831
|
+
device.unlockForConfiguration()
|
|
2832
|
+
} catch {
|
|
2833
|
+
throw CameraControllerError.invalidOperation
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2724
2837
|
private func activeVideoDevice() -> AVCaptureDevice? {
|
|
2725
2838
|
switch currentCameraPosition {
|
|
2726
2839
|
case .front:
|
|
@@ -35,7 +35,7 @@ extension UIWindow {
|
|
|
35
35
|
*/
|
|
36
36
|
@objc(CameraPreview)
|
|
37
37
|
public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelegate {
|
|
38
|
-
private let pluginVersion: String = "8.
|
|
38
|
+
private let pluginVersion: String = "8.10.0"
|
|
39
39
|
public let identifier = "CameraPreviewPlugin"
|
|
40
40
|
public let jsName = "CameraPreview"
|
|
41
41
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -90,6 +90,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
90
90
|
CAPPluginMethod(name: "getExposureCompensationRange", returnType: CAPPluginReturnPromise),
|
|
91
91
|
CAPPluginMethod(name: "getExposureCompensation", returnType: CAPPluginReturnPromise),
|
|
92
92
|
CAPPluginMethod(name: "setExposureCompensation", returnType: CAPPluginReturnPromise),
|
|
93
|
+
CAPPluginMethod(name: "getWhiteBalanceModes", returnType: CAPPluginReturnPromise),
|
|
94
|
+
CAPPluginMethod(name: "getWhiteBalanceMode", returnType: CAPPluginReturnPromise),
|
|
95
|
+
CAPPluginMethod(name: "setWhiteBalanceMode", returnType: CAPPluginReturnPromise),
|
|
93
96
|
CAPPluginMethod(name: "getSupportedVideoFrameRates", returnType: CAPPluginReturnPromise),
|
|
94
97
|
CAPPluginMethod(name: "getVideoFrameRate", returnType: CAPPluginReturnPromise),
|
|
95
98
|
CAPPluginMethod(name: "setVideoFrameRate", returnType: CAPPluginReturnPromise),
|
|
@@ -1262,10 +1265,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1262
1265
|
let width = call.getInt("width")
|
|
1263
1266
|
let height = call.getInt("height")
|
|
1264
1267
|
let photoQualityPrioritization = call.getString("photoQualityPrioritization", "speed")
|
|
1268
|
+
let mirrorFrontCamera = call.getBool("mirrorFrontCamera", false) ?? false
|
|
1265
1269
|
|
|
1266
1270
|
print("[CameraPreview] Raw parameter values - width: \(String(describing: width)), height: \(String(describing: height))")
|
|
1267
1271
|
|
|
1268
|
-
print("[CameraPreview] Capture params - quality: \(quality), saveToGallery: \(saveToGallery), withExifLocation: \(withExifLocation ?? false), embedTimestamp: \(embedTimestamp), embedLocation: \(effectiveEmbedLocation) (requested=\(embedLocationRequested)), width: \(width ?? -1), height: \(height ?? -1)")
|
|
1272
|
+
print("[CameraPreview] Capture params - quality: \(quality), saveToGallery: \(saveToGallery), withExifLocation: \(withExifLocation ?? false), embedTimestamp: \(embedTimestamp), embedLocation: \(effectiveEmbedLocation) (requested=\(embedLocationRequested)), width: \(width ?? -1), height: \(height ?? -1), mirrorFrontCamera: \(mirrorFrontCamera)")
|
|
1269
1273
|
print("[CameraPreview] Current location: \(self.currentLocation?.description ?? "nil")")
|
|
1270
1274
|
// Safely read frame from main thread for logging
|
|
1271
1275
|
let (previewWidth, previewHeight): (CGFloat, CGFloat) = {
|
|
@@ -1300,9 +1304,21 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1300
1304
|
return
|
|
1301
1305
|
}
|
|
1302
1306
|
|
|
1303
|
-
guard let
|
|
1304
|
-
|
|
1305
|
-
|
|
1307
|
+
guard let capturedImage = image else {
|
|
1308
|
+
print("[CameraPreview] Failed to create image data with EXIF")
|
|
1309
|
+
call.reject("Failed to create image data with EXIF")
|
|
1310
|
+
return
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
let imageToEncode: UIImage
|
|
1314
|
+
if mirrorFrontCamera, self.cameraPosition == "front" {
|
|
1315
|
+
imageToEncode = self.cameraController.mirrorImageHorizontally(capturedImage)
|
|
1316
|
+
} else {
|
|
1317
|
+
imageToEncode = capturedImage
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
guard let imageDataWithExif = self.createImageDataWithExif(
|
|
1321
|
+
from: imageToEncode,
|
|
1306
1322
|
quality: Int(quality),
|
|
1307
1323
|
location: withExifLocation ? self.currentLocation : nil,
|
|
1308
1324
|
heading: withExifLocation ? self.currentHeading : nil,
|
|
@@ -1517,6 +1533,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1517
1533
|
|
|
1518
1534
|
@objc func captureSample(_ call: CAPPluginCall) {
|
|
1519
1535
|
let quality: Int = call.getInt("quality") ?? 85
|
|
1536
|
+
let mirrorFrontCamera = call.getBool("mirrorFrontCamera", false) ?? false
|
|
1520
1537
|
|
|
1521
1538
|
self.cameraController.captureSample { image, error in
|
|
1522
1539
|
guard let image = image else {
|
|
@@ -1525,13 +1542,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1525
1542
|
return
|
|
1526
1543
|
}
|
|
1527
1544
|
|
|
1528
|
-
|
|
1529
|
-
if self.cameraPosition == "front" {
|
|
1530
|
-
|
|
1531
|
-
imageData = flippedImage.jpegData(compressionQuality: CGFloat(quality)/100)
|
|
1532
|
-
} else {
|
|
1533
|
-
imageData = image.jpegData(compressionQuality: CGFloat(quality)/100)
|
|
1545
|
+
var outputImage = image
|
|
1546
|
+
if mirrorFrontCamera, self.cameraPosition == "front" {
|
|
1547
|
+
outputImage = self.cameraController.mirrorImageHorizontally(image)
|
|
1534
1548
|
}
|
|
1549
|
+
let imageData = outputImage.jpegData(compressionQuality: CGFloat(quality)/100)
|
|
1535
1550
|
|
|
1536
1551
|
if self.storeToFile == false {
|
|
1537
1552
|
guard let imageBase64 = imageData?.base64EncodedString() else {
|
|
@@ -2392,6 +2407,58 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
2392
2407
|
}
|
|
2393
2408
|
}
|
|
2394
2409
|
|
|
2410
|
+
// MARK: - White Balance Bridge
|
|
2411
|
+
|
|
2412
|
+
@objc func getWhiteBalanceModes(_ call: CAPPluginCall) {
|
|
2413
|
+
guard isInitialized else {
|
|
2414
|
+
call.reject("Camera not initialized")
|
|
2415
|
+
return
|
|
2416
|
+
}
|
|
2417
|
+
do {
|
|
2418
|
+
let modes = try self.cameraController.getWhiteBalanceModes()
|
|
2419
|
+
call.resolve(["modes": modes])
|
|
2420
|
+
} catch {
|
|
2421
|
+
call.reject("Failed to get white balance modes: \(error.localizedDescription)")
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
@objc func getWhiteBalanceMode(_ call: CAPPluginCall) {
|
|
2426
|
+
guard isInitialized else {
|
|
2427
|
+
call.reject("Camera not initialized")
|
|
2428
|
+
return
|
|
2429
|
+
}
|
|
2430
|
+
do {
|
|
2431
|
+
let mode = try self.cameraController.getWhiteBalanceMode()
|
|
2432
|
+
call.resolve(["mode": mode])
|
|
2433
|
+
} catch {
|
|
2434
|
+
call.reject("Failed to get white balance mode: \(error.localizedDescription)")
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
@objc func setWhiteBalanceMode(_ call: CAPPluginCall) {
|
|
2439
|
+
guard isInitialized else {
|
|
2440
|
+
call.reject("Camera not initialized")
|
|
2441
|
+
return
|
|
2442
|
+
}
|
|
2443
|
+
guard let mode = call.getString("mode") else {
|
|
2444
|
+
call.reject("mode parameter is required")
|
|
2445
|
+
return
|
|
2446
|
+
}
|
|
2447
|
+
let normalized = mode.uppercased()
|
|
2448
|
+
let allowedModes: Set<String> = ["AUTO", "LOCK", "CONTINUOUS"]
|
|
2449
|
+
guard allowedModes.contains(normalized) else {
|
|
2450
|
+
let allowedList = Array(allowedModes).sorted().joined(separator: ", ")
|
|
2451
|
+
call.reject("Invalid white balance mode: \(mode). Allowed values: \(allowedList)")
|
|
2452
|
+
return
|
|
2453
|
+
}
|
|
2454
|
+
do {
|
|
2455
|
+
try self.cameraController.setWhiteBalanceMode(mode: normalized)
|
|
2456
|
+
call.resolve()
|
|
2457
|
+
} catch {
|
|
2458
|
+
call.reject("Failed to set white balance mode: \(error.localizedDescription)")
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2395
2462
|
// MARK: - Exposure Bridge
|
|
2396
2463
|
|
|
2397
2464
|
@objc func getExposureModes(_ call: CAPPluginCall) {
|