Draw Image to PDF - objective-c

I'd like to draw an full page image to a PDF but I've always had a hard time wrapping my head around CGContextRefs so I don't know what to make of the error.
Note, this is NOT iOS. I'm making a desktop application.
So far, I have this:
-(void) addImage:(NSURL*) url toPage:(size_t) page toPDF:(CGPDFDocumentRef) pdf
{
NSImage *image = [[NSImage alloc] initWithContentsOfURL:url];
image = [image imageScaledToFitSize:pageSize.size]; //From Matt Gemmell's Crop extensions category
[image lockFocus];
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
[image drawInRect:pageSize];
[image unlockFocus];
CGPDFPageRef pageRef = CGPDFDocumentGetPage(pdf, page);
CGPDFContextBeginPage(context, pageInformation);
CGContextDrawPDFPage(context, pageRef);
CGPDFContextEndPage(context);
}
However, I'm greeted with the error:
CGPDFContextEndPage: invalid context 0x61000017b540. This is a serious error. This application, or a library it uses, is using an invalid context and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update.
What is wrong with my contexts please?

You need to create and use a CGPDFContext. Different contexts are specific to different rendering processes / destinations so you need to choose the correct one. So look at using CGPDFContextCreateWithURL to create a PDF context to write the data to a file.

Related

NSImage drawInRect and NSView cacheDisplayInRect memory retained

Application that I am working process images. It's like user drops at max 4 images and app layout them based on user's selected template. One image might be added 2-3 times in final view.
Each image in layout is drawn in NSView (drawRect method of NSView using drawInRect method).Now final image (combined image by layouting all images) is created by saving NSView as Image and it all works very well.
Now problem that I am facing is memory is being retained by app once all processing is done. I have used instruments allocation and I don't see memory leaks but I see "Persistent bytes" are increasing continuously with each session of app and one user reported issue in GB's. Please see screenshot.
When I further investigated in Instruments I saw below code snaps of app that is causing memory retentions. All are related to ImageIO and coreImages. See below from instruments:
However this seems to be only problem with 10.10 and above system. Tested same version of the app in 10.9.x and memory usage remains with in 60MB. During session execution in app it goes to 200MB but once it's done it comes back to 50-60MB that usual for kind of app.
[_photoImage drawInRect: self.bounds fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
_photoImage = nil;
Above code I am using to draw image in NSView's drawRect method and code shown in image is being used to get NSView as Image.
Update: After my further investigation I found that it's CGImageSourceCreateWithData that is caching the TIFF data of NSImage. More ever I am using below code to crop the image and if i uncomment it memory consumption just works fine.
NSData *imgData = [imageToCrop TIFFRepresentation];
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imgData, NULL);
CGImageRef maskRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);
CGImageRef imageRef = CGImageCreateWithImageInRect(maskRef, rect);
NSImage *cropped = [[NSImage alloc] initWithCGImage: imageRef size:rect.size];
CGImageRelease(maskRef);
CGImageRelease(imageRef);
CFRelease(source);
//CFRelease( options );
imgData = nil;
I have also trying explicitly setting kCGImageSourceShouldCache to false (but it's by default false) but same results.
Please help to solve the memory retention issue.
Finally after lots of debugging it turns out that CGImageSourceCreateWithData is somewhere retaining TIFF data of NSImage. When I changed this line:
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imgData, NULL);
with
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)[NSURL fileURLWithPath:path], NULL);
everything just started working fine and app's memory usage was dropped from 300MB (for 6images) to 50-60MB and it's consistent behaviour now.
Apart from above changes it was still causing memory retention somewhere so to get rid of that, after all processing is done I cleared image of each layer to 'nil' and that works like charm. I was in impression that making parent as 'nil' would release images as well but that was not working.
Anyway if anyone seems issue with drawInRect or cacheDisplayInRect then make sure to clear out the image if not needed later on.
Update 2nd July 2016
I found that kCGImageSourceShouldCache is false by default in 32bit and true for 64bit. I was able to release memory with below code by setting it to false.
const void *keys[] = { kCGImageSourceShouldCache};
const void *values[] = { kCFBooleanFalse};
CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)[image TIFFRepresentation], optionsDictionary);
Hope it helps someone.

renderInContext on retina and non-retina devices

