DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

How to Add Real-Time Visual Overlays to React Native Barcode Scanner with Dynamsoft SDK

Building a barcode scanner app is one thing, but creating an intuitive user experience with real-time visual feedback is what sets great apps apart from the rest. In this comprehensive tutorial, you'll learn how to enhance the basic Dynamsoft React Native barcode scanner sample by adding dynamic visual overlays that highlight detected barcodes with detailed information and precise contours.

Demo Video: React Native Barcode Scanner with Overlays

Prerequisites

Before starting this tutorial, ensure you have:

What You'll Build

By the end of this tutorial, your barcode scanner will feature:

  • Real-time text overlays showing barcode content and format
  • Precise green contour lines outlining detected barcodes
  • Red corner dots marking the four corners of each barcode
  • Multi-barcode support with simultaneous detection and display

Step 1: Start with the Base Dynamsoft Sample

First, clone the official Dynamsoft React Native samples repository:

git clone https://github.com/Dynamsoft/barcode-reader-react-native-samples.git
cd barcode-reader-react-native-samples/ScanBarcodes_FoundationalAPI
npm install
Enter fullscreen mode Exit fullscreen mode

The base sample provides basic barcode detection with alert dialogs. We'll transform it into a real-time overlay system.

Step 2: Understand the Camera Coordinate System

Before implementing overlays, it’s important to understand how Dynamsoft handles camera coordinates.

Key Technical Points

  1. Fixed Camera Resolution: The dynamsoft-barcode-reader-bundle-react-native package uses a preset camera resolution - 1920x1080 by default. If your device doesn't support this resolution, the SDK will automatically select the closest available one.
  2. Portrait Mode Rotation: In portrait mode, barcode corner coordinates are already rotated 90 degrees. This results in:

    • Portrait mode: Width = 1080px, Height = 1920px
    • Landscape mode: Width = 1920px, Height = 1080px
  3. Coordinate Scaling: You only need to scale the coordinates to fit your screen dimensions. No additional rotation is required.

Step 3: Create Overlay Data Structure

Define a TypeScript interface for the barcode overlay:

interface BarcodeOverlay {
  id: string;
  text: string;
  formatString: string;
  x: number;
  y: number;
  corners: Array<{ x: number, y: number }>; 
}
Enter fullscreen mode Exit fullscreen mode

This structure holds:

  • id: A unique identifier for each barcode overlay
  • text: The decoded barcode text
  • formatString: The barcode format (e.g., QR Code, Code 128)
  • x and y: The center point of the barcode for text placement
  • corners: The four corner points of the barcode for contour drawing

Step 4: Implement the Barcode Contour Component

Create a reusable component to draw contours using the corner points:

