How to Build Java Edge Detection Application to Scan and Normalize Documents

Xiao Ling
6 min readNov 22, 2022

--

Dynamsoft Document Normalizer SDK helps developers to quickly build document scanning applications. It provides a set of APIs to detect document edges and normalize document images. Currently, the SDK only supports C/C++, Android, iOS, Xamarin.Forms and JavaScript. Although there is no Java edition available for download yet, we can make it by ourselves. This article aims to encapsulate the Dynamsoft Document Normalizer C++ libraries into a Java JAR package. The JAR package can be used in Java applications on Windows and Linux.

Prerequisites

How to Build Java JNI Project with CMake

First, we start a new Java project and create a NativeDocumentScanner.java file that defines some native methods. The native methods are used to load the native libraries and bridge the C++ APIs.

package com.dynamsoft.ddn;

import java.util.ArrayList;

public class NativeDocumentScanner {

private long nativePtr = 0;

static {
try {
if (NativeLoader.load()) {
System.out.println("Successfully loaded Dynamsoft Document Normalizer.");
}
} catch (Exception e) {
e.printStackTrace();
}
}

public NativeDocumentScanner() {
nativePtr = nativeCreateInstance();
}

public void destroyInstance() {
if (nativePtr != 0)
nativeDestroyInstance(nativePtr);
}

public static int setLicense(String license) {
return nativeInitLicense(license);
}

public ArrayList<DocumentResult> detectFile(String fileName) {
return nativeDetectFile(nativePtr, fileName);
}

public String getVersion() {
return nativeGetVersion();
}

public NormalizedImage normalizeFile(String fileName, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
return nativeNormalizeFile(nativePtr, fileName, x1, y1, x2, y2, x3, y3, x4, y4);
}

public int setParameters(String parameters) {
return nativeSetParameters(nativePtr, parameters);
}

public int saveImage(NormalizedImage image, String fileName) {
return nativeSaveImage(image, fileName);
}

private native static int nativeInitLicense(String license);

private native long nativeCreateInstance();

private native void nativeDestroyInstance(long nativePtr);

private native ArrayList<DocumentResult> nativeDetectFile(long nativePtr, String fileName);

private native String nativeGetVersion();

private native NormalizedImage nativeNormalizeFile(long nativePtr, String fileName, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4);

private native int nativeSetParameters(long nativePtr, String parameters);

private native int nativeSaveImage(NormalizedImage image, String filename);
}

Then, we use the javah tool to generate the header file NativeDocumentScanner.h for the Java class NativeDocumentScanner.

cd src/main/java
javah -o ../../../jni/NativeDocumentScanner.h com.dynamsoft.ddn.NativeDocumentScanner

The tool eliminates the need to write the header file manually.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_dynamsoft_ddn_NativeDocumentScanner */

#ifndef _Included_com_dynamsoft_ddn_NativeDocumentScanner
#define _Included_com_dynamsoft_ddn_NativeDocumentScanner
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_dynamsoft_ddn_NativeDocumentScanner
* Method: nativeInitLicense
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_dynamsoft_ddn_NativeDocumentScanner_nativeInitLicense
(JNIEnv *, jclass, jstring);

/*
* Class: com_dynamsoft_ddn_NativeDocumentScanner
* Method: nativeCreateInstance
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_com_dynamsoft_ddn_NativeDocumentScanner_nativeCreateInstance
(JNIEnv *, jobject);

/*
* Class: com_dynamsoft_ddn_NativeDocumentScanner
* Method: nativeDestroyInstance
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_com_dynamsoft_ddn_NativeDocumentScanner_nativeDestroyInstance
(JNIEnv *, jobject, jlong);

/*
* Class: com_dynamsoft_ddn_NativeDocumentScanner
* Method: nativeDetectFile
* Signature: (JLjava/lang/String;)Ljava/util/ArrayList;
*/
JNIEXPORT jobject JNICALL Java_com_dynamsoft_ddn_NativeDocumentScanner_nativeDetectFile
(JNIEnv *, jobject, jlong, jstring);

