How to Develop a DotCode Reader in Java Using a Webcam and OpenCV

A DotCode is a 2D barcode symbology composed of disconnected dots, commonly used in the tobacco industry. Recently, Dynamsoft introduced a barcode reader SDK with DotCode support. In this post, I will demonstrate how to build a Java DotCode reader, featuring both a command-line application and a GUI application.

Decoding DotCode in a Java Command-Line App

The command-line app is straightforward. It decodes DotCode from an image file using a specified API.

Configure Dynamsoft Barcode Reader in pom.xml:

<repositories>
  <repository>
    <id>dbr</id>
    <url>https://download2.dynamsoft.com/maven/dbr/jar</url>
  </repository>
</repositories>
<dependencies>
  <dependency>
    <groupId>com.dynamsoft</groupId>
    <artifactId>dbr</artifactId>
    <version>9.6.40.1</version>
  </dependency>
</dependencies>

To simplify the running command, add a plugin to assemble all dependencies into one JAR file:

<build>
  <plugins>
    <plugin>
      <artifactId>maven-assembly-plugin</artifactId>
      <configuration>
        <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
          <manifest>
            <mainClass>com.java.barcode.App</mainClass>
          </manifest>
        </archive>
      </configuration>
      <executions>
        <execution>
          <id>make-assembly</id>
          <phase>package</phase>
          <goals>
            <goal>single</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Instantiate Dynamsoft Barcode Reader and set DotCode as the target barcode:

BarcodeReader br = null;
try {
    BarcodeReader.initLicense("LICENSE-KEY");
    br = new BarcodeReader();
            br.initRuntimeSettingsWithString("{\"ImageParameter\":{\"Name\":\"BestCoverage\",\"DeblurLevel\":9,\"ExpectedBarcodesCount\":512,\"ScaleDownThreshold\":100000,\"LocalizationModes\":[{\"Mode\":\"LM_CONNECTED_BLOCKS\"},{\"Mode\":\"LM_SCAN_DIRECTLY\"},{\"Mode\":\"LM_STATISTICS\"},{\"Mode\":\"LM_LINES\"},{\"Mode\":\"LM_STATISTICS_MARKS\"}],\"GrayscaleTransformationModes\":[{\"Mode\":\"GTM_ORIGINAL\"},{\"Mode\":\"GTM_INVERTED\"}]}}", EnumConflictMode.CM_OVERWRITE);
    PublicRuntimeSettings runtimeSettings = br.getRuntimeSettings();
    runtimeSettings.barcodeFormatIds_2 = EnumBarcodeFormat_2.BF2_DOTCODE;
    br.updateRuntimeSettings(runtimeSettings);
} catch (Exception e) {
    System.out.println(e);
    return;
}

By default, the SDK decodes all supported barcode formats. To focus on DotCode and speed up decoding, disable other barcode formats:

runtimeSettings.barcodeFormatIds = EnumBarcodeFormat.BF_NULL;

Pass the file path to the decodeFile() function to return the barcode results:

        TextResult[] results = null;
        try {
            results = br.decodeFile(filename, "");
        } catch (Exception e) {
            System.out.println("decode buffered image: " + e);
        }

Build and run the project:

mvn clean package
java -jar target/test-1.0-SNAPSHOT-jar-with-dependencies.jar <image-file>

A Webcam DotCode Reader Built with OpenCV Java and Java Swing

Creating a GUI app requires more effort.

How to Install OpenCV Java

The OpenCV Java SDK contains a JAR package and a shared library. For Windows users, install the pre-built package and locate the OpenCV Java SDK in opencv-4.3\opencv\build\java. For a Maven project, install the JAR file to the Maven local repository:

mvn install:install-file -Dfile=opencv-430.jar -DgroupId=org -DartifactId=opencv -Dversion=4.3.0 -Dpackaging=jar

Add the configuration to pom.xml:

    <dependency>
      <groupId>org</groupId>
      <artifactId>opencv</artifactId>
      <version>4.3.0</version>
    </dependency>

