Convert a PDF to an Image in Swift

Illustration: Convert a PDF to an Image in Swift

Converting a PDF file to an image is a common use case for any app that displays PDFs. An app might want to display the image representation of a PDF file’s page at different resolutions — some examples include a low-resolution image for a thumbnail, a medium-resolution image for a full-page display, and a high-resolution cropped image to show when zoomed in or for printing purposes. This blog post will show how to convert your PDF file to an image using Core Graphics, PDFKit, and PSPDFKit for iOS.

Apple has a rich history of supporting PDF files that dates back to its old NeXTSTEP days. As such, iOS has built-in support for reading and rendering PDF files. Starting with iOS 11, Apple introduced a framework to display and manipulate PDF files: PDFKit. In turn, PDFKit is built on top of Core Graphics, which is a framework used primarily for lightweight 2D graphics rendering. For our use case, we can rely on the functionality of either one of these to convert a PDF file to an image. So let’s get started.

Core Graphics

Here’s how to render a page from a PDF file to an image using Core Graphics:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Create a URL for the PDF file.
guard let path = Bundle.main.path(forResource: "filename", ofType: "pdf") else { return }
let url = URL(fileURLWithPath: path)

// Instantiate a `CGPDFDocument` from the PDF file's URL.
guard let document = CGPDFDocument(url as CFURL) else { return }

// Get the first page of the PDF document. Note that page indices start from 1 instead of 0.
guard let page = document.page(at: 1) else { return }

// Fetch the page rect for the page we want to render.
let pageRect = page.getBoxRect(.mediaBox)

// Optionally, specify a cropping rect. Here, we don’t want to crop so we keep `cropRect` equal to `pageRect`.
let cropRect = pageRect

let renderer = UIGraphicsImageRenderer(size: cropRect.size)
let img = renderer.image { ctx in
    // Set the background color.
    UIColor.white.set()
    ctx.fill(CGRect(x: 0, y: 0, width: cropRect.width, height: cropRect.height))

    // Translate the context so that we only draw the `cropRect`.
    ctx.cgContext.translateBy(x: -cropRect.origin.x, y: pageRect.size.height - cropRect.origin.y)

    // Flip the context vertically because the Core Graphics coordinate system starts from the bottom.
    ctx.cgContext.scaleBy(x: 1.0, y: -1.0)

    // Draw the PDF page.
    ctx.cgContext.drawPDFPage(page)
}

Here, we haven’t made use of the cropping rect, but we can use it for something like tiled rendering, which is where we split the PDF into multiple smaller images. Doing this has several benefits, such as faster rendering and lower memory usage when displaying a zoomed-in document.

One caveat of the UIGraphicsImageRenderer drawing API is that only a single page can be rendered at a time. If you want concurrent rendering, you’ll need to create multiple UIGraphicsImageRenderer instances for the same document and enqueue the rendering jobs in background queues.

PDFKit

We can use Apple’s PDFKit instead of Core Graphics for the same task. The code is almost the same as before, the only difference being that we use PDFPage’s draw(with box: to context:) API to do the actual drawing.

Here’s how the code looks:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Create a URL for the PDF file.
guard let path = Bundle.main.path(forResource: "filename", ofType: "pdf") else { return }
let url = URL(fileURLWithPath: path)

// Instantiate a `CGPDFDocument` from the PDF file's URL.
guard let document = PDFDocument(url: url) else { return }

// Get the first page of the PDF document.
guard let page = document.page(at: 0) else { return }

// Fetch the page rect for the page we want to render.
let pageRect = page.bounds(for: .mediaBox)

let renderer = UIGraphicsImageRenderer(size: pageRect.size)
let img = renderer.image { ctx in
    // Set and fill the background color.
    UIColor.white.set()
    ctx.fill(CGRect(x: 0, y: 0, width: pageRect.width, height: pageRect.height))

    // Translate the context so that we only draw the `cropRect`.
    ctx.cgContext.translateBy(x: -pageRect.origin.x, y: pageRect.size.height - pageRect.origin.y)

    // Flip the context vertically because the Core Graphics coordinate system starts from the bottom.
    ctx.cgContext.scaleBy(x: 1.0, y: -1.0)

    // Draw the PDF page.
    page.draw(with: .mediaBox, to: ctx.cgContext)
}

PSPDFKit for iOS

The PDF manipulation API provided by Apple in Core Graphics and PDFKit is quite low level. If you need extra customization — such as adding custom drawing (e.g. watermarks) on a PDF page or controlling the page color — you’ll need to write your own code.

