@trustchex/react-native-sdk 1.472.0 → 1.475.1
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/android/build.gradle +3 -3
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +147 -9
- package/ios/Camera/TrustchexCameraView.swift +92 -19
- package/lib/module/Screens/Debug/MRZTestScreen.js +121 -147
- package/lib/module/Screens/Static/ResultScreen.js +5 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +38 -4
- package/lib/module/Shared/Libs/MRZ_KNOWN_ISSUES.md +112 -0
- package/lib/module/Shared/Libs/mrz.utils.js +639 -16
- package/lib/module/Shared/Libs/mrzFrameAggregator.js +301 -0
- package/lib/module/Shared/Libs/mrzOcrIntegration.js +109 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +8 -0
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts +76 -0
- package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/mrzOcrIntegration.d.ts +73 -0
- package/lib/typescript/src/Shared/Libs/mrzOcrIntegration.d.ts.map +1 -0
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +15 -10
- package/src/Screens/Debug/MRZTestScreen.tsx +137 -166
- package/src/Screens/Static/ResultScreen.tsx +5 -0
- package/src/Shared/Components/IdentityDocumentCamera.tsx +46 -6
- package/src/Shared/Libs/MRZ_KNOWN_ISSUES.md +112 -0
- package/src/Shared/Libs/mrz.utils.ts +704 -11
- package/src/Shared/Libs/mrzFrameAggregator.ts +370 -0
- package/src/Shared/Libs/mrzOcrIntegration.ts +175 -0
- package/src/version.ts +1 -1
package/android/build.gradle
CHANGED
|
@@ -83,9 +83,9 @@ dependencies {
|
|
|
83
83
|
implementation "androidx.camera:camera-video:1.4.0"
|
|
84
84
|
|
|
85
85
|
// Google ML Kit dependencies
|
|
86
|
-
implementation 'com.google.mlkit:text-recognition:16.0.
|
|
87
|
-
implementation 'com.google.mlkit:face-detection:16.1.
|
|
88
|
-
implementation 'com.google.mlkit:barcode-scanning:17.
|
|
86
|
+
implementation 'com.google.mlkit:text-recognition:16.0.1'
|
|
87
|
+
implementation 'com.google.mlkit:face-detection:16.1.7'
|
|
88
|
+
implementation 'com.google.mlkit:barcode-scanning:17.3.0'
|
|
89
89
|
|
|
90
90
|
// Google Play Services dependency for Tasks
|
|
91
91
|
implementation 'com.google.android.gms:play-services-tasks:18.2.0'
|
|
@@ -452,9 +452,20 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
452
452
|
// Brightness calculation restricted to scanning frame area (between 36% from top and 36% from bottom, 5% margins on sides)
|
|
453
453
|
val averageBrightness = computeYPlaneBrightness(imageProxy, reportedWidth, reportedHeight)
|
|
454
454
|
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
|
|
455
|
+
// Text recognition: process ONLY the data inside the on-screen scan
|
|
456
|
+
// frame. We capture the full image but crop to the scan-frame ROI
|
|
457
|
+
// (top 36% / bottom 36% / 5% side margins — the same rectangle the UI
|
|
458
|
+
// guide and brightness sampling use) and UPSCALE it before OCR. This
|
|
459
|
+
// (a) excludes front-of-card text and surrounding noise (screen glare,
|
|
460
|
+
// IDE/browser chrome) that pollute the MRZ read, and (b) gives ML Kit
|
|
461
|
+
// more pixels per character — ML Kit needs ≥16px/char, and a full-frame
|
|
462
|
+
// capture leaves a 30–44-char MRZ line below that floor. Block
|
|
463
|
+
// coordinates from the ROI pass are mapped back to the full portrait
|
|
464
|
+
// frame so the JS overlay/filters stay correct.
|
|
465
|
+
val roi = if (textRecognitionEnabled) {
|
|
466
|
+
buildScanFrameRoiImage(imageProxy, rotationDegrees, reportedWidth, reportedHeight)
|
|
467
|
+
} else null
|
|
468
|
+
val textInputImage = roi?.image ?: inputImage
|
|
458
469
|
|
|
459
470
|
// Generate JPEG base64 only when JS side explicitly needs the image
|
|
460
471
|
// NOTE: Do NOT auto-generate for face detection - too expensive, causes frame drops
|
|
@@ -539,13 +550,23 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
539
550
|
blockMap.putString("text", block.text)
|
|
540
551
|
val bb = block.boundingBox
|
|
541
552
|
if (bb != null) {
|
|
553
|
+
// When OCR ran on the upscaled scan-frame ROI, map
|
|
554
|
+
// the box back to full portrait-frame coordinates:
|
|
555
|
+
// divide by the ROI upscale, then add the ROI origin.
|
|
556
|
+
val s = roi?.scale ?: 1f
|
|
557
|
+
val ox = roi?.offsetX ?: 0
|
|
558
|
+
val oy = roi?.offsetY ?: 0
|
|
559
|
+
val left = (bb.left / s).toInt() + ox
|
|
560
|
+
val top = (bb.top / s).toInt() + oy
|
|
561
|
+
val w = (bb.width() / s).toInt()
|
|
562
|
+
val h = (bb.height() / s).toInt()
|
|
542
563
|
val boxMap = Arguments.createMap()
|
|
543
|
-
boxMap.putInt("x",
|
|
544
|
-
boxMap.putInt("y",
|
|
545
|
-
boxMap.putInt("width",
|
|
546
|
-
boxMap.putInt("height",
|
|
547
|
-
boxMap.putInt("boundingCenterX",
|
|
548
|
-
boxMap.putInt("boundingCenterY",
|
|
564
|
+
boxMap.putInt("x", left)
|
|
565
|
+
boxMap.putInt("y", top)
|
|
566
|
+
boxMap.putInt("width", w)
|
|
567
|
+
boxMap.putInt("height", h)
|
|
568
|
+
boxMap.putInt("boundingCenterX", left + w / 2)
|
|
569
|
+
boxMap.putInt("boundingCenterY", top + h / 2)
|
|
549
570
|
blockMap.putMap("blockFrame", boxMap)
|
|
550
571
|
}
|
|
551
572
|
blocksArray.pushMap(blockMap)
|
|
@@ -1017,6 +1038,123 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
1017
1038
|
}
|
|
1018
1039
|
}
|
|
1019
1040
|
|
|
1041
|
+
/** A scan-frame ROI prepared for OCR, with the transform back to full-frame coords. */
|
|
1042
|
+
private data class ScanFrameRoi(
|
|
1043
|
+
val image: InputImage,
|
|
1044
|
+
val offsetX: Int,
|
|
1045
|
+
val offsetY: Int,
|
|
1046
|
+
val scale: Float
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Builds an [InputImage] cropped to the on-screen scan frame (top 36% /
|
|
1051
|
+
* bottom 36% / 5% side margins — the same rectangle the UI guide draws) and
|
|
1052
|
+
* UPSCALED so ML Kit gets more pixels per character. We capture the full frame
|
|
1053
|
+
* but OCR only this region, which removes front-of-card text and surrounding
|
|
1054
|
+
* noise and lifts a dense MRZ line above ML Kit's ≥16px/char floor. Returns
|
|
1055
|
+
* null on any failure so the caller falls back to full-frame OCR.
|
|
1056
|
+
*/
|
|
1057
|
+
private fun buildScanFrameRoiImage(
|
|
1058
|
+
imageProxy: ImageProxy,
|
|
1059
|
+
rotationDegrees: Int,
|
|
1060
|
+
portraitWidth: Int,
|
|
1061
|
+
portraitHeight: Int
|
|
1062
|
+
): ScanFrameRoi? {
|
|
1063
|
+
return try {
|
|
1064
|
+
val full = yuvImageToBitmap(imageProxy, rotationDegrees) ?: return null
|
|
1065
|
+
|
|
1066
|
+
// Scan-frame rectangle in the rotated (portrait) bitmap. Covers the
|
|
1067
|
+
// lower-middle band where the MRZ lands, with generous vertical
|
|
1068
|
+
// tolerance (top 25% → bottom 12%) so the band doesn't have to be
|
|
1069
|
+
// perfectly centered to be OCR'd. Keep it large enough for tolerance
|
|
1070
|
+
// but not the whole frame (front text/glare would creep back in).
|
|
1071
|
+
val scanTopPercent = 0.25
|
|
1072
|
+
val scanBottomPercent = 0.12
|
|
1073
|
+
val scanLeftPercent = 0.04
|
|
1074
|
+
val scanRightPercent = 0.04
|
|
1075
|
+
val left = (full.width * scanLeftPercent).toInt().coerceIn(0, full.width - 1)
|
|
1076
|
+
val right = (full.width * (1.0 - scanRightPercent)).toInt().coerceIn(left + 1, full.width)
|
|
1077
|
+
val top = (full.height * scanTopPercent).toInt().coerceIn(0, full.height - 1)
|
|
1078
|
+
val bottom = (full.height * (1.0 - scanBottomPercent)).toInt().coerceIn(top + 1, full.height)
|
|
1079
|
+
val cropW = right - left
|
|
1080
|
+
val cropH = bottom - top
|
|
1081
|
+
if (cropW < 8 || cropH < 8) {
|
|
1082
|
+
full.recycle()
|
|
1083
|
+
return null
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
val cropped = Bitmap.createBitmap(full, left, top, cropW, cropH)
|
|
1087
|
+
full.recycle()
|
|
1088
|
+
|
|
1089
|
+
// Upscale so a ~30–44-char MRZ line clears the per-character pixel
|
|
1090
|
+
// minimum. Cap the long edge to keep OCR latency bounded.
|
|
1091
|
+
val maxLongEdge = 2200
|
|
1092
|
+
val longEdge = max(cropW, cropH)
|
|
1093
|
+
val rawScale = if (longEdge > 0) (maxLongEdge.toFloat() / longEdge) else 1f
|
|
1094
|
+
val scale = rawScale.coerceIn(1f, 2.5f)
|
|
1095
|
+
val outW = (cropW * scale).toInt()
|
|
1096
|
+
val outH = (cropH * scale).toInt()
|
|
1097
|
+
val scaled = if (scale != 1f) {
|
|
1098
|
+
val s = Bitmap.createScaledBitmap(cropped, outW, outH, true)
|
|
1099
|
+
cropped.recycle()
|
|
1100
|
+
s
|
|
1101
|
+
} else {
|
|
1102
|
+
cropped
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// Grayscale + contrast boost: the MRZ is black OCR-B on a light
|
|
1106
|
+
// background, so desaturating and stretching contrast makes glyphs
|
|
1107
|
+
// crisp and suppresses the dominant filler confusion (the "<" angle
|
|
1108
|
+
// bracket misread as C/E/K under glare). Done with a ColorMatrix on a
|
|
1109
|
+
// Canvas — no OpenCV dependency.
|
|
1110
|
+
val enhanced = enhanceForOcr(scaled)
|
|
1111
|
+
if (enhanced !== scaled) scaled.recycle()
|
|
1112
|
+
|
|
1113
|
+
// The bitmap is already rotated to portrait, so rotation = 0 here.
|
|
1114
|
+
ScanFrameRoi(
|
|
1115
|
+
image = InputImage.fromBitmap(enhanced, 0),
|
|
1116
|
+
offsetX = left,
|
|
1117
|
+
offsetY = top,
|
|
1118
|
+
scale = scale
|
|
1119
|
+
)
|
|
1120
|
+
} catch (e: Exception) {
|
|
1121
|
+
null
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Desaturate + boost contrast to make dark OCR-B glyphs pop against the light
|
|
1127
|
+
* MRZ background, which suppresses the "<" filler being misread as C/E/K under
|
|
1128
|
+
* glare. Uses a single ColorMatrix (saturation 0, then a contrast scale about
|
|
1129
|
+
* mid-grey). Returns the original bitmap unchanged on failure.
|
|
1130
|
+
*/
|
|
1131
|
+
private fun enhanceForOcr(src: Bitmap): Bitmap {
|
|
1132
|
+
return try {
|
|
1133
|
+
val out = Bitmap.createBitmap(src.width, src.height, Bitmap.Config.ARGB_8888)
|
|
1134
|
+
val canvas = android.graphics.Canvas(out)
|
|
1135
|
+
val paint = android.graphics.Paint()
|
|
1136
|
+
|
|
1137
|
+
val gray = android.graphics.ColorMatrix().apply { setSaturation(0f) }
|
|
1138
|
+
// Contrast about mid-grey (128): out = c*in + (1-c)*128.
|
|
1139
|
+
val c = 1.6f
|
|
1140
|
+
val t = (1f - c) * 128f
|
|
1141
|
+
val contrast = android.graphics.ColorMatrix(
|
|
1142
|
+
floatArrayOf(
|
|
1143
|
+
c, 0f, 0f, 0f, t,
|
|
1144
|
+
0f, c, 0f, 0f, t,
|
|
1145
|
+
0f, 0f, c, 0f, t,
|
|
1146
|
+
0f, 0f, 0f, 1f, 0f
|
|
1147
|
+
)
|
|
1148
|
+
)
|
|
1149
|
+
gray.postConcat(contrast)
|
|
1150
|
+
paint.colorFilter = android.graphics.ColorMatrixColorFilter(gray)
|
|
1151
|
+
canvas.drawBitmap(src, 0f, 0f, paint)
|
|
1152
|
+
out
|
|
1153
|
+
} catch (e: Exception) {
|
|
1154
|
+
src
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1020
1158
|
fun cleanup() {
|
|
1021
1159
|
// Cancel any active recording and delete file
|
|
1022
1160
|
if (activeRecording != null) {
|
|
@@ -864,28 +864,30 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
864
864
|
// Compute brightness early for OCR-B enhancement decision
|
|
865
865
|
let brightness = computeBrightness(from: pixelBuffer, width: portraitWidth, height: portraitHeight)
|
|
866
866
|
|
|
867
|
-
//
|
|
868
|
-
|
|
869
|
-
let textEnhancedImage = orientedImage
|
|
870
|
-
|
|
871
|
-
// Create VisionImage from the image (enhanced or original) for better text recognition
|
|
872
|
-
// This ensures MLKit processes the image in the correct orientation
|
|
873
|
-
guard let cgImage = self.ciContext.createCGImage(textEnhancedImage, from: textEnhancedImage.extent) else {
|
|
867
|
+
// Face detection / base64 use the full portrait frame.
|
|
868
|
+
guard let cgImage = self.ciContext.createCGImage(orientedImage, from: orientedImage.extent) else {
|
|
874
869
|
resetProcessingState()
|
|
875
870
|
return
|
|
876
871
|
}
|
|
877
872
|
let visionImage = VisionImage(image: UIImage(cgImage: cgImage))
|
|
878
873
|
visionImage.orientation = .up // Already oriented correctly
|
|
879
|
-
|
|
880
|
-
//
|
|
881
|
-
//
|
|
882
|
-
//
|
|
883
|
-
// -
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
874
|
+
|
|
875
|
+
// Text recognition: process ONLY the data inside the on-screen scan frame
|
|
876
|
+
// (matching Android). We crop the portrait frame to the scan-frame ROI
|
|
877
|
+
// (top 25% / bottom 12% / 4% side margins), UPSCALE it, and grayscale +
|
|
878
|
+
// contrast-enhance before OCR. This removes front-of-card text and glare
|
|
879
|
+
// noise and lifts a dense MRZ line above ML Kit's ~16px/char floor.
|
|
880
|
+
// `mrzRoi` carries the transform back to full portrait-frame coordinates.
|
|
881
|
+
let mrzRoi = enableTextRecognition
|
|
882
|
+
? buildScanFrameRoiImage(orientedImage, portraitWidth: portraitWidth, portraitHeight: portraitHeight)
|
|
883
|
+
: nil
|
|
884
|
+
var textVisionImage = visionImage
|
|
885
|
+
if let roi = mrzRoi,
|
|
886
|
+
let roiCg = self.ciContext.createCGImage(roi.image, from: roi.image.extent) {
|
|
887
|
+
let img = VisionImage(image: UIImage(cgImage: roiCg))
|
|
888
|
+
img.orientation = .up
|
|
889
|
+
textVisionImage = img
|
|
890
|
+
}
|
|
889
891
|
|
|
890
892
|
// Generate JPEG base64 only when JS side explicitly needs the image
|
|
891
893
|
// NOTE: Do NOT auto-generate for face detection - too expensive, causes frame drops
|
|
@@ -961,8 +963,16 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
961
963
|
let blockY: Int
|
|
962
964
|
let blockWidth: Int
|
|
963
965
|
let blockHeight: Int
|
|
964
|
-
|
|
965
|
-
if
|
|
966
|
+
|
|
967
|
+
if let roi = mrzRoi {
|
|
968
|
+
// OCR ran on the upscaled, already-portrait scan-frame ROI.
|
|
969
|
+
// Map the box back to full portrait-frame coordinates:
|
|
970
|
+
// divide out the ROI upscale, then add the ROI origin.
|
|
971
|
+
blockX = Int(bb.origin.x / roi.scale) + roi.offsetX
|
|
972
|
+
blockY = Int(bb.origin.y / roi.scale) + roi.offsetY
|
|
973
|
+
blockWidth = Int(bb.width / roi.scale)
|
|
974
|
+
blockHeight = Int(bb.height / roi.scale)
|
|
975
|
+
} else if isBufferLandscape {
|
|
966
976
|
// Rotate from landscape (1920x1080) to portrait (1080x1920)
|
|
967
977
|
// When rotating 90° clockwise (.right):
|
|
968
978
|
// new_x = old_y
|
|
@@ -1041,6 +1051,69 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
1041
1051
|
}
|
|
1042
1052
|
}
|
|
1043
1053
|
|
|
1054
|
+
/// A scan-frame ROI prepared for OCR, with the transform back to full-frame coords.
|
|
1055
|
+
private struct ScanFrameRoi {
|
|
1056
|
+
let image: CIImage
|
|
1057
|
+
let offsetX: Int
|
|
1058
|
+
let offsetY: Int
|
|
1059
|
+
let scale: CGFloat
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/// Crops the portrait frame to the on-screen scan frame (top 25% / bottom 12%
|
|
1063
|
+
/// / 4% side margins — the same rectangle the UI guide draws), UPSCALES it so a
|
|
1064
|
+
/// dense MRZ line clears ML Kit's ~16px/char floor, and grayscale + contrast
|
|
1065
|
+
/// enhances it (the MRZ is dark OCR-B on a light background, so this makes
|
|
1066
|
+
/// glyphs pop and suppresses the "<" filler being misread as a letter under
|
|
1067
|
+
/// glare). Returns nil on failure so the caller falls back to full-frame OCR.
|
|
1068
|
+
/// Mirrors the Android `buildScanFrameRoiImage`.
|
|
1069
|
+
private func buildScanFrameRoiImage(_ portrait: CIImage, portraitWidth: Int, portraitHeight: Int) -> ScanFrameRoi? {
|
|
1070
|
+
let w = portrait.extent.width
|
|
1071
|
+
let h = portrait.extent.height
|
|
1072
|
+
if w < 8 || h < 8 { return nil }
|
|
1073
|
+
|
|
1074
|
+
// Scan-frame rectangle. CIImage origin is BOTTOM-left, so "top 25%" maps to
|
|
1075
|
+
// a y measured from the bottom: cropY = h*0.12 (bottom margin), height =
|
|
1076
|
+
// h*(1 - 0.25 - 0.12).
|
|
1077
|
+
let leftPct: CGFloat = 0.04
|
|
1078
|
+
let rightPct: CGFloat = 0.04
|
|
1079
|
+
let topPct: CGFloat = 0.25
|
|
1080
|
+
let bottomPct: CGFloat = 0.12
|
|
1081
|
+
let cropX = (w * leftPct).rounded()
|
|
1082
|
+
let cropW = (w * (1 - leftPct - rightPct)).rounded()
|
|
1083
|
+
let cropY = (h * bottomPct).rounded()
|
|
1084
|
+
let cropH = (h * (1 - topPct - bottomPct)).rounded()
|
|
1085
|
+
if cropW < 8 || cropH < 8 { return nil }
|
|
1086
|
+
|
|
1087
|
+
let cropRect = CGRect(x: portrait.extent.origin.x + cropX,
|
|
1088
|
+
y: portrait.extent.origin.y + cropY,
|
|
1089
|
+
width: cropW, height: cropH)
|
|
1090
|
+
var img = portrait.cropped(to: cropRect)
|
|
1091
|
+
// Move the crop origin to (0,0) so downstream extents are simple.
|
|
1092
|
+
img = img.transformed(by: CGAffineTransform(translationX: -cropRect.origin.x, y: -cropRect.origin.y))
|
|
1093
|
+
|
|
1094
|
+
// Upscale (≤2.5×, long edge capped at 2200) for more pixels per character.
|
|
1095
|
+
let longEdge = max(cropW, cropH)
|
|
1096
|
+
let rawScale = longEdge > 0 ? (2200.0 / longEdge) : 1.0
|
|
1097
|
+
let scale = min(max(rawScale, 1.0), 2.5)
|
|
1098
|
+
if scale != 1.0 {
|
|
1099
|
+
img = img.transformed(by: CGAffineTransform(scaleX: scale, y: scale))
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Grayscale (saturation 0) + contrast boost.
|
|
1103
|
+
img = img.applyingFilter("CIColorControls", parameters: [
|
|
1104
|
+
kCIInputSaturationKey: 0.0,
|
|
1105
|
+
kCIInputContrastKey: 1.6
|
|
1106
|
+
])
|
|
1107
|
+
|
|
1108
|
+
// The crop is taken from the TOP of the portrait frame (UIKit coords), but
|
|
1109
|
+
// CIImage y is bottom-up. The block coords ML Kit returns are top-left in
|
|
1110
|
+
// the OCR image, so the offset back to the portrait frame is the UIKit top
|
|
1111
|
+
// edge (= portraitHeight * topPct) and left edge.
|
|
1112
|
+
let offsetX = Int(cropX)
|
|
1113
|
+
let offsetY = Int((CGFloat(portraitHeight) * topPct).rounded())
|
|
1114
|
+
return ScanFrameRoi(image: img, offsetX: offsetX, offsetY: offsetY, scale: scale)
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1044
1117
|
private func computeBrightness(from pixelBuffer: CVPixelBuffer, width: Int, height: Int) -> Double {
|
|
1045
1118
|
guard let yPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0) else { return 128.0 }
|
|
1046
1119
|
let pixelWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0)
|