I am creating a PDF by taking a screenshot of a UIView, this is currently working great on the iPad3 with the retina display, but when testing on other devices with lower resolution screens I am having problems with text resolution.
Here is my code:
//start a new page with default size and info
//this can be changed later to include extra info.
UIGraphicsBeginPDFPage();
//render the view's layer into an image context
//the last option specifies scale. If 0, it uses the devices scale.
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 2.0);
CGContextRef context = UIGraphicsGetCurrentContext();
[view.layer renderInContext:context];
UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//render the screenshot into the pdf page CGContext
[screenShot drawInRect:view.bounds];
//close the pdf context (saves the pdf to the NSData object)
UIGraphicsEndPDFContext();
I have also tried to set the UIGraphicsBeginImageContextWithOptions scale to 2.0, but this gives no change. How can I force a view on an iPad2 to render at 2x resolution?
Expected output:
Actual output:
I ended up fixing this by recursively setting the contentScaleFactor property of the parent view and its subviews to 2.0.
The UIImage was rendering at the correct resolution, but the layer wasn't when renderInContext was being called.

How can I create a Quicktime movie from a series of generated images?

I need to create a movie from a series of generated images. (I'm creating the images based on the output of a physics modeling program.)
I found Apple's sample in QtKitCreateMovie and used that as a starting point. Instead of loading jpgs from the application bundle, I'm drawing to an NSImage and then adding that NSImage to the movie object. Here's the basic code I used for testing. mMovie is an instance of QTMovie:
NSImage *anImage = [[NSImage alloc] initWithSize:NSMakeSize(frameSize, frameSize)];
[anImage lockFocus];
float blendValue;
for (blendValue = 0.0; blendValue <= 1.0; blendValue += 0.05) {
[[[NSColor blueColor] blendedColorWithFraction:blendValue ofColor:[NSColor redColor]] setFill];
[NSBezierPath fillRect:NSMakeRect(0, 0, frameSize, frameSize)];
[mMovie addImage:anImage forDuration:duration withAttributes:myDict];
}
[anImage unlockFocus];
[anImage release];
This works under OS X 10.5, but under OS X 10.6 I get an array index beyond bounds exception on the call to addImage:forDuration:withAttributes: (http://openradar.appspot.com/radar?id=1146401)
What's the proper way to create a movie under 10.6?
Also, although this works under 10.5, I run out of memory if I try to create a movie with thousands of frames. That also makes me think I'm not using the correct approach.
You're doing it right, but you're doing it wrong.
The correct way hasn't changed in QTKit. Your mistake is that you're trying to add the image before you have finished it, which happens when you unlock focus. Since you don't unlock focus until after you try to add the image (20 times), you are trying to add an unfinished image (20 times), which doesn't work.
The “out of bounds” exception is because the image has no representations. QTMovie, it seems, is trying to loop through the array returned by the image in response to a representations message, but that array is empty because the image is not finished.
Somehow, you got away with this in Leopard (probably due to an implementation detail that changed in Snow Leopard), but I'd say it was no less your bug then.
The solution is simply to lock focus and unlock focus on the image each time through the loop:
float blendValue;
for (blendValue = 0.0; blendValue <= 1.0; blendValue += 0.05) {
[anImage lockFocus];
[[NSGraphicsContext currentContext] setShouldAntialias:NO];
[[[NSColor blueColor] blendedColorWithFraction:blendValue ofColor:[NSColor redColor]] setFill];
[NSBezierPath fillRect:NSMakeRect(0, 0, frameSize, frameSize)];
[anImage unlockFocus];
[mMovie addImage:anImage forDuration:duration withAttributes:myDict];
}

CGContextDrawPDFPage displays white or garbled text

In the process of updating my iPad app I've been attempting to draw a page from an existing PDF document into a Core Graphics context then save it as a new PDF, but am having difficulty getting the text to display properly. Images in the newly-created PDF look great, but text rarely appears correctly: more often that not it appears white/invisible or garbled. When the text is invisible, I am still able to to select where it -should- be and copy/paste correctly into a text editor. Is this an issue related to the limited number of fonts available on the iPad?
My code is as follows:
CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(dataProvider);
CGPDFPageRef page = CGPDFDocumentGetPage(document, pageNumberToRetrieve);
CGRect pageRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
UIGraphicsBeginPDFContextToFile(pathToFile, pageRect, nil);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPage(context, NULL);
// I don't think this line is necessary, but I have tried both with and without it.
CGContextSetTextDrawingMode (context, kCGTextFill);
CGContextDrawPDFPage(context, page);
CGContextEndPage(context);
UIGraphicsEndPDFContext();
CGDataProviderRelease(dataProvider);
CGPDFDocumentRelease(document);
If anyone has any suggestions I would greatly appreciate hearing them.
Thanks for your time.
Rob
Drawing into an image context does not pose a problem (text displays correctly).
What I am trying to do is create a -new- PDF file containing just a few pages from the original PDF. It seems that text does not draw correctly into the new file for some reason.
The information is there (I can select text by 'guessing' where it should be in Preview) but it doesn't render. I assume CGContextDrawPDFPage writes the string to the PDF file, but doesn't draw it because it doesn't know what the characters of that font 'look like'?
I thought the point of embedded fonts in PDFs was that programs would be able to perform these sorts of manipulations even if that font wasn't installed on the system (in this case, the iPad). Is this a limitation of the format, or the Quartz framework?
Do you want to render on the screen? I don't see the need for UIGraphicsBeginPDFContextToFile?
However, to render on the screen, you can use something like this:
pageReference = CGPDFDocumentGetPage(pdfReference, page);
CGContextRef context = UIGraphicsGetCurrentContext();
#try {
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, scale, -scale);
CGContextSaveGState(context);
#try {
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(pageReference, kCGPDFCropBox, self.bounds, 0, true);
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, pageReference);
}
#finally {
CGContextRestoreGState(context);
}
}
#finally {
UIGraphicsEndImageContext();
}

