How can I programmatically generate a thumbnail of a PDF with iOS? - ios

We're displaying PDF content using UIWebViews at the moment. Ideally I would like to be able to display thumbnails in the UITableView without loading many different UIWebViews concurrently... they're slow enough loading one document - never mind 10+!
How do I go about doing this?
I've thought about screen capturing a document loaded using UIDocumentInteractionController or UIWebView but this means they'd all have to be thumbnailed before displaying the table.

Apple supply a whole bunch of methods down at the CoreGraphics level for drawing PDF content directly. As far as I'm aware, none of it is neatly packaged up at the UIKit level, so it may not be a good fit for your project at this point, especially if you're not as comfortable down at the C level. However, the relevant function is CGContextDrawPDFPage; per the normal CoreGraphics way of things there are other methods to create a PDF reference from a data source and then to get a page reference from a PDF. You'll then need to deal with scaling and translating to the view you want, and be warned that you'll need to perform a horizontal flip because PDFs (and OS X) use the lower left as the origin whereas iOS uses the top left. Example code, off the top of my head:
UIGraphicsBeginImageContext(thumbnailSize);
CGPDFDocumentRef pdfRef = CGPDFDocumentCreateWithProvider( (CGDataProviderRef)instanceOfNSDataWithPDFInside );
CGPDFPageRef pageRef = CGPDFDocumentGetPage(pdfRef, 1); // get the first page
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// ignore issues of transforming here; depends on exactly what you want and
// involves a whole bunch of normal CoreGraphics stuff that is nothing to do
// with PDFs
CGContextDrawPDFPage(contextRef, pageRef);
UIImage *imageToReturn = UIGraphicsGetImageFromCurrentImageContext();
// clean up
UIGraphicsEndImageContext();
CGPDFDocumentRelease(pdfRef);
return imageToReturn;
At a guess, you'll probably want to use CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox) to get the page's crop box and then work out how to scale/move that to fit your image size.
Probably easier for your purposes is the -renderInContext: method on CALayer (see QA 1703) — the old means of getting a screenshot was UIGetScreenImage, but that was never really official API and was seemingly temporarily allowed only because of the accidental approval of RedLaser. With the code in the QA you can rig yourself up to get a UIImage from any other view without that view having to be on screen. Which possibly resolves some of your issue with screen capturing? Though it means you can support OS 4.x only.
In either case, PDFs just don't draw that quickly. You probably need to populate the table then draw the thumbnails on a background thread, pushing them upwards when available. You can't actually use UIKit objects safely on background threads but all the CoreGraphics stuff is safe.

Here is sample code considering transformation. :)
NSURL* pdfFileUrl = [NSURL fileURLWithPath:finalPath];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfFileUrl);
CGPDFPageRef page;
CGRect aRect = CGRectMake(0, 0, 70, 100); // thumbnail size
UIGraphicsBeginImageContext(aRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
UIImage* thumbnailImage;
NSUInteger totalNum = CGPDFDocumentGetNumberOfPages(pdf);
for(int i = 0; i < totalNum; i++ ) {
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, aRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetGrayFillColor(context, 1.0, 1.0);
CGContextFillRect(context, aRect);
// Grab the first PDF page
page = CGPDFDocumentGetPage(pdf, i + 1);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFMediaBox, aRect, 0, true);
// And apply the transform.
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, page);
// Create the new UIImage from the context
thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();
//Use thumbnailImage (e.g. drawing, saving it to a file, etc)
CGContextRestoreGState(context);
}
UIGraphicsEndImageContext();
CGPDFDocumentRelease(pdf);

