How to Merge Images into PDF on Android using Java
Sometimes, we may need to merge multiple images into a single PDF file. For example, we scan the front side and the back side of an ID card and want to store them in a single PDF file.
In this article, we are going to talk about how to build an Android app using Java to merge images into a PDF file with the help of Dynamsoft Document Normalizer.
New Project
Open Android Studio and create a new Empty Views Activity
.
Add Dependencies
Next, add Dynamsoft Document Normalizer and its dependencies.
-
Add the following to
settings.gradle
.dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven { url "https://download2.dynamsoft.com/maven/aar" } } }
-
Add the following to
build.gradle
.implementation 'com.dynamsoft:dynamsoftcapturevisionrouter:2.2.30' implementation 'com.dynamsoft:dynamsoftcore:3.2.30' implementation 'com.dynamsoft:dynamsoftdocumentnormalizer:2.2.11' implementation 'com.dynamsoft:dynamsoftimageprocessing:2.2.30' implementation 'com.dynamsoft:dynamsoftlicense:3.2.20' implementation 'com.dynamsoft:dynamsoftutility:1.2.20'
Layout Design
Open activity_main.xml
and add the following:
<Button
android:id="@+id/selectImagesButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select Images for Merging into PDF"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<CheckBox
android:id="@+id/enableAutoCroppingCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enable Auto Cropping"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/selectImagesButton" />
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="151dp"
android:layout_marginEnd="148dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/enableAutoCroppingCheckBox">
<RadioButton
android:id="@+id/blackAndWhiteRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Black and White" />
<RadioButton
android:id="@+id/grayRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Grayscale" />
<RadioButton
android:id="@+id/colorRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="Color" />
</RadioGroup>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/radioGroup" />
The layout is like the following screenshot. It contains a button to select images from the gallery, a checkbox to enable auto cropping of the document in the image and radio buttons to select which color mode to use for the images in the PDF.
Pick Images from Gallery
Create a new ActivityResultLauncher
and launch it for selecting the images after selectImagesButton
is clicked. We can get the list of URIs in onActivityResult
.
public class MainActivity extends AppCompatActivity {
public static final String TAG = "DDN";
private ActivityResultLauncher<String[]> galleryActivityLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
Button selectImagesButton = findViewById(R.id.selectImagesButton);
selectImagesButton.setOnClickListener((view)->{
galleryActivityLauncher.launch(new String[]{"image/*"});
});
galleryActivityLauncher = registerForActivityResult(new ActivityResultContracts.OpenMultipleDocuments(), new ActivityResultCallback<List<Uri>>() {
@Override
public void onActivityResult(List<Uri> results) {
if (results != null) {
// perform desired operations using the result Uri
Log.d(TAG,"selected "+results.size()+" files");
textView.setText("Merging...");
new Thread(new Runnable() {
@Override
public void run() {
try {
mergeImagesToPDF(results);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}).start();
} else {
Log.d(TAG, "onActivityResult: the result is null for some reason");
}
}
});
}
}
Use Dynamsoft Document Normalizer to Merge Images into PDF
-
Initialize the license for Dynamsoft Document Normalizer. You can apply for a license here.
private static final String LICENSE = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; protected void onCreate(Bundle savedInstanceState) { LicenseManager.initLicense(LICENSE, this, (isSuccess, error) -> { if (!isSuccess) { Log.e(TAG, "InitLicense Error: " + error); } }); }
-
Create an instance of Capture Vision Router to call Dynamsoft Document Normalizer.
private CaptureVisionRouter mRouter; protected void onCreate(Bundle savedInstanceState) { mRouter = new CaptureVisionRouter(MainActivity.this); }
-
Process images and save them to a PDF file. It uses Capture Vision Router to process the images and uses Image Manager to save them to a PDF file.
private void mergeImagesToPDF(List<Uri> results) throws Exception { String templateName = EnumPresetTemplate.PT_NORMALIZE_DOCUMENT; if (enableAutoCroppingCheckBox.isChecked()) { templateName = EnumPresetTemplate.PT_DETECT_AND_NORMALIZE_DOCUMENT; }else{ templateName = EnumPresetTemplate.PT_NORMALIZE_DOCUMENT; SimplifiedCaptureVisionSettings settings = mRouter.getSimplifiedSettings(EnumPresetTemplate.PT_NORMALIZE_DOCUMENT); settings.roiMeasuredInPercentage = true; settings.roi = new Quadrilateral(new Point(0,0),new Point(100,0),new Point(100,100),new Point(0,100)); //process full image mRouter.updateSettings(EnumPresetTemplate.PT_NORMALIZE_DOCUMENT,settings } ImageManager imageManager = new ImageManager(); File externalFilesDir = this.getApplicationContext().getExternalFilesDir(""); String filename = new Date().getTime()+".pdf"; File outputFile = new File(externalFilesDir,filename); for (Uri result:results) { InputStream inp = this.getApplicationContext().getContentResolver().openInputStream(result); if (inp != null) { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int nRead; byte[] data = new byte[16384]; while ((nRead = inp.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); } CapturedResult capturedResult = mRouter.capture(buffer.toByteArray(), templateName); NormalizedImagesResult normalizedImagesResult = capturedResult.getNormalizedImagesResult(); if (normalizedImagesResult != null) { NormalizedImageResultItem[] items = normalizedImagesResult.getItems(); if (items != null && items.length>0) { ImageData imageData = items[0].getImageData(); imageManager.saveToFile(imageData,outputFile.getPath(),true); //will append images to a PDF file } } } } Log.d(TAG,"run on ui thread"); runOnUiThread(()->{ if (outputFile.exists()) { textView.setText("PDF written to "+outputFile.getAbsolutePath()); }else{ textView.setText("Failed"); } }); }
-
The color mode is modified with the following code. Converting an image to black and white can clean the background and save the file’s size while converting to grayscale has a balance of details and size.
private void updateColorMode() throws CaptureVisionRouterException { int colorMode; if (blackAndWhiteRadioButton.isChecked()){ colorMode = EnumImageColourMode.ICM_BINARY; }else if (grayRadioButton.isChecked()){ colorMode = EnumImageColourMode.ICM_GRAYSCALE; }else{ colorMode = EnumImageColourMode.ICM_COLOUR; } SimplifiedCaptureVisionSettings settings = mRouter.getSimplifiedSettings(templateName); settings.documentSettings.colourMode = mode; mRouter.updateSettings(templateName,settings); }
Screenshot of converted files:
Source Code
You can check out the source code to have a try: https://github.com/tony-xlh/Merge-Images-to-PDF/tree/main/Android/PDFCreator