Using drawAtPoint with my CIImage not doing anything on screen

Stuck again. :(
I have the following code crammed into a procedure invoked when I click on a button on my application main window. I'm just trying to tweak a CIIMage and then display the results. At this point I'm not even worried about exactly where / how to display it. I'm just trying to slam it up on the window to make sure my Transform worked. This code seems to work down through the drawAtPoint message. But I never see anything on the screen. What's wrong? Thanks.
Also, as far as displaying it in a particular location on the window ... is the best technique to put a frame of some sort on the window, then get the coordinates of that frame and "draw into" that rectangle? Or use a specific control from IB? Or what? Thanks again.
// earlier I initialize a NSImage from JPG file on disk.
// then create NSBitmapImageRep from the NSImage. This all works fine.
// then ...
CIImage * inputCIimage = [[CIImage alloc] initWithBitmapImageRep:inputBitmap];
if (inputCIimage == Nil)
NSLog(#"could not create CI Image");
else {
NSLog (#"CI Image created. working on transform");
CIFilter *transform = [CIFilter filterWithName:#"CIAffineTransform"];
[transform setDefaults];
[transform setValue:inputCIimage forKey:#"inputImage"];
NSAffineTransform *affineTransform = [NSAffineTransform transform];
[affineTransform rotateByDegrees:3];
[transform setValue:affineTransform forKey:#"inputTransform"];
CIImage * myResult = [transform valueForKey:#"outputImage"];
if (myResult == Nil)
NSLog(#"Transformation failed");
else {
NSLog(#"Created transformation successfully ... now render it");
[myResult drawAtPoint: NSMakePoint ( 0,0 )
fromRect: NSMakeRect ( 0,0,128,128 )
operation: NSCompositeSourceOver
fraction: 1.0]; //100% opaque
[inputCIimage release];
}
}
Edit #1:
snip - removed the prior code sample mentioned below (in the comments about drawRect), which did not work
Edit #2: adding some code that DOES work, for anyone else in the future who might be stuck on this same thing. Not sure if this is the BEST way to do it ... but it does work for my quick and dirty purposes. So this new code (below) replaces the entire [myResult drawAtPoint ...] message from above / in my initial question. This code takes the image created by the CIImage transform and displays it in the NSImageView control.
NSImage *outputImage;
NSCIImageRep *ir;
ir = [NSCIImageRep imageRepWithCIImage:myResult];
outputImage = [[[NSImage alloc] initWithSize: NSMakeSize(inputImage.size.width, inputImage.size.height)] autorelease];
[outputImage addRepresentation:ir];
[outputImageView setImage: outputImage]; //outputImageView is an NSImageView control on my application's main window
Drawing on screen in Cocoa normally takes place inside an -[NSView drawRect:] override. I take it you're not doing that, so you don't have a correctly set up graphics context.
So one solution to this problem is to create a NSCIImageRep from the CIImage, then add that representation to a new NSImage, then it is easy to display the NSImage in a variety of ways. I've added the code I used up above (see "edit #2"), where I display the "output image" within an NSImageView control. Man ... what a PITA this was!