Here is a swift 3 method for generating a UIImage thumbnail for a pdf page
static func getThumbnailForPDF(_ urlString:String, pageNumber:Int) -> UIImage? {
let bundle = Bundle.main
let path = bundle.path(forResource: urlString, ofType: "pdf")
let pdfURL = URL(fileURLWithPath: path!)
let pdf = CGPDFDocument(pdfURL as CFURL )!
let page = pdf.page(at: pageNumber)!
let rect = CGRect(x: 0, y: 0, width: 100.0, height: 70.0) //Image size here
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
context.translateBy(x: 0, y: rect.height)
context.scaleBy(x: 1.0, y: -1.0)
context.setFillColor(gray: 1.0, alpha: 1.0)
context.fill(rect)
let pdfTransform = page.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true)
context.concatenate(pdfTransform)
context.drawPDFPage(page)
let thumbImage = UIGraphicsGetImageFromCurrentImageContext()
context.restoreGState()
UIGraphicsEndImageContext()
return thumbImage
}

If you use PDFKit, there is a method for PDFPage called thumbnail. It is available in iOS11 and above and does the job in just a few lines of code. Here is a simple method for generating a thumbnail of a given page in a PDFDocument.
func makeThumbnail(pdfDocument: PDFDocument?, page: Int) -> UIImage? {
return pdfDocument?.page(at: page)?.thumbnail(of: CGSize(width: 40, height: 40), for: .artBox)
}
Unfortunately it misses official documentation (see, https://developer.apple.com/documentation/pdfkit/pdfpage/2869834-thumbnail). However, there are some comments accessible in the code for PDFPage.

Related

iOS - display PDF in a different Orientation

I'm using the following code to display a PDF inside a UIView
Its a simple application, displaying a single PDF (which is all I need) and I"m handling zooming separately: The problem I'm having with this is when the iPad changes to Landscape everything is distorted - any help as to where I should look ? / what i should do to handle the orientation issue ?
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
//PDF might be transparent, assume white paper - set White Background
[[UIColor whiteColor] set];
CGContextFillRect(ctx, rect);
//Flip coordinates
CGContextGetCTM(ctx);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -rect.size.height);
//PDF File Path
NSURL *pdfURL = [[NSBundle mainBundle] URLForResource:#"TEST" withExtension:#"pdf"];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)pdfURL);
CGPDFPageRef page1 = CGPDFDocumentGetPage(pdf, 1);
//Get the rectangle of the cropped inside
CGRect mediaRect = CGPDFPageGetBoxRect(page1, kCGPDFCropBox);
CGContextScaleCTM(ctx, rect.size.width / mediaRect.size.width,
rect.size.height / mediaRect.size.height);
//Draw PDF
CGContextDrawPDFPage(ctx, page1);
CGPDFDocumentRelease(pdf);
}
Hi For Anybody Who may be interested in this. . .
This may be the best way to do it :
Supporting two orientations with separate view controllers in iOS
The following question discusses how to register to device orientation change, When a change in orientation is detected, a modal View is pushed -displaying the PDF with dimensions
The Other options may be as follows : when you initialize the UIView decide how you want it to react when the orientation changes
UIView.contentMode = UIViewContentModeScaleAspectFill;
Play around with the various UIViewContentMode... : to achieve the best effect for you
The current one allows the PDF to be redrawn maintaining its Aspect ration and filling the entire size of the screen:so it cuts of about half the PDF - - -

Memory Leak when create a UIImage

