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
JavaScript
// 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