PSPDFKit offers a comprehensive PDF solution for iOS and other platforms, and first-class support is included with every plan. Our SDK comes with a fully featured PDF document viewer with a modern customizable user interface and a range of additional advanced features such as text extraction and search, full annotation and forms support, document editing, redaction, and much more.

To render a PDF as an image in PSPDFKit, you need to do the following:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Create a URL for the PDF file.
guard let path = Bundle.main.path(forResource: "report", ofType: "pdf") else { return }
let url = URL(fileURLWithPath: path)

// Instantiate a `Document` from the PDF file's URL.
let document = Document(url: url)

let pageIndex: PageIndex = 0
guard let pageImageSize = document.pageInfoForPage(at: pageIndex)?.mediaBox.size else { return }

// Create a render request from your `Document`.
let request = MutableRenderRequest(document: document)
request.imageSize = pageImageSize
request.pageIndex = pageIndex

do {
    // Create a render task using the `MutableRenderRequest`.
    let task = try RenderTask(request: request)
    task.priority = .utility
    PSPDFKit.SDK.shared.renderManager.renderQueue.schedule(task)

    // The page is rendered as a `UIImage`.
    let image = try PSPDFKit.SDK.shared.cache.image(for: request, imageSizeMatching: [.allowLarger])
} catch {
    // Handle error.
}

The RenderTask API that we used in the above example is an asynchronous API that won’t block the main thread when rendering a PDF page to an image. It includes an image cache by default, so multiple render calls for the same page are fetched directly from the cache. We also have a full featured guide article about how to use the RenderTask API, and it outlines the process of rendering PDF pages.

Additionally, we provide another API to render a PDF page to an image: imageForPage:at:size:clippedTo:annotations:options. One thing to keep in mind is that this method is a synchronous call that will block the main thread when it’s rendering a particularly large or complex PDF page.

If you’re interested in our solution, visit our iOS PDF SDK page to learn more and start a free trial of PSPDFKit for iOS.

Processing Rendered Images

When rendering a PDF page to an image, there are many ways to process the image before displaying or sharing it. We can add filters, transforms, and watermarks, as well as use a bunch of other image processing tools provided by Core Graphics to alter an image. In the following examples, we’ll showcase a few use cases where we do exactly that.

Using a Filter to Invert Image Colors

Inverting colors of an image or showing an image in grayscale is a common requirement for a lot of use cases — it could be to show an image in the all-too-popular Dark Mode, or for accessibility purposes, or just as a reading experience improvement for something like a sepia filter. In the following example, we’ll show how to invert the color of an image.

The initial steps are almost identical to the simple page rendering example above:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Create a URL for the PDF file.
guard let path = Bundle.main.path(forResource: "filename", ofType: "pdf") else { return }
let url = URL(fileURLWithPath: path)

// Instantiate a `CGPDFDocument` from the PDF file's URL.
guard let document = CGPDFDocument(url as CFURL) else { return }

// Get the first page of the PDF document. Note that page indices start from 1 instead of 0.
guard let page = document.page(at: 1) else { return }

// Fetch the page rect for the page we want to render.
let pageRect = page.getBoxRect(.mediaBox)

let renderer = UIGraphicsImageRenderer(size: pageRect.size)
let img = renderer.image { ctx in
    // Set and fill the background color.
    UIColor.white.set()
    ctx.fill(CGRect(x: 0, y: 0, width: pageRect.width, height: pageRect.height))

    // Translate the context.
    ctx.cgContext.translateBy(x: -pageRect.origin.x, y: pageRect.size.height - pageRect.origin.y)

    // Flip the context vertically because the Core Graphics coordinate system starts from the bottom.
    ctx.cgContext.scaleBy(x: 1.0, y: -1.0)

    // Draw the PDF page.
    ctx.cgContext.drawPDFPage(page)
}

Now, once we have the image object, we’ll create a filter and run the image through the filter:

Copy
1
2
3
4
5
6
7
8
9
// Create a `CIImage` from the input image.
let inputImage = CIImage(cgImage: img.cgImage!)

// Create an inverting filter and set its input image.
guard let filter = CIFilter(name: "CIColorInvert") else { return }
filter.setValue(inputImage, forKey: kCIInputImageKey)

// Get the output `CIImage` from the filter.
guard let outputCIImage = filter.outputImage else { return }

