Converting an Image to PDF in Kotlin
In this blog post, we’ll take a look at how to create a PDF from an image. We’ll be using PSPDFKit for Android to convert the image to a PDF, which we can then open and annotate like any other PDF.
Below is a preview of how the final product will work.
So let’s get started.
Converting an Image File to PDF
Let’s kick this off with the most interesting part: how to actually convert an image to PDF. While PSPDFKit has the ability to work directly with images, in some cases, it might be preferable to convert images to PDFs before annotating them. To do this, we’ll be using the PdfProcessor
. This provides APIs that allow you to add NewPage
objects. In doing this, you can create a new PDF — containing the exact content you want — from scratch.
In our case, we’ll be using a PageImage
as the content for our new page. This will create a PDF page with the specified image as its background. Let’s see how that looks below:
/** * This creates a `PdfProcessorTask` that will create a single-page document using the supplied image as the page background. */ private fun createPdfFromImageTask(imageUri: Uri) : PdfProcessorTask { // First obtain the size of the image. val options = BitmapFactory.Options().apply { // By setting this, we won't actually load the image but only figure out the size. inJustDecodeBounds = true } BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri), null, options) val imageHeight: Int = options.outHeight val imageWidth: Int = options.outWidth // We take A4 as a baseline and alter the page's aspect ratio based on the given bitmap. val pageSize: Size = if (imageWidth <= imageHeight) { Size(NewPage.PAGE_SIZE_A4.width, imageHeight * (NewPage.PAGE_SIZE_A4.width / imageWidth)) } else { Size(NewPage.PAGE_SIZE_A4.height, imageHeight * NewPage.PAGE_SIZE_A4.height / imageWidth) } // Now that we know the desired size, we can create a `PdfProcessorTask` that will create a document containing a single page. return PdfProcessorTask.newPage(NewPage.emptyPage(pageSize) // We initialize our new page using the passed-in image URI and calculated page size. .withPageItem(PageImage(this, imageUri, RectF(0f, pageSize.height, pageSize.width, 0f))) .build()) }
Looking at the code, the only thing we haven’t discussed already is the need to set the correct page size. To do this, we first need to obtain the size of the selected image, and based on that, we can then create an appropriate page size. For our example, we scale it so it matches an A4-sized page in the largest dimension.
All we need to do now is create the PdfProcessorTask
by calling createPdfFromImageTask
and passing it to PdfProcessor.processDocument
. We’ll get to that in the next section where we create the rest of our activity that actually handles opening the file and then showing the PdfActivity
.
Opening the Image
The first thing we do is set up our PdfFromImageActivity
to immediately open the file picker when a user starts it. To do this, we create an Intent
with ACTION_GET_CONTENT
, which will open the system file picker:
private var waitingForResult = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Prevent the example from requesting multiple documents at the same time. if (!waitingForResult) { waitingForResult = true startActivityForResult(getImagePickerIntent(), REQUEST_IMAGE) } } private fun getImagePickerIntent(): Intent? { // Creates an intent that will open a file picker with the filter set to only open images. val intent = Intent() intent.type = "image/*" intent.action = Intent.ACTION_GET_CONTENT return if (intent.resolveActivity(packageManager) == null) null else Intent.createChooser(intent, "") } companion object { private const val REQUEST_IMAGE = 1 }
After we start the file picker, we need to handle the result. To do this, we’ll implement onActivityResult
. In the callback, we’ll exit PdfFromImageActivity
since it’s no longer needed. We’ll also call createPdfFromImageTask
with the URI that was returned. Finally, we’ll use PdfProcessor.processDocument
and show the resulting PDF:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { // Once the image is picked and we're done with this activity, close it. finish() if (requestCode == REQUEST_IMAGE && resultCode == RESULT_OK && data?.data != null) { // Grab the path to the selected image. val imageUri = data.data ?: return // Create a `PdfProcessorTask` to create the new PDF. val task = createPdfFromImageTask(imageUri) // Obtain a path where we can save the resulting file. // For simplicity we always put it in our application directory. val outputPath = filesDir.resolve("image.pdf") // Process the document. PdfProcessor.processDocument(task, outputPath) // And finally show it. PdfActivity.showDocument(this, Uri.fromFile(outputPath), PdfActivityConfiguration.Builder(this).build()) } }
And with that, we’re done! Our PdfFromImageActivity
now asks the user to pick an image, converts the selected image to a PDF, and then presents the PDF in a PdfActivity
.
Conclusion
In this article, we covered how to use the PdfProcessor
to create new PDF documents from scratch based on existing images. We looked at the NewPage
API that’s used for specifying how newly added pages look, and we used the PageImage
API to put the image in our document.
There are many places you can go from here. For example, you could create more elaborate PDFs from multiple images or other content, use this to attach images to existing documents, or simply use it to annotate the image.
You can find the full source code for this example in our Android catalog.