For the DLL file, if it isn’t placed correctly, you’ll encounter an error when loading the library:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no opencv_java430 in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
        at java.lang.Runtime.loadLibrary0(Runtime.java:870)
        at java.lang.System.loadLibrary(System.java:1122)
        at com.java.barcode.App.main(App.java:65)

Workarounds for the Issue

Check the available Java library path, copy the DLL file to the current working directory, or add the DLL path to the system environment PATH:

System.out.println(System.getProperty("java.library.path"));

Use the full path to load the library:

System.load("D:/opencv-4.3/opencv/build/java/x64/opencv_java430.dll");

Define the Java library path when running your Java app:

java -Djava.library.path=<dll path> -cp target/opencv-dotcode-1.0-SNAPSHOT-jar-with-dependencies.jar com.java.barcode.App

A “Hello World” Program Using OpenCV Java

Once OpenCV is ready, test the library as follows:

System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
Mat mat = Mat.eye(3, 3, CvType.CV_8UC1);
System.out.println("mat = " + mat.dump());

Display Webcam Video Stream in a Java Swing Component

Inspired by the OpenCV Java docs for JavaFX programming, we can render the webcam video stream in a Java Swing component as follows:

public void updateViewer(final BufferedImage image) {
        if (!SwingUtilities.isEventDispatchThread()) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    mImage.setIcon(new ImageIcon(image));
                }
            });
            return;
        }
    }

Runnable frameGrabber = new Runnable() {

                    @Override
                    public void run() {
                        Mat frame = grabFrame();
                        byte[] data = Utils.matToByteArray(frame);

                        if (!status.get()) {
                            status.set(true);
                            barcodeTimer.schedule(new BarcodeRunnable(frame, mBarcodeReader, callback, status), 0, TimeUnit.MILLISECONDS);
                        }
                    
                        BufferedImage bufferedImage = Utils.byteToBufferedImage(data, frame.width(), frame.height(), frame.channels());
                        if (isRunning) updateViewer(bufferedImage);
                    }
                };
this.timer = Executors.newSingleThreadScheduledExecutor();
this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);

Reading DotCode and Displaying Results

In the code above, a barcode timer instance is created for reading DotCode:

barcodeTimer = Executors.newSingleThreadScheduledExecutor();

Run the barcode decoding API in a worker thread to maintain the frame rate.

To draw the DotCode position, create a class CustomJLabel that extends JLabel:

private ArrayList<Point[]> data = new ArrayList<>();

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    if (data.size() > 0) {
        g2d.setColor(Color.RED);
        for (Point[] points : data) {
            for (int i = 0; i < points.length; ++i) {
                if (i == 3) {
                    g2d.drawLine(points[i].x, points[i].y, points[0].x, points[0].y);
                } else {
                    g2d.drawLine(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
                }
            }
        }

    }
    g2d.dispose();
}

public void appendPoints(Point[] points) {
    data.add(points);
}

public void clearPoints() {
    data.clear();
}

Build and run the GUI DotCode reader:

mvn clean package
java -jar target/test-1.0-SNAPSHOT-jar-with-dependencies.jar

Java DotCode reader

Q&A

How to convert OpenCV Mat to Java byte array?

public static byte[] matToByteArray(Mat original)
{
    int width = original.width(), height = original.height(), channels = original.channels();
    byte[] sourcePixels = new byte[width * height * channels];
    original.get(0, 0, sourcePixels);
    return sourcePixels;
}

How to convert Java byte array to Java BufferedImage?

public static BufferedImage byteToBufferedImage(byte[] sourcePixels, int width, int height, int channels)
{
    BufferedImage image = null;
    
    if (channels > 1)
    {
        image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
    }
    else
    {
        image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
    }
    final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
    System.arraycopy(sourcePixels, 0, targetPixels, 0, sourcePixels.length);
    
    return image;
}

Source Code

https://github.com/yushulx/java-jni-barcode-qrcode-reader/tree/main/examples/9.x/dotcode