16

I'm converting a PDF page into a UIImage. While doing so, I lose the image quality. Need help in getting high quality images.

Code to generate UIImage

-(UIImage *)imageForPage:(int)pageNumber {
    CGPDFPageRef pdfPageRef = [self pdfReferenceForPage:pageNumber];

    CGSize pageSize = [self sizeOfPage:pageNumber];

    //UIGraphicsBeginImageContext(pageSize);

    UIGraphicsBeginImageContextWithOptions(pageSize, NO, 0.0);

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetInterpolationQuality(context, kCGInterpolationHigh);

    CGContextTranslateCTM(context, 0.0, pageSize.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextSaveGState(context);

    CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(pdfPageRef, kCGPDFCropBox, CGRectMake(0, 0, pageSize.width, pageSize.height), 0, true);
    CGContextConcatCTM(context, pdfTransform);

    CGContextDrawPDFPage(context, pdfPageRef);
    CGContextRestoreGState(context);

    UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return resultingImage;
}


- (void)saveImage {
       UIImage *image = [self imageForPage:1];
       [UIImageJPEGRepresentation(image, 1.0) writeToFile:filePath atomically:YES];
}

Image comparison

Original PDF Original PDF


Image from PDF Image from PDF

----------

EDIT: CODE WITH CHANGES

float dpi = 100.0 / 72.0;

CGPDFPageRef pdfPageRef = [self pdfReferenceForPage:pageNumber];

CGSize pageSize = [self sizeOfPage:pageNumber];
pageSize.width = pageSize.width * dpi;
pageSize.height = pageSize.height * dpi;

UIGraphicsBeginImageContext(pageSize);

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGContextTranslateCTM(context, 0.0, pageSize.height);
CGContextScaleCTM(context, dpi, -dpi);
CGContextSaveGState(context);
//CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(pdfPageRef, kCGPDFCropBox, CGRectMake(0, 0, pageSize.width, pageSize.height), 0, true);
//CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, pdfPageRef);
CGContextRestoreGState(context);

UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

return resultingImage;
Sahil Khanna
  • 4,262
  • 8
  • 47
  • 72
  • You are probably rendering at 72 ppi, which is the default for PDF contexts... You'll have to change to 300 ppi or more. – nielsbot Jan 04 '13 at 06:52
  • 1
    I was having the same problem, but the answer for me was simply adding the line: CGContextextSetInterpolationQuality(context, .High) – Dale Nov 27 '16 at 21:23

7 Answers7

10

This one gives the best quality... if you need another quality just change dpi.

    func drawPDFfromURL(url: URL) -> UIImage? {
    guard let document = CGPDFDocument(url as CFURL) else { return nil }
    guard let page = document.page(at: 1) else { return nil }
    let dpi: CGFloat = 300.0 / 72.0
    let pageRect = page.getBoxRect(.mediaBox)

    let renderer = UIGraphicsImageRenderer(size: CGSize(width: pageRect.size.width * dpi, height: pageRect.size.height * dpi))

    let img1 = renderer.jpegData(withCompressionQuality: 1.0, actions: { cnv in
          UIColor.white.set()
          cnv.fill(pageRect)
          cnv.cgContext.translateBy(x: 0.0, y: pageRect.size.height * dpi);
          cnv.cgContext.scaleBy(x: dpi, y: -dpi);
          cnv.cgContext.drawPDFPage(page);

        })
    let img2 = UIImage(data: img1)
    return img2
}
Ivan
  • 101
  • 1
  • 3
6

When you ask the PDF for it's page size, you're getting a width/height for 72 PPI. You might try creating a context that's scaled up using a scale transform. For example, if you wanted to render at 300 dpi, add a scale transform to scale by 300.0/72.0.

If you export as TIFF, you will be able to encapsulate the final PPI (300) of the generated image.

nielsbot
  • 15,922
  • 4
  • 48
  • 73
  • Thanks for explaining. I couldn't understand how to scale transform a context. Tried googling but in vain. Can you please help further, probably with some code snippets? – Sahil Khanna Jan 04 '13 at 08:05
  • I thoroughly studied Transforms and understood what you meant. Silly on my part. Now I get high quality images. Can you also help to get the DPI of a page in PDF? Currently I set it to 300 for every page, but I want to set it to the original value of the page. – Sahil Khanna Jan 05 '13 at 09:05
  • I think it's always 72 PPI. You'll have to check the PDF spec. – nielsbot Jan 05 '13 at 09:16
  • @Sahil How did you set the PPI? I am running on the same issue. Could you please help? – GenieWanted Jun 02 '14 at 12:16
  • Care to show a code sample on how you finally managed to solve it? – Reza.Ab Jan 31 '18 at 03:45
3

Try this code for Swift 3 :

func drawPDFfromURL(url: URL) -> UIImage? {
    guard let document = CGPDFDocument(url as CFURL) else { return nil }
    guard let page = document.page(at: 1) else { return nil }

    let pageRect = page.getBoxRect(.mediaBox)
    let renderer = UIGraphicsImageRenderer(size: pageRect.size)
    let img1 = renderer.jpegData(withCompressionQuality: 1.0, actions: { cnv in
        UIColor.white.set()
        cnv.fill(pageRect)
        cnv.cgContext.translateBy(x: 0.0, y: pageRect.size.height);
        cnv.cgContext.scaleBy(x: 1.0, y: -1.0);
        cnv.cgContext.drawPDFPage(page);
    })
    let img2 = UIImage(data: img1)
    return img2
}

This gives much higher resolution

2

I've written a handy UIImage PDF renderer category that you may find helpful. It also caches all rendered results for high performance.

https://github.com/mindbrix/UIImage-PDF

Mindbrix
  • 129
  • 6
2
import PDFKit

let pdfDocument = PDFDocument(url: pdfUrl)!
let thumb = pdfDocument.page(at: pageIndex)?.thumbnail(of: size, for: .mediaBox)
Warif Akhand Rishi
  • 23,920
  • 8
  • 80
  • 107
2

As of iOS 11.0, macOS 10.13, Mac Catalyst 13.0 you can use thumbnailOfSize:forBox:

Skoua
  • 3,373
  • 3
  • 38
  • 51
Holtwick
  • 1,849
  • 23
  • 29
  • If the PDF is a transparent image, this method will use white background. Is there a way to remove the background? – Aleksandar Vacić Nov 09 '22 at 09:21
  • I'm afraid this is not possible with this approach. You'll probably need to use one of the other suggested solutions that render PDF directly as a graphic. – Holtwick Nov 10 '22 at 11:06
1

Combining Ivan's and Dale's answers I managed to achieve a result indistinguishable from the image in the assets catalog with Preserve Vector Data checkbox checked. The only downside is that the output image size is 9 times larger than the original PDF. Here is code:

func convertPDFDataToImage() -> UIImage? {
    guard let document = CGPDFDocument(url as CFURL),
          let page = document.page(at: 1) else { return nil }

    let dpi: CGFloat = 9
    let pageRect = page.getBoxRect(.mediaBox)
    let imageSize = CGSize(width: pageRect.size.width * dpi, height: pageRect.size.height * dpi)

    let renderer = UIGraphicsImageRenderer(size: imageSize)

    let imageData = renderer.pngData { cnv in
        UIColor.clear.set()
        cnv.fill(pageRect)
        cnv.cgContext.interpolationQuality = .high
        cnv.cgContext.translateBy(x: 0.0, y: pageRect.size.height * dpi)
        cnv.cgContext.scaleBy(x: dpi, y: -dpi)
        cnv.cgContext.drawPDFPage(page)
    }

    return UIImage(data: imageData)
}
Roma Kavinskyi
  • 268
  • 4
  • 12
  • I needed a solution that would keep the transparency layer and this answer led me to the right direction with the UIColor.clear.set :)) – Edudjr Mar 09 '23 at 17:45