Finally, we convert the output CIImage into a UIImage to be able to use it:

1
2
// Convert the `CIImage` to `UIImage`
let outputImage = UIImage(ciImage: outputCIImage)

The images below show the result.

Input / Output Image

PSPDFKit directly supports adding CIFilters to a document’s render options. To get the same output as the example above, in PSPDFKit you need to do this:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
guard let path = Bundle.main.path(forResource: "filename", ofType: "pdf") else { return }
let url = URL(fileURLWithPath: path)

// Instantiate a `Document` from the PDF file's URL.
let document = Document(url:url)
let pageInfo = document.pageInfoForPage(at: 0)!

// Create an inverting filter.
guard let filter = CIFilter(name: "CIColorInvert") else { return }

// Set the inverting filter as a rendering option.
document.updateRenderOptions(for: .all) { (options) in
    options.additionalCIFilters = [filter];
}

do {
    let image = try document.imageForPage(at: 0, size: pageInfo.size, clippedTo: .zero, annotations: nil, options: nil)
    print(image)
    // Use the image.
} catch {
    // Handle error.
}

Watermarks

Adding a watermark to a PDF page is another common use case. In our Watermarking a PDF on iOS blog post, we describe how to add either a temporary or a permanent watermark to a PDF file, and also how to perform the same tasks using PSPDFKit’s API. For reference, here’s how to add a permanent watermark to a PDF using PSPDFKit:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Create default configuration.
guard let configuration = Processor.Configuration(document: document) else {
    // Handle failure.
    abort()
}

configuration.drawOnAllCurrentPages { context, page, cropBox, _ in
    let text = "PSPDF Live Watermark"

    // Add text over the diagonal of the page.
    context.translateBy(x: 50, y: cropBox.size.height / 2)
    let attributes: [NSAttributedString.Key: Any] = [
        .font: UIFont.boldSystemFont(ofSize: 100),
        .foregroundColor: UIColor.red.withAlphaComponent(0.5)
    ]
    text.draw(with: cropBox, options: .usesLineFragmentOrigin, attributes: attributes, context: NSStringDrawingContext())
}

do {
    // Start the conversion from `document` to `processedDocumentURL`.
    let processor = Processor(configuration: configuration, securityOptions: nil)
    try processor.write(toFileURL: processedDocumentURL)
} catch {
    // Handle failure.
    abort()
}

Miscellaneous

Apart from the two use cases mentioned above, there are a couple of other interesting components provided by PSPDFKit that I want to highlight. These allow you to annotate images or perform optical character recognition (OCR) on your images.

  • Annotating Images: PSPDFKit for iOS provides the ImageDocument class, which makes the process of annotating images easy. All you need to do is pass your image to this class, and we handle the rest. We even simplified the PDF controller configuration by providing a prebuilt configuration that adjusts the UI so that it works great for images. To learn more, check out our Annotate Images guide.

Here’s a code snippet that shows how simple it is to open an image as a PDF document:

Copy
1
2
3
4
5
6
7
8
9
10
11
// Create a URL for the PDF file.
guard let path = Bundle.main.path(forResource: "img", ofType: "png") else { return }
let url = URL(fileURLWithPath: path)

// Instantiate an image document from the image's URL and present it.
let imageDocument = ImageDocument(imageURL: url)
let pdfViewController = PDFViewController(document: imageDocument, configuration: PDFConfiguration.image)
let pdfNavigationController = PDFNavigationController(rootViewController: pdfViewController)

// Present the view controller.
self.present(pdfNavigationController, animated: true)
  • OCR: When dealing with PDF documents, you’ll sometimes encounter documents with inaccessible text — for example, when working with scanned or photographed pages. PSPDFKit’s OCR component unlocks inaccessible text and makes it available for selecting, copying, and searching. If you’d like to check out the OCR component, we also have a handy integration guide.

Conclusion

This blog post shows how easy it is to render a PDF file to an image using Core Graphics, PDFKit, and PSPDFKit for iOS. We also talk about the same task for the Android platform here. To perform the reverse task and convert an image to a PDF, please check out the Converting an Image to a PDF in Swift blog post.

Along with rendering an image, we explored several tasks related to images in PDFs such as:

  • Applying a filter to an image
  • Adding a watermark to a PDF file
  • Annotating images
  • Performing OCR

If you’d like to try PSPDFKit for iOS for yourself, head over to the trial page and download the SDK today.

PSPDFKit for iOS

Download the free 60-day trial and add it to your app today.