/*
* Class: com_dynamsoft_ddn_NativeDocumentScanner
* Method: nativeGetVersion
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_dynamsoft_ddn_NativeDocumentScanner_nativeGetVersion
(JNIEnv *, jobject);

/*
* Class: com_dynamsoft_ddn_NativeDocumentScanner
* Method: nativeNormalizeFile
* Signature: (JLjava/lang/String;IIIIIIII)Lcom/dynamsoft/ddn/NormalizedImage;
*/
JNIEXPORT jobject JNICALL Java_com_dynamsoft_ddn_NativeDocumentScanner_nativeNormalizeFile
(JNIEnv *, jobject, jlong, jstring, jint, jint, jint, jint, jint, jint, jint, jint);

/*
* Class: com_dynamsoft_ddn_NativeDocumentScanner
* Method: nativeSetParameters
* Signature: (JLjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_dynamsoft_ddn_NativeDocumentScanner_nativeSetParameters
(JNIEnv *, jobject, jlong, jstring);

/*
* Class: com_dynamsoft_ddn_NativeDocumentScanner
* Method: nativeSaveImage
* Signature: (Lcom/dynamsoft/ddn/NormalizedImage;Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_dynamsoft_ddn_NativeDocumentScanner_nativeSaveImage
(JNIEnv *, jobject, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

Create a NativeDocumentScanner.cxx file to implement the native methods. We will talk about the implementation in the later section.

Now, open the CMakeLists.txt file and add the following build configurations:

cmake_minimum_required (VERSION 2.6)
project (ddn)
MESSAGE( STATUS "PROJECT_NAME: " ${PROJECT_NAME} )

find_package(JNI REQUIRED)
include_directories(${JNI_INCLUDE_DIRS})

MESSAGE( STATUS "JAVA_INCLUDE: " ${JAVA_INCLUDE})

# Check lib
if (CMAKE_HOST_WIN32)
set(WINDOWS 1)
elseif(CMAKE_HOST_APPLE)
set(MACOS 1)
elseif(CMAKE_HOST_UNIX)
set(LINUX 1)
endif()

# Set RPATH
if(CMAKE_HOST_UNIX)
SET(CMAKE_CXX_FLAGS "-std=c++11 -O3 -Wl,-rpath=$ORIGIN")
SET(CMAKE_INSTALL_RPATH "$ORIGIN")
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
endif()

# Add search path for include and lib files
if(WINDOWS)
link_directories("${PROJECT_SOURCE_DIR}/lib/win/" ${JNI_LIBRARIES})
elseif(LINUX)
link_directories("${PROJECT_SOURCE_DIR}/lib/linux/" ${JNI_LIBRARIES})
endif()
include_directories("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include/")


# Add the library
add_library(ddn SHARED NativeDocumentScanner.cxx)
if(WINDOWS)
target_link_libraries (${PROJECT_NAME} "DynamsoftCorex64" "DynamsoftDocumentNormalizerx64")
else()
target_link_libraries (${PROJECT_NAME} "DynamsoftCore" "DynamsoftDocumentNormalizer" pthread)
endif()

# Set installation directory
set(CMAKE_INSTALL_PREFIX "${PROJECT_SOURCE_DIR}/../src/main/")
set(LIBRARY_PATH "java/com/dynamsoft/ddn/native")
if(WINDOWS)
install (DIRECTORY "${PROJECT_SOURCE_DIR}/lib/win/" DESTINATION "${CMAKE_INSTALL_PREFIX}${LIBRARY_PATH}/win")
install (TARGETS ddn DESTINATION "${CMAKE_INSTALL_PREFIX}${LIBRARY_PATH}/win")
elseif(LINUX)
install (DIRECTORY "${PROJECT_SOURCE_DIR}/lib/linux/" DESTINATION "${CMAKE_INSTALL_PREFIX}${LIBRARY_PATH}/linux")
install (TARGETS ddn DESTINATION "${CMAKE_INSTALL_PREFIX}${LIBRARY_PATH}/linux")
endif()
  • find_package(JNI REQUIRED): Find the JNI library in the system.
  • include_directories: Add the include path for the JNI library and Dynamsoft Document Normalizer SDK.
  • link_directories: Add the search path for the C++ library files.
  • add_library: Build a shared library.
  • target_link_libraries: Link the library with the Dynamsoft Document Normalizer SDK.
  • install: Copy the library files to the target folder.

Run the following commands to build the JNI project on Windows and Linux:

# Windows
mkdir build
cd build
cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..
cmake --build . --config Release --target install

# Linux
mkdir build
cd build
cmake ..
cmake --build . --config Release --target install

It will generate a dnn.dll for Windows and a libdnn.so for Linux. In the next section, we will package the native libraries into a JAR file.

How to Build Java JAR Package with C++ Libraries Using Maven

Create a pom.xml file, in which we define the resource path where native library files are located and use the Maven Assembly Plugin to package the native libraries into a JAR file.

<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dynamsoft</groupId>
<artifactId>ddn</artifactId>
<version>1.0.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.md</exclude>
<exclude>**/*.h</exclude>
<exclude>**/*.lib</exclude>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

After that, run the following command to generate ddn-1.0.0.jar:

mvn package

The JAR file contains class files and platform-specific libraries. To call these native libraries in Java applications, we need to firstly extract them to a temporary folder and then call System.load() to load the native library.

Note: when loading DLL files on Windows, the sequence of loading DLL files is important. The DLL files that are loaded first should not depend on other DLL files. For example, ddn.dll depends on DynamsoftDocumentNormalizerx64.dll, so DynamsoftDocumentNormalizerx64.dll should be loaded first. You will see unsatisfiedLinkError if you make the loading sequence wrong.

How to Implement JNI APIs for Document Edge Detection and Normalization

In this section, you will see how to implement APIs in Java and C++ to do document edge detection, perspective correction and image enhancement.

Initialize the Dynamsoft Document Normalizer

Since the license works globally, we create a static method to set the license. The method only needs to be called once.

The GetStringUTFChars method is used to convert Java string to C string. Don't forget release the memory after using it.

Create and Destroy Document Scanner Instance

In C/C++, we use DDN_CreateInstance to create a document scanner instance and use DDN_DestroyInstance to destroy the instance.

When creating a document scanner instance, the nativeCreateInstance() method is called in constructor of DocumentScanner class. The native pointer is saved in Java.

The C++ object is kept in memory until the destroyInstance() method is called.

Configure Parameters for Document Normalizer

The parameter configuration allows you to change the behavior of the document scanner.

For example, you can change the normalized image color mode:

Document Edge Detection

The DDN_DetectQuadFromFile() method is used to detect the document edge from an image file.

We need to create a DocumentResult class to store the detection result.

Document Normalization

After getting the quadrilateral coordinates of the document, we call DDN_NormalizeFile() to normalize the document.

The normalized image data are stored in NormalizedImage class.

Save the Normalized Document

The NormalizedImage class contains image data and image format. So we can convert NormalizedImage to BufferedImage and use ImageIO to save the image data to a file.

The ImageIO class does not support PDF, but Dynamsoft Document Normalizer does. The native method NormalizedImageResult_SaveToFile() can save a normalized document as BMP, PNG, JPEG and PDF files. The code is a little bit complicated that we need to construct a C++ ImageData type from Java NormalizedImage object.

In addition to saving images to files, we can also display them on the screen using JFrame:

How to Build Java Document Scanner Applications on Windows and Linux

The Whole Steps to Build and Test the Java Document Scanner SDK

Source Code

https://github.com/yushulx/java-document-scanner-sdk

Originally published at https://www.dynamsoft.com on November 22, 2022.

--

--

Xiao Ling
Xiao Ling

Written by Xiao Ling

Manager of Dynamsoft Open Source Projects | Tech Lover

No responses yet