Blog Post

Converting an Image to PDF in Kotlin

Reinhard Hafenscher
Illustration: 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.

Explore related topics

Related products
Share post
Free trial Ready to get started?
Free trial