UNPKG

vision-camera-simple-scanner

Version:

High performance barcode scanner for React Native using VisionCamera, forked from vision-camera-barcode-scanner

531 lines (519 loc) 18.2 kB
// src/constants/android.ts var AndroidBarcodeFormat = /* @__PURE__ */ ((AndroidBarcodeFormat2) => { AndroidBarcodeFormat2[AndroidBarcodeFormat2["UNKNOWN"] = -1] = "UNKNOWN"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["ALL_FORMATS"] = 0] = "ALL_FORMATS"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["CODE_128"] = 1] = "CODE_128"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["CODE_39"] = 2] = "CODE_39"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["CODE_93"] = 4] = "CODE_93"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["CODABAR"] = 8] = "CODABAR"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["DATA_MATRIX"] = 16] = "DATA_MATRIX"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["EAN_13"] = 32] = "EAN_13"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["EAN_8"] = 64] = "EAN_8"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["ITF"] = 128] = "ITF"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["QR_CODE"] = 256] = "QR_CODE"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["UPC_A"] = 512] = "UPC_A"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["UPC_E"] = 1024] = "UPC_E"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["PDF417"] = 2048] = "PDF417"; AndroidBarcodeFormat2[AndroidBarcodeFormat2["AZTEC"] = 4096] = "AZTEC"; return AndroidBarcodeFormat2; })(AndroidBarcodeFormat || {}); var AndroidBarcodeValueType = /* @__PURE__ */ ((AndroidBarcodeValueType2) => { AndroidBarcodeValueType2[AndroidBarcodeValueType2["UNKNOWN"] = 0] = "UNKNOWN"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["CONTACT_INFO"] = 1] = "CONTACT_INFO"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["EMAIL"] = 2] = "EMAIL"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["ISBN"] = 3] = "ISBN"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["PHONE"] = 4] = "PHONE"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["PRODUCT"] = 5] = "PRODUCT"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["SMS"] = 6] = "SMS"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["TEXT"] = 7] = "TEXT"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["URL"] = 8] = "URL"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["WIFI"] = 9] = "WIFI"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["GEO"] = 10] = "GEO"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["CALENDAR_EVENT"] = 11] = "CALENDAR_EVENT"; AndroidBarcodeValueType2[AndroidBarcodeValueType2["DRIVER_LICENSE"] = 12] = "DRIVER_LICENSE"; return AndroidBarcodeValueType2; })(AndroidBarcodeValueType || {}); var AndroidAddressType = /* @__PURE__ */ ((AndroidAddressType2) => { AndroidAddressType2[AndroidAddressType2["UNKNOWN"] = 0] = "UNKNOWN"; AndroidAddressType2[AndroidAddressType2["WORK"] = 1] = "WORK"; AndroidAddressType2[AndroidAddressType2["HOME"] = 2] = "HOME"; return AndroidAddressType2; })(AndroidAddressType || {}); var AndroidEncryptionType = /* @__PURE__ */ ((AndroidEncryptionType2) => { AndroidEncryptionType2[AndroidEncryptionType2["OPEN"] = 1] = "OPEN"; AndroidEncryptionType2[AndroidEncryptionType2["WPA"] = 2] = "WPA"; AndroidEncryptionType2[AndroidEncryptionType2["WEP"] = 3] = "WEP"; return AndroidEncryptionType2; })(AndroidEncryptionType || {}); var AndroidEmailType = /* @__PURE__ */ ((AndroidEmailType2) => { AndroidEmailType2[AndroidEmailType2["UNKNOWN"] = 0] = "UNKNOWN"; AndroidEmailType2[AndroidEmailType2["WORK"] = 1] = "WORK"; AndroidEmailType2[AndroidEmailType2["HOME"] = 2] = "HOME"; return AndroidEmailType2; })(AndroidEmailType || {}); var AndroidPhoneType = /* @__PURE__ */ ((AndroidPhoneType2) => { AndroidPhoneType2[AndroidPhoneType2["UNKNOWN"] = 0] = "UNKNOWN"; AndroidPhoneType2[AndroidPhoneType2["WORK"] = 1] = "WORK"; AndroidPhoneType2[AndroidPhoneType2["HOME"] = 2] = "HOME"; AndroidPhoneType2[AndroidPhoneType2["FAX"] = 3] = "FAX"; AndroidPhoneType2[AndroidPhoneType2["MOBILE"] = 4] = "MOBILE"; return AndroidPhoneType2; })(AndroidPhoneType || {}); // src/constants/templates.ts var Templates = { /** * HD-quality for faster Frame Processing in YUV pixelFormat (e.g. 720p) */ FrameProcessingYUV: [{ videoResolution: { width: 1080, height: 720 } }], /** * XGA-quality for faster Frame Processing in YUV pixelFormat */ FrameProcessingBarcodeXGA: [ { videoResolution: { width: 1024, height: 768 } } ] }; // src/hooks/useBarcodeScanner.ts import { useState } from "react"; import { runAtTargetFps, useFrameProcessor } from "react-native-vision-camera"; import { Worklets, useSharedValue as useSharedValue2 } from "react-native-worklets-core"; // src/module.ts import { NativeEventEmitter, NativeModules, Platform as Platform4 } from "react-native"; import { VisionCameraProxy } from "react-native-vision-camera"; // src/utils/convert.ts import { Platform } from "react-native"; // src/utils/types.ts var normalizeiOSCodeType = (symbology) => { "worklet"; switch (symbology) { case "VNBarcodeSymbologyAztec": return "aztec"; case "VNBarcodeSymbologyCode39": case "VNBarcodeSymbologyCode39Checksum": case "VNBarcodeSymbologyCode39FullASCII": case "VNBarcodeSymbologyCode39FullASCIIChecksum": return "code-39"; case "VNBarcodeSymbologyCode93": case "VNBarcodeSymbologyCode93i": return "code-93"; case "VNBarcodeSymbologyCode128": return "code-128"; case "VNBarcodeSymbologyDataMatrix": return "data-matrix"; case "VNBarcodeSymbologyEAN8": return "ean-8"; case "VNBarcodeSymbologyEAN13": return "ean-13"; case "VNBarcodeSymbologyGS1DataBar": case "VNBarcodeSymbologyGS1DataBarExpanded": case "VNBarcodeSymbologyGS1DataBarLimited": return "gs1-databar"; case "VNBarcodeSymbologyI2of5": case "VNBarcodeSymbologyI2of5Checksum": case "VNBarcodeSymbologyITF14": return "itf"; case "VNBarcodeSymbologyMicroPDF417": case "VNBarcodeSymbologyPDF417": return "pdf-417"; case "VNBarcodeSymbologyMicroQR": case "VNBarcodeSymbologyQR": return "qr"; case "VNBarcodeSymbologyMSIPlessey": return "msi-plessey"; case "VNBarcodeSymbologyUPCE": return "upc-e"; default: return "unknown"; } }; var normalizeAndroidCodeType = (format) => { "worklet"; switch (format) { case 4096 /* AZTEC */: return "aztec"; case 8 /* CODABAR */: return "codabar"; case 2 /* CODE_39 */: return "code-39"; case 4 /* CODE_93 */: return "code-93"; case 1 /* CODE_128 */: return "code-128"; case 16 /* DATA_MATRIX */: return "data-matrix"; case 64 /* EAN_8 */: return "ean-8"; case 32 /* EAN_13 */: return "ean-13"; case 128 /* ITF */: return "itf"; case 2048 /* PDF417 */: return "pdf-417"; case 256 /* QR_CODE */: return "qr"; case 512 /* UPC_A */: return "upc-a"; case 1024 /* UPC_E */: return "upc-e"; case -1 /* UNKNOWN */: default: return "unknown"; } }; // src/utils/convert.ts var isIOSBarcode = (barcode) => { "worklet"; return Platform.OS === "ios"; }; var isAndroidBarcode = (barcode) => { "worklet"; return Platform.OS === "android"; }; var computeBoundingBoxFromCornerPoints = (cornerPoints) => { "worklet"; const xArray = cornerPoints.map((corner) => corner.x); const yArray = cornerPoints.map((corner) => corner.y); const x = Math.min.apply(null, xArray); const y = Math.min.apply(null, yArray); const width = Math.max.apply(null, xArray) - x; const height = Math.max.apply(null, yArray) - y; return { origin: { x, y }, size: { width, height } }; }; var normalizePrecision = (number) => { "worklet"; return Math.round(number); }; var normalizeNativeBarcode = (barcode, frame) => { "worklet"; if (isIOSBarcode(barcode)) { const { payload, symbology, boundingBox, corners } = barcode; return { value: payload, type: normalizeiOSCodeType(symbology), boundingBox: { origin: { x: normalizePrecision(boundingBox.origin.x * frame.width), y: normalizePrecision(boundingBox.origin.y * frame.height) }, size: { width: normalizePrecision(boundingBox.size.width * frame.width), height: normalizePrecision(boundingBox.size.height * frame.height) } }, // This explicitly defined to match android's implementation which is to start top left and go clockwise. // https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/common/Barcode#public-point[]-getcornerpoints cornerPoints: [ corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft ].map(({ x, y }) => ({ x: normalizePrecision(x * frame.width), y: normalizePrecision(y * frame.height) })), native: barcode }; } else if (isAndroidBarcode(barcode)) { const { rawValue, format, cornerPoints } = barcode; return { value: rawValue, type: normalizeAndroidCodeType(format), boundingBox: computeBoundingBoxFromCornerPoints(cornerPoints), cornerPoints, native: barcode }; } else { throw new Error(`Unsupported platform: ${Platform.OS}`); } }; var convertVisionCameraCodeToBarcode = (code) => { return { value: code.value ?? null, type: code.type, boundingBox: { origin: { x: code.frame?.x ?? 0, y: code.frame?.y ?? 0 }, size: { width: code.frame?.width ?? 0, height: code.frame?.height ?? 0 } }, cornerPoints: code.corners ?? [] }; }; // src/utils/geometry.ts import { Platform as Platform2 } from "react-native"; var applyScaleFactor = ({ x, y }, source, target, resizeMode = "cover") => { "worklet"; const ratio = { width: target.width / source.width, height: target.height / source.height }; let scaleFactor; if (resizeMode === "contain") { scaleFactor = Math.min(ratio.width, ratio.height); } else if (resizeMode === "cover") { scaleFactor = Math.max(ratio.width, ratio.height); } else { throw new Error(`Invalid resize mode: ${resizeMode}`); } let newX = x * scaleFactor; let newY = y * scaleFactor; if (ratio.width < ratio.height && resizeMode === "contain" || ratio.width > ratio.height && resizeMode === "cover") { newY += (target.height - source.height * scaleFactor) / 2; } else { newX += (target.width - source.width * scaleFactor) / 2; } return { x: normalizePrecision(newX), y: normalizePrecision(newY) }; }; var applyTransformation = ({ x, y }, { width, height }, orientation) => { "worklet"; if (Platform2.OS === "android") { switch (orientation) { case "portrait": return { x, y }; default: console.warn(`Unsupported orientation: ${orientation}`); return { x, y }; } } else if (Platform2.OS === "ios") { switch (orientation) { case "portrait": return { x: height - y, y: width - x }; case "landscape-left": return { x: width - x, y }; case "landscape-right": return { x, y: height - y }; case "portrait-upside-down": return { x: y, y: x }; default: console.warn(`Unsupported orientation: ${orientation}`); return { x, y }; } } else { throw new Error(`Unsupported platform: ${Platform2.OS}`); } }; // src/utils/highlights.ts import { Platform as Platform3 } from "react-native"; var computeHighlights = (barcodes, frame, layout, resizeMode = "cover", pointMapper) => { "worklet"; if (layout.width === 0 || layout.height === 0) { return []; } let adjustedLayout = { width: layout.height, height: layout.width }; if (Platform3.OS === "ios") { adjustedLayout = ["portrait", "portrait-upside-down"].includes( frame.orientation ) ? { width: layout.height, height: layout.width } : { width: layout.width, height: layout.height }; } const highlights = barcodes.map((barcode, index) => { const { value, cornerPoints } = barcode; let translatedCornerPoints = cornerPoints; translatedCornerPoints = translatedCornerPoints?.map( (point) => applyScaleFactor(point, frame, adjustedLayout, resizeMode) ); if (pointMapper) { translatedCornerPoints = translatedCornerPoints?.map( (point) => pointMapper(point, layout, frame.orientation) ); } else { translatedCornerPoints = translatedCornerPoints?.map( (point) => applyTransformation(point, adjustedLayout, frame.orientation) ); } const valueFromCornerPoints = computeBoundingBoxFromCornerPoints( translatedCornerPoints ); return { ...barcode, key: `${value}.${index}`, cornerPoints: translatedCornerPoints, boundingBox: valueFromCornerPoints }; }); return highlights; }; // src/module.ts var LINKING_ERROR = `The package 'vision-camera-code-scanner' doesn't seem to be linked. Make sure: ` + Platform4.select({ ios: "- You have run 'pod install'\n", default: "" }) + "- You rebuilt the app after installing the package\n- You are not using Expo Go\n"; var VisionCameraSimpleScanner = NativeModules.VisionCameraSimpleScanner ? NativeModules.VisionCameraSimpleScanner : new Proxy( {}, { get() { throw new Error(LINKING_ERROR); } } ); var { MODULE_NAME, BARCODE_TYPES, BARCODE_FORMATS } = VisionCameraSimpleScanner.getConstants(); var visionCameraEventEmitter = new NativeEventEmitter( VisionCameraSimpleScanner ); var onBarcodeDetected = (callback) => { visionCameraEventEmitter.addListener("onBarcodeDetected", (nativeBarcode) => { callback(nativeBarcode); }); }; var visionCameraProcessorPlugin = VisionCameraProxy.initFrameProcessorPlugin( MODULE_NAME ); var scanCodes = (frame, options) => { "worklet"; if (visionCameraProcessorPlugin == null) { throw new Error(`Failed to load Frame Processor Plugin "${MODULE_NAME}"!`); } frame.incrementRefCount && frame.incrementRefCount(); const nativeCodes = visionCameraProcessorPlugin.call( frame, options ); frame.decrementRefCount && frame.decrementRefCount(); if (!Array.isArray(nativeCodes)) { console.log( `Native frame processor ${MODULE_NAME} failed to return a proper array!`, nativeCodes ); return []; } return nativeCodes.slice().map((nativeBarcode) => normalizeNativeBarcode(nativeBarcode, frame)); }; // src/hooks/useLatestSharedValue.ts import { useEffect } from "react"; import { useSharedValue } from "react-native-worklets-core"; function useLatestSharedValue(value, dependencies = [value]) { const sharedValue = useSharedValue(value); useEffect(() => { sharedValue.value = value; }, dependencies); return sharedValue; } // src/hooks/useBarcodeScanner.ts var useBarcodeScanner = ({ barcodeTypes, regionOfInterest, onBarcodeScanned, pointMapper, disableHighlighting, resizeMode = "cover", scanMode = "continuous", isMountedRef, fps = 30 }) => { const layoutRef = useSharedValue2({ width: 0, height: 0 }); const onLayout = (event) => { const { width, height } = event.nativeEvent.layout; layoutRef.value = { width, height }; }; const resizeModeRef = useLatestSharedValue(resizeMode); const barcodesRef = useSharedValue2([]); const [highlights, setHighlights] = useState([]); const lastHighlightsCount = useSharedValue2(0); const setHighlightsJS = Worklets.createRunOnJS(setHighlights); const pixelFormat = "yuv"; const frameProcessor = useFrameProcessor( (frame) => { "worklet"; if (isMountedRef && isMountedRef.value === false) { return; } runAtTargetFps(fps, () => { "worklet"; const { value: layout } = layoutRef; const { value: prevBarcodes } = barcodesRef; const { value: resizeMode2 } = resizeModeRef; const { width, height, orientation } = frame; const options = {}; if (barcodeTypes !== void 0) { options.barcodeTypes = barcodeTypes; } if (regionOfInterest !== void 0) { const { x, y, width: width2, height: height2 } = regionOfInterest; options.regionOfInterest = [x, y, width2, height2]; } const barcodes = scanCodes(frame, options); if (barcodes.length > 0) { if (scanMode === "continuous") { onBarcodeScanned && onBarcodeScanned(barcodes, frame); } else if (scanMode === "once") { const hasChanged = prevBarcodes.length !== barcodes.length || JSON.stringify(prevBarcodes.map(({ value }) => value)) !== JSON.stringify(barcodes.map(({ value }) => value)); if (hasChanged) { onBarcodeScanned && onBarcodeScanned(barcodes, frame); } } barcodesRef.value = barcodes; } if (disableHighlighting !== true && resizeMode2 !== void 0) { const highlights2 = computeHighlights( barcodes, { width, height, orientation }, // "serialized" frame layout, resizeMode2, pointMapper ); if (lastHighlightsCount.value === 0 && highlights2.length === 0) { return; } lastHighlightsCount.value = highlights2.length; setHighlightsJS(highlights2); } }); }, [layoutRef, resizeModeRef, disableHighlighting] ); return { props: { pixelFormat, frameProcessor, onLayout }, highlights }; }; export { AndroidAddressType, AndroidBarcodeFormat, AndroidBarcodeValueType, AndroidEmailType, AndroidEncryptionType, AndroidPhoneType, BARCODE_FORMATS, BARCODE_TYPES, Templates, VisionCameraSimpleScanner, applyScaleFactor, applyTransformation, computeBoundingBoxFromCornerPoints, computeHighlights, convertVisionCameraCodeToBarcode, isAndroidBarcode, isIOSBarcode, normalizeAndroidCodeType, normalizeNativeBarcode, normalizePrecision, normalizeiOSCodeType, onBarcodeDetected, scanCodes, useBarcodeScanner, useLatestSharedValue }; //# sourceMappingURL=index.js.map