iPDFdev Tips & Tricks for PDF development

Convert an image to PDF on the iPhone and iPad

April 22nd, 2011

A reader asked a me about image to PDF conversion a few days ago and I promised him an article, so here it is.

Image to PDF conversion is based on Quartz API. Using Quartz we can create PDF files and add content to them, the conversion actually means drawing the image on a PDF page.

Let's begin.

First we compute the size of the PDF page. Based on the image size and the resolution we want to draw the image, the page size is computed like this (for flexibility different horizontal and vertical resolutions are supported):

1
2
    double pageWidth = image.size.width * image.scale * 72 / horzRes;
    double pageHeight = image.size.height * image.scale * 72 / vertRes;

Now we're ready to create the PDF file. It can be created directly on disk or in memory. For flexibility let's create it in memory.

1
2
3
4
5
6
    NSMutableData *pdfFile = [[NSMutableData alloc] init];
    CGDataConsumerRef pdfConsumer = 
        CGDataConsumerCreateWithCFData((CFMutableDataRef)pdfFile);
    // The page size matches the image, no white borders.
    CGRect mediaBox = CGRectMake(0, 0, pageWidth, pageHeight);
    CGContextRef pdfContext = CGPDFContextCreate(pdfConsumer, &mediaBox, NULL);

The PDF context is created, we can now perform the conversion based on image's orientation (mostly required by the images taken with the camera):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    CGContextBeginPage(pdfContext, &mediaBox);
    switch (image.imageOrientation) {
        case UIImageOrientationDown:
            CGContextTranslateCTM(pdfContext, pageWidth, pageHeight);
            CGContextScaleCTM(pdfContext, -1, -1);
            break;
        case UIImageOrientationLeft:
            mediaBox.size.width = pageHeight;
            mediaBox.size.height = pageWidth;
            CGContextTranslateCTM(pdfContext, pageWidth, 0);
            CGContextRotateCTM(pdfContext, M_PI / 2);
            break;
        case UIImageOrientationRight:
            mediaBox.size.width = pageHeight;
            mediaBox.size.height = pageWidth;
            CGContextTranslateCTM(pdfContext, 0, pageHeight);
            CGContextRotateCTM(pdfContext, -M_PI / 2);
            break;
        case UIImageOrientationUp:
        default:
            break;
    } 
    CGContextDrawImage(pdfContext, mediaBox, [image CGImage]);
    CGContextEndPage(pdfContext);

The work is done, finalize the PDF file and release the used objects.

1
2
    CGContextRelease(pdfContext);
    CGDataConsumerRelease(pdfConsumer);

The PDF file is now available in the pdfFile variable.
I placed all the code above in the convertImageToPDF:withHorizontalResolution:verticalResolution: static method in the PDFImageConverter utility class, it can be used like this:

1
2
3
4
    NSData *pdfData = [PDFImageConverter convertImageToPDF: image 
        withHorizontalResolution: 300 verticalResolution: 300];
    NSString *path = [NSHomeDirectory() stringByAppendingPathComponent: @"Documents/image.pdf"];
    [pdfData writeToFile:path atomically:NO];

Sometimes it is necessary to convert an image to PDF and use a predefined page size, such as Letter or A4. Also a maximum bounding rectangle can be specified for the image, so it is placed at a specific position on the page and it does not get out of the page. In this situation the image size must be adjusted (increased resolution) to make sure the image fits the given rectangle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    double imageWidth = image.size.width * image.scale * 72 / resolution;
    double imageHeight = image.size.height * image.scale * 72 / resolution;
 
    double sx = imageWidth / boundsRect.size.width;
    double sy = imageHeight / boundsRect.size.height;
 
    // At least one image edge is larger than maxBoundsRect, reduce its size.
    if ((sx > 1) || (sy > 1)) {
        double maxScale = sx > sy ? sx : sy;
        imageWidth = imageWidth / maxScale;
        imageHeight = imageHeight / maxScale;
    }
 
    // Put the image in the top left corner of the bounding rectangle
    CGRect imageBox = CGRectMake(
        boundsRect.origin.x, boundsRect.origin.y + boundsRect.size.height - imageHeight, 
        imageWidth, imageHeight);

Having the final box, the image can be converted to PDF using the code in the first half of the article.
The code for the new conversion method is placed in the convertImageToPDF:withResolution:maxBoundsRect:pageSize: static method in the PDFImageConverter utility class, it is used like this:

1
2
3
4
5
6
7
8
    CGSize pageSize = CGSizeMake(612, 792);
    CGRect imageBoundsRect = CGRectMake(50, 50, 512, 692);
 
    NSData *pdfData = [PDFImageConverter convertImageToPDF: image 
        withResolution: 300 maxBoundsRect: imageBoundsRect pageSize: pageSize];
 
    NSString *path = [NSHomeDirectory() stringByAppendingPathComponent: @"Documents/image.pdf"];
    [pdfData writeToFile:path atomically:NO];

Source code: PDFImageConverter.zip
[relatedPosts]

Comments (42) Trackbacks (1)
  1. It would be possible to add a digital signature (certificate, hash, etc) to a PDF on the iPhone?

    Congratulations on the blog

    • Current PDF API in iOS does not support adding new objects in a PDF file, such as digital signatures, annotations, etc. Let’s hope this will be fixed in iOS 5 🙂

  2. If I convert a PDF page to image, add some ink to it, and convert the image back to PDF, will it grow in size substantially? Is there an easy way to replace this new page in the original PDF document?

    • The growth in size depends on the source PDF page. It is text and image based, the growth in size can be significant. If you convert the page to image at a lower resolution to get a smaller image, when you create the new page from the image it will look pixelated. If you convert the page to image at a higher resolution you will have an image with a higher quality but also greater size. I recommend to add the ink directly to the existing PDF page. You can create a CGPDFDocumentRef from your original PDF file, update the document’s pages and then save the new document.

  3. We’re trying to save the ink in a separate context and send it up to the server with a document id where it will be combined with the original document. How can we determine what page of the PDF document the user is viewing?

  4. Thank you for your article. This works perfectly.But I am trying to convert tiff images to pdf. Actually it converts the first page of tiff images to pdf. Do you know how to convert multi-paged tiff images to pdf ?

  5. Thanks for this great post! I’m trying to take an image from the device’s camera and convert it to PDF. I’m using this method to grab the image and convert it with your class:

    – (void)imagePickerController:(UIImagePickerController *)picker
    didFinishPickingMediaWithInfo:(NSDictionary *)info
    {
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];

    NSData *pdfData = [PDFImageConverter convertImageToPDF: image
    withHorizontalResolution: 300 verticalResolution: 300];

    NSString *path = [NSHomeDirectory() stringByAppendingPathComponent: @”Documents/new.pdf”];
    [pdfData writeToFile:path atomically:NO];
    }

    The issue is the image is saved rotated and it looks distorted. It works fine with any image that is not taken from the camera. Any ideas?

  6. Can i use this code to convert SVG images to PDF .

  7. Can you provide a code that converts SVG File to PDF.

  8. It’s a nice article. One thing what I observed was the UIImage will be converted to an image in PDF rather than actual PDF . Is there any way to convert it into actual PDF text.

  9. Very helpful post as I have posted this on our forum so other people can find out also on image to PDF creation

  10. Hi,

    How can i make multiple image into a single pdf. I tried ur code. but it works oly for 1 image. The image which i choose last alone appears in the pdf but i want all the images which i choose from the photo library to be displayed in the pdf. please help me

  11. I am also having difficulty saving multiple images as a single pdf. I loop through the images and apply the “begin”, “draw”, and “end” methods to each image, but the resulting pdf is only one of the images.

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    +(NSData*)convertArrayOfImagesToPDF: (NSArray*)theImages withResolution: (double)resolution {
    if (resolution <= 0) {
    return nil;
    }

    CGSize pageSize = CGSizeMake(612, 792);

    NSMutableData *pdfFile = [[NSMutableData alloc] init];
    CGDataConsumerRef pdfConsumer = CGDataConsumerCreateWithCFData((CFMutableDataRef)pdfFile);

    // CGContextRef pdfContext = nil;

    for (int i=0; i 1) || (sy > 1)) {
    double maxScale = sx > sy ? sx : sy;
    imageWidth = imageWidth / maxScale;
    imageHeight = imageHeight / maxScale;
    }

    // Put the image in the top left corner of the bounding rectangle
    CGRect imageBox = CGRectMake(0, 0 + pageSize.height – imageHeight, imageWidth, imageHeight);

    CGRect mediaBox = CGRectMake(0, 0, pageSize.width, pageSize.height);
    CGContextRef pdfContext = CGPDFContextCreate(pdfConsumer, &mediaBox, NULL);

    CGContextBeginPage(pdfContext, &mediaBox);
    CGContextDrawImage(pdfContext, imageBox, [image CGImage]);
    CGContextEndPage(pdfContext);
    CGContextRelease(pdfContext);

    }

    CGDataConsumerRelease(pdfConsumer);

    return pdfFile;
    }

  12. Nevermind – I figured it out. Just needed a good night’s sleep 🙂

    Here’s is the method that will give multi-page pdf from an array of images, in case anyone else is interested:

    //Code added by Chris Ruddell
    ////////////////////////////////////
    +(NSData*)convertArrayOfImagesToPDF: (NSArray*)theImages withResolution: (double)resolution {
    if (resolution < = 0) { return nil; } CGSize pageSize = CGSizeMake(612, 792); NSMutableData *pdfFile = [[NSMutableData alloc] init]; CGDataConsumerRef pdfConsumer = CGDataConsumerCreateWithCFData((CFMutableDataRef)pdfFile); CGRect mediaBox = CGRectMake(0, 0, pageSize.width, pageSize.height); CGContextRef pdfContext = CGPDFContextCreate(pdfConsumer, &mediaBox, NULL); for (int i=0; i < theImages.count; i++) { double maxScale = sx > sy ? sx : sy; imageWidth = imageWidth / maxScale; imageHeight = imageHeight / maxScale; // Put the image in the top left corner of the bounding rectangle CGRect imageBox = CGRectMake(0, 0 + pageSize.height - imageHeight, imageWidth, imageHeight); UIImage* image = [theImages objectAtIndex: i]; CGContextBeginPage(pdfContext, &mediaBox); CGContextDrawImage(pdfContext, imageBox, [image CGImage]); CGContextEndPage(pdfContext); } CGContextRelease(pdfContext); CGDataConsumerRelease(pdfConsumer); return pdfFile; }

  13. I’m using this code in a Cocoa app to see what I can do with some large images I have. This works very well when the image is 72 DPI but there’s a lot of detail lost when the DPI is higher. I have a large map that was scanned with 400 DPI that I’m trying to convert to PDF. Any ideas on how to handle that?

    Thank you for any thoughts you have,
    Nick

  14. I have the same issue as Dana & Stefan :
    “The issue is the image is saved rotated and it looks distorted. It works fine with any image that is not taken from the camera. Any ideas?”

    Any solutions anyone ?
    thanks
    Frank

    • I updated the article and the attached archive to include to source code that handles the image orientation. The images taken with the camera have a fixed layout and they are logically rotated through the Orientation tag in the EXIF data. The code now handles this situation correctly.

  15. Great article! I want to add electronic signature (handwriting signature) into a PDF. Do you think if I can do it directly with the quartz instead of using commercial PDF lib? I am sorry for the silly question because I am new to iOS development.

    Best,
    Thanh

  16. How can i add water mark in right bottom corner of pdf?

  17. I think you may have these lines wrong

    double pageWidth = image.size.width * image.scale * 72 / horzRes;
    double pageHeight = image.size.height * image.scale * 72 / vertRes;

    on this code horzRez and vertRez are divisors. That means that if you increase the resolution your final variables will have smaller values. As far as I know, higher resolutions will always give bigger contexts.

    so, I suppose these lines have to be

    double pageWidth = image.size.width * image.scale * horzRes / 72;
    double pageHeight = image.size.height * image.scale * vertRes / 72;

    isn’t it?

    • The article refers to image to PDF conversion (not PDF to image). In this scenario, the higher the resolution you want the smaller the image will appear on the page.

      • ok, if what you say is true then I do not understand the whole thing. I will give the example I am trying to understand. I have a picture taken by the camera that is 720 x 720 pixels. I want to print this picture and I want this picture, when printed at 600 dpi, to be exactly 2 x 2 inches. What is the size of the context I need to create?

        I thought this: PDF uses the reference of 72 points = 1 inch. If you will embed a 720 x 720 pixels in a 72 x 72 context, then, each inch will be 720 dpi when printed at 72 dpi. If I print it at 600 dpi the final printing will have 1.2 inches, right?

        so, what context should I have to generate a PDF that will print at 100 % scale to be 2 x 2 inches?

        • There are 2 resolutions here: the resolution of the embedded image in the PDF and the resolution of the printing device and they are totally independent. Please see this article that explains the situation: http://ipdfdev.com/2016/07/06/what-resolution-pdf-files/
          If your image is 720*720 pixels and you want its size on the page to be 2*2 inches, then you create a PDF file that is 2*2 inches and draw the image in it. The resolution of the embedded image will be 720/2 = 360dpi. When you print the PDF file it will always be 2*2 inches, no matter the dpi of the printer. The higher the printer dpi, the more details you will have in the printout, but the printout size will always be 2*2 inches.


Leave a comment