I have following method to take a pdf file in documents directory and create a thumbnail out of it. This method is leaking memory in two places as shown in comments. Since I am using ARC I am not sure why is it leaking memory. How can I solve this.
+ (UIImage*)createPdfThumbnail:(NSString*)pdfFilePath {
NSURL *targetURL = [NSURL fileURLWithPath:pdfFilePath];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)targetURL); // 3.0% of memory leak
CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);//for the first page
CGRect aRect = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
UIGraphicsBeginImageContext(aRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, aRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, -(aRect.origin.x), -(aRect.origin.y));
CGContextSetGrayFillColor(context, 1.0, 1.0);
CGContextFillRect(context, aRect);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, aRect, 0, false);
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, page);
UIImage *thumbnail = UIGraphicsGetImageFromCurrentImageContext(); // 97% of memory leak
CGContextRestoreGState(context);
UIGraphicsEndImageContext();
CGPDFDocumentRelease(pdf);
return thumbnail;
}
EDIT:
-(void)fromJSON:(NSDictionary *)JSON{
[super fromJSON:JSON];
self.path = JSON[#"path"];
//Create and save thumbnail
if (self.parentSpecSheet != nil){
#autoreleasepool {
UIImage* thumbnail = [Utilities createPdfThumbnail:self.path];
Photo* thumbnailPhoto = [Photo addObject];
[thumbnailPhoto setDelta:#(0)];
[thumbnailPhoto setImage:thumbnail];
[thumbnailPhoto.file setDelta:#(0)];
self.parentSpecSheet.thumbnail = thumbnailPhoto;
}
}
}
Two thoughts:
I experience a significant leak from CGContextDrawPDFPage when I test your code in iOS5 (and if you search for "CGContextDrawPDFPage leak", you'll see tons of references to permutations of this problem). This appears to be a known problem.
I see no appreciable leak in iOS 6 from the above code, though.
If you're still seeing this leak in iOS 6, then I suspect the problem does not rest in the above code. Do you have any other leaks reported? I'd also suggest you confirm that the object that owns this thumbnail is successfully getting deallocated itself (e.g. log/breakpoint in its dealloc method).
Unfortunately, when you look at the leaks tool, it's reporting where the leaked object was instantiated, not where the leak took place. You might want to confirm that the owner of this thumbnail is not, somehow, maintaining a strong reference to it (e.g., the owner, itself, has a retain cycle, or something like that).

Fast screenshot ios

In my project I have to make a screenshot of the screen and apply blur to create the effect of frosted glass. Content can be moved under the glass and blured picture changed.
I'v used Accelerate.framework to speedup blurring, also i,v used OpenGL to draw CIImage directly to GLView.
Now I'm looking for a way to optimize getting screenshot of the screen.
I use this method to get screenshot of some area at the bottom of the screen:
CGSize size = CGSizeMake(rect.size.width, rect.size.height);
// get screenshot of self.view
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(nil, size.width, size.height, 8, 0, colorSpaceRef, kCGImageAlphaPremultipliedFirst);
CGContextClearRect(ctx, rect);
CGColorSpaceRelease(colorSpaceRef);
CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);
CGContextSetShouldAntialias(ctx, NO);
CGContextSetAllowsAntialiasing(ctx, NO);
CGContextTranslateCTM(ctx, 0.0, someView.frame.size.height);
CGContextScaleCTM(ctx, 1, -1);
//add mask
CGImageRef maskImage = [UIImage imageNamed:#"mask.png"].CGImage;
CGContextClipToMask(ctx, rect, maskImage);
[someView.layer renderInContext:ctx];
//get screenshot image
CGImageRef imageRef = CGBitmapContextCreateImage(ctx);
It works fine and fast if self.view has 1-2 subviews, but if there are several subviews (or it is tableview), then everything starts to slow down.
So i try to find a fast way to get pixels from some rect on screen. Maybe using a low-level API.
if just perform some animations , try this way , called -snapshotViewAfterScreenUpdates: or -resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets: method which are UIView provided , these method return UIView object without rendering into a bitmap image , so it is a more efficient .

Convert PDF to UIImageView

I've found some code which gives me a UIImage out of a PDF-File. It works, but I have two questions:
Is there a possibility to achieve a better quality of the UIImage? (See Screenshot)
I only see the first page in my UIImageView. Do I have to embed the file in a UIScrollView to be complete?
Or is it better to render just one page and use buttons to navigate through the pages?
P.S. I know that UIWebView can display PDF-Pages with some functionalities but I need it as a UIImage or at least in a UIView.
Bad quality Image:
Code:
-(UIImage *)image {
UIGraphicsBeginImageContext(CGSizeMake(280, 320));
CGContextRef context = UIGraphicsGetCurrentContext();
CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("ls.pdf"), NULL, NULL);
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
CGContextTranslateCTM(context, 0.0, 320);
CGContextScaleCTM(context, 1.0, -1.0);
CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 4);
CGContextSaveGState(context);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, CGRectMake(0, 0, 280, 320), 0, true);
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultingImage;
}
I know i'm a little late here, but i hope i can help someone else looking for an answer.
As to the questions asked:
I'm afraid the only way to achieve a better image quality is to render a bigger image, and letting the UIImageView resize it for you. I don't think you can set the resolution, but using a bigger image may be a good choice. It won't take too long for the page to render, and the image will have a better quality. PDF files are rendered on demand depending on the zoom level, that's why they seem to have "better quality".
As to rendering all the pages, you can get the number of pages in the document calling CGPDFDocumentGetNumberOfPages( pdf ) and using a simple for loop you can concat all the images generated in one single image. For displaying it, use the UIScrollVIew.
In my opinion, this approach is better than the above, but you should try to optimize it, for example rendering always the current, the previous and the next page. For nice scrolling transition effects, why not use a horizontal UIScrollView.
For more generic rendering code, i always do the rotation like this:
int rotation = CGPDFPageGetRotationAngle(page);
CGContextTranslateCTM(context, 0, imageSize.height);//moves up Height
CGContextScaleCTM(context, 1.0, -1.0);//flips horizontally down
CGContextRotateCTM(context, -rotation*M_PI/180);//rotates the pdf
CGRect placement = CGContextGetClipBoundingBox(context);//get the flip's placement
CGContextTranslateCTM(context, placement.origin.x, placement.origin.y);//moves the the correct place
//do all your drawings
CGContextDrawPDFPage(context, page);
//undo the rotations/scaling/translations
CGContextTranslateCTM(context, -placement.origin.x, -placement.origin.y);
CGContextRotateCTM(context, rotation*M_PI/180);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageSize.height);
Steipete already mentioned setting the white background:
CGContextSetRGBFillColor(context, 1, 1, 1, 1);
CGContextFillRect(context, CGRectMake(0, 0, imageSize.width, imageSize.height));
So the last thing to keep in mind is when exporting an image, set the quality to the maximum. For example:
UIImageJPEGRepresentation(image, 1);
What are you doing with the CGContextTranslateCTM(context, 0.0, 320); call?
You should extract the proper metrics form the pdf, with code like this:
cropBox = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
rotate = CGPDFPageGetRotationAngle(page);
Also, as you see, the pdf might has rotation info, so you need to use the CGContextTranslateCTM/CGContextRotateCTM/CGContextScaleCTM depending on the angle.
You also might wanna clip any content that is outside of the CropBox area, as pdf has various viewPorts that you usually don't wanna display (e.g. for printers so that seamless printing is possible) -> use CGContextClip.
Next, you're forgetting that the pdf reference defines a white background color. There are a lot of documents out there that don't define any background color at all - you'll get weird results if you don't draw a white background on your own --> CGContextSetRGBFillColor & CGContextFillRect.