const BarcodeContour: React.FC<{ corners: Array<{ x: number, y: number }> }> = ({ corners }) => {
  if (corners.length !== 4) return null;

  return (
    <>
      {corners.map((corner, index) => {
        const nextCorner = corners[(index + 1) % 4];
        const lineWidth = 3;

        const deltaX = nextCorner.x - corner.x;
        const deltaY = nextCorner.y - corner.y;
        const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
        const angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);

        return (
          <View
            key={index}
            style={[
              styles.contourLine,
              {
                left: corner.x,
                top: corner.y - lineWidth / 2,
                width: length,
                height: lineWidth,
                transform: [{ rotate: `${angle}deg` }],
              },
            ]}
          />
        );
      })}

      {corners.map((corner, index) => (
        <View
          key={`dot-${index}`}
          style={[
            styles.cornerDot,
            {
              left: corner.x - 4,
              top: corner.y - 4,
            },
          ]}
        />
      ))}
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

Step 5: Add State Management for Overlays

Update your main App component with state management:

function App(): React.JSX.Element {
  const cameraView = useRef<CameraView>(null!);
  const cvr = CaptureVisionRouter.getInstance();
  const camera = CameraEnhancer.getInstance();

  const [barcodeOverlays, setBarcodeOverlays] = useState<BarcodeOverlay[]>([]);
  const [cameraViewDimensions, setCameraViewDimensions] = useState<{ width: number, height: number } | null>(null);

  const clearTimeoutRef = useRef<NodeJS.Timeout | null>(null);

  const screenWidth = Dimensions.get('window').width;
  const screenHeight = Dimensions.get('window').height;

  // ... rest of component
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Implement Coordinate Transformation Logic

This is the most critical part - accurately mapping camera coordinates to screen coordinates:

const receiver = cvr.addResultReceiver({
  onDecodedBarcodesReceived: ({ items }) => {
    if (items?.length) {
      const overlays: BarcodeOverlay[] = items.map((item, index) => {
        let cameraResolutionWidth = 1080;
        let cameraResolutionHeight = 1920;

        if (screenWidth > screenHeight) {
          cameraResolutionWidth = 1920;
          cameraResolutionHeight = 1080;
        }

        const scaleFactorX = (cameraViewDimensions?.width || screenWidth) / cameraResolutionWidth;
        const scaleFactorY = (cameraViewDimensions?.height || screenHeight) / cameraResolutionHeight;

        const scaleFactor = Math.max(scaleFactorX, scaleFactorY);

        const scaledCameraWidth = cameraResolutionWidth * scaleFactor;
        const scaledCameraHeight = cameraResolutionHeight * scaleFactor;

        const viewWidth = cameraViewDimensions?.width || screenWidth;
        const viewHeight = cameraViewDimensions?.height || screenHeight;
        const cropOffsetX = (scaledCameraWidth - viewWidth) / 2;
        const cropOffsetY = (scaledCameraHeight - viewHeight) / 2;

        const scaledCorners = item.location.points.map(point => ({
          x: (point.x * scaleFactor) - cropOffsetX,
          y: (point.y * scaleFactor) - cropOffsetY
        }));

        const centerX = scaledCorners.reduce((sum, point) => sum + point.x, 0) / 4;
        const centerY = scaledCorners.reduce((sum, point) => sum + point.y, 0) / 4;

        const stableId = `${item.text}-${item.formatString}-${Math.round(centerX / 10) * 10}-${Math.round(centerY / 10) * 10}`;

        return {
          id: stableId, 
          text: item.text,
          formatString: item.formatString,
          x: centerX,
          y: centerY,
          corners: scaledCorners,
        };
      });

      setBarcodeOverlays(overlays);

      if (clearTimeoutRef.current) {
        clearTimeout(clearTimeoutRef.current);
      }

      clearTimeoutRef.current = setTimeout(() => {
        setBarcodeOverlays([]);
      }, 2000);
    }
  },
});
Enter fullscreen mode Exit fullscreen mode

What This Logic Does

  1. Orientation Detection: Adjusts resolution based on portrait/landscape mode.
  2. Scale Factor Calculation: Maintains aspect ratio with Math.max() to avoid distortion.
  3. Crop Offset Compensation: Adjusts for the portion of camera feed that's cropped due to aspect ratio differences
  4. Point Transformation: Scales and offsets each barcode corner for accurate overlay.

Step 7: Render the Overlays

Add the overlays to the render output:

return (
  <SafeAreaView style={StyleSheet.absoluteFill}>
    <CameraView
      style={StyleSheet.absoluteFill}
      ref={cameraView}
      onLayout={(event) => {
        const { width, height } = event.nativeEvent.layout;
        setCameraViewDimensions({ width, height });
      }}
    />

    {barcodeOverlays.map((overlay) => (
      <BarcodeContour key={`contour-${overlay.id}`} corners={overlay.corners} />
    ))}

    {barcodeOverlays.map((overlay) => (
      <View
        key={overlay.id}
        style={[
          styles.barcodeOverlay,
          {
            left: overlay.x - 75, 
            top: overlay.y - 40, 
          },
        ]}
      >
        <Text style={styles.barcodeText}>{overlay.text}</Text>
        <Text style={styles.formatText}>{overlay.formatString}</Text>
      </View>
    ))}
  </SafeAreaView>
);
Enter fullscreen mode Exit fullscreen mode

Step 8: Style Your Overlays

Add the necessary styles for your overlays:

const styles = StyleSheet.create({
  barcodeOverlay: {
    position: 'absolute',
    backgroundColor: 'rgba(0, 0, 0, 0.7)',
    borderRadius: 8,
    padding: 8,
    alignItems: 'center',
    minWidth: 150,
    maxWidth: 250,
  },
  barcodeText: {
    color: 'white',
    fontSize: 14,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 2,
  },
  formatText: {
    color: '#00FF00',
    fontSize: 10,
    textAlign: 'center',
  },
  contourLine: {
    position: 'absolute',
    backgroundColor: '#00FF00',
    transformOrigin: 'left center',
  },
  cornerDot: {
    position: 'absolute',
    width: 8,
    height: 8,
    borderRadius: 4,
    backgroundColor: '#FF0000',
  },
});
Enter fullscreen mode Exit fullscreen mode

Step 9: Handle Cleanup

Don't forget to clean up resources in your useEffect cleanup:

return () => {
  sub.remove();
  stopScanning();
  cvr.removeResultReceiver(receiver);
  if (clearTimeoutRef.current) {
    clearTimeout(clearTimeoutRef.current);
  }
};
Enter fullscreen mode Exit fullscreen mode

Step 10: Run Your App

Finally, run your app on a physical device:

npx react-native run-android
# or
npx react-native run-ios
Enter fullscreen mode Exit fullscreen mode

react-native barcode scanner

Source Code

https://github.com/yushulx/android-camera-barcode-mrz-document-scanner/tree/main/examples/react-native-barcode-scanner

You Know That Your Mobile App Needs Security. Here\

You Know That Your Mobile App Needs Security. Here's How to Get Started

Mobile apps have become a cornerstone of modern life. With billions of users, they have become a prime target for attackers. By implementing strong mobile app security, organizations can prevent IP theft, revenue loss, and an erosion of user trust.

Read the guide

Top comments (0)