How to Build Java Edge Detection Application to Scan and Normalize Documents
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
Originally published at https://www.dynamsoft.com on November 22, 2022.