Draw background image using CGContextDrawImage

I want to draw on a UIView that has a background image that I set using the code below:
- (void)setBackgroundImageFromData:(NSData *)imageData {
UIImage *image = [UIImage imageWithData:imageData];
int width = image.size.width;
int height = image.size.height;
CGSize size = CGSizeMake(width, height);
CGRect imageRect = CGRectMake(0, 0, width, height);
UIGraphicsBeginImageContext(size);
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(currentContext, 0, height);
CGContextScaleCTM(currentContext, 1.0, -1.0);
CGContextDrawImage(currentContext, imageRect, image.CGImage);
UIGraphicsEndImageContext();
}
The initial view is created using the code from Apple's GLPaint example. For the life of me, that background image is not shown. What am I missing?
Thanks!
You create a UIImage, and an imageRect successfully. You then begin a image context to draw the image to, draw the image to the context, and end the context. The problem is that you just allow the context to expire without doing anything to it.
In UIKit you don't push new visuals upwards, you wait until requested to draw. Internal mechanisms cache your images and use them to move things about and otherwise redraw the screen at the usual 60fps.
If this is a custom UIView subclass, then you probably want to keep hold of the UIImage and composite it as part of your drawRect:. You can set the contents of your UIView as having changed by calling setNeedsRedraw — you'll then be asked to redraw your contents at some point in the future.
If this isn't a custom subclass, then the easiest thing to do is to wrap this view in an outer view and add a UIImageView behind it, to which you can set the UIImage.

Resources