I'm trying to convert individual PDF pages into PNGs here, and it's worked perfectly until UIGraphicsGetCurrentContext suddenly started returning nil.
I'm trying to retrace my steps here, but I'm not quite sure that I know at which point this happened. My frame is not 0, which I see might create this problem, but other than that everything "looks" correct.
Here's the beginning of my code.
_pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)_pdfFileUrl);
CGPDFPageRef myPageRef = CGPDFDocumentGetPage(_pdf, pageNumber);
CGRect aRect = CGPDFPageGetBoxRect(myPageRef, kCGPDFCropBox);
CGRect bRect = CGRectMake(0, 0, height / (aRect.size.height / aRect.size.width), height);
UIGraphicsBeginImageContext(bRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
Anybody have any idea what else might be causing the nil context?
It doesn't have to be called from "drawRect".
you can also call it after "UIGraphicsBeginImageContext(bRect.size);"
Check in following line
UIGraphicsBeginImageContext(bRect.size);
if bRect.size is not 0,0
In my case, this was the reason why the returned context on the following line was null.
Are you calling UIGraphicsGetCurrentContext() inside of the drawRect method? As far as I know, it can only be called within drawRect, otherwise it will just return nil.
Indeed, it is possible to have CGContextRef object reusable after it has been set in drawRect method.
The point is - you need to push the Context to the stack before using it from anywhere. Otherwise, current context will be 0x0
1. Add
#interface RenderView : UIView {
CGContextRef visualContext;
BOOL renderFirst;
}
2. In your #implementation first set renderFirst to TRUE before view has appeared on the screen, then:
-(void) drawRect:(CGRect) rect {
if (renderFirst) {
visualContext = UIGraphicsGetCurrentContext();
renderFirst = FALSE;
}
}
3. Rendering Something to the context after the context was set.
-(void) renderSomethingToRect:(CGRect) rect {
UIGraphicsPushContext(visualContext);
// For instance
UIGraphicsPushContext(visualContext);
CGContextSetRGBFillColor(visualContext, 1.0, 1.0, 1.0, 1.0);
CGContextFillRect(visualContext, rect);
}
Here is an example exactly matching the thread case:
- (void) drawImage: (CGImageRef) img inRect: (CGRect) aRect {
UIGraphicsBeginImageContextWithOptions(aRect.size, NO, 0.0);
visualContext = UIGraphicsGetCurrentContext();
CGContextConcatCTM(visualContext, CGAffineTransformMakeTranslation(-aRect.origin.x, -aRect.origin.y));
CGContextClipToRect(visualContext, aRect);
CGContextDrawImage(visualContext, aRect, img);
// this can be used for drawing image on CALayer
self.layer.contents = (__bridge id) img;
[CATransaction flush];
UIGraphicsEndImageContext();
}
And Drawing image from context that was taken before in this post:
-(void) drawImageOnContext: (CGImageRef) someIm onPosition: (CGPoint) aPos {
UIGraphicsPushContext(visualContext);
CGContextDrawImage(visualContext, CGRectMake(aPos.x,
aPos.y, someIm.size.width,
someIm.size.height), someIm.CGImage);
}
Do not call UIGraphicsPopContext() function until you need the context to render your objects.
It seems that CGContextRef is being removed from the top of the graphic stack automatically when the calling method finishes.
Anyway, this example seems to be a kind of Hack - not planned and proposed by Apple. The solution is very unstable and works only with direct method messages calls inside only one UIView that is on the top of the screen. In case of "performselection" calls, Context does not render any results to the screen. So, I suggest to use CALayer as a rendering to the screen target instead of direct graphic context usage.
Hope it helps.
Related
I have a code block which aims to capture snapshot of pdf based custom views for each page. To accomplish it I'll create view controller in a loop and then iterate. The problem is even view controller released custom view doesn't released and look like live on Instruments tool. As a result for loop iterates a lot so it breaks the memory (up to 500MB for 42 living) and crashes.
Here is the iteration code;
do
{
__pageDictionary = CFDictionaryGetValue(_allPages,
__pageID);
CUIPageViewController *__pageViewController = [self _pageWithID:__pageID];
[__pageViewController addMainLayers];
[[APP_DELEGATE assetManager] temporarilyPasteSnapshotSource:__pageViewController.view];
UIImage *__snapshotImage = [__pageViewController captureSnapshot];
[[AOAssetManager sharedManager] saveImage:__snapshotImage
forPublicationBundle:_publicationTileViewController.publication.bundle
pageID:(__bridge NSString *)__pageID];
[[APP_DELEGATE assetManager] removePastedSnapshotSource:__pageViewController.view];
__snapshotImage = nil;
__pageViewController = nil;
ind += 6 * 0.1 / CFDictionaryGetCount(_allPages);
}
while (![(__bridge NSString *)(__pageID = CFDictionaryGetValue(__pageDictionary,
kMFMarkupKeyPageNextPageID)) isMemberOfClass:[NSNull class]]);
_generatingSnapshots = NO;
And here the captureSnapshot method;
- (UIImage *)captureSnapshot
{
CGRect rect = [self.view bounds];
UIGraphicsBeginImageContextWithOptions(rect.size,YES,0.0f);
context = UIGraphicsGetCurrentContext();
[self.view.layer renderInContext:context];
UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return capturedImage;
}
Instruments;
Edit for further details:
Below code is from CUIPDFView a subclass of UIView;
- (void)drawRect:(CGRect)rect
{
[self drawInContext:UIGraphicsGetCurrentContext()];
}
-(void)drawInContext:(CGContextRef)context
{
CGRect drawRect = CGRectMake(self.bounds.origin.x, self.bounds.origin.y,self.bounds.size.width, self.bounds.size.height);
CGContextSetRGBFillColor(context, 1.0000, 1.0000, 1.0000, 1.0f);
CGContextFillRect(context, drawRect);
// PDF page drawing expects a Lower-Left coordinate system, so we flip the coordinate system
// before we start drawing.
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Grab the first PDF page
CGPDFPageRef page = CGPDFDocumentGetPage(_pdfDocument, _pageNumberToUse);
// We're about to modify the context CTM to draw the PDF page where we want it, so save the graphics state in case we want to do more drawing
CGContextSaveGState(context);
// CGPDFPageGetDrawingTransform provides an easy way to get the transform for a PDF page. It will scale down to fit, including any
// base rotations necessary to display the PDF page correctly.
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, self.bounds, 0, true);
// And apply the transform.
CGContextConcatCTM(context, pdfTransform);
// Finally, we draw the page and restore the graphics state for further manipulations!
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
}
When I delete drawRect method implementation, memory allocation problem dismiss but obviously it can't print the pdf.
Try putting an #autoreleasepool inside your loop:
do
{
#autoreleasepool
{
__pageDictionary = CFDictionaryGetValue(_allPages,
__pageID);
CUIPageViewController *__pageViewController = [self _pageWithID:__pageID];
[__pageViewController addMainLayers];
[[APP_DELEGATE assetManager] temporarilyPasteSnapshotSource:__pageViewController.view];
UIImage *__snapshotImage = [__pageViewController captureSnapshot];
[[AOAssetManager sharedManager] saveImage:__snapshotImage
forPublicationBundle:_publicationTileViewController.publication.bundle
pageID:(__bridge NSString *)__pageID];
[[APP_DELEGATE assetManager] removePastedSnapshotSource:__pageViewController.view];
__snapshotImage = nil;
__pageViewController = nil;
ind += 6 * 0.1 / CFDictionaryGetCount(_allPages);
}
}
while (![(__bridge NSString *)(__pageID = CFDictionaryGetValue(__pageDictionary,
This will flush the autorelease pool each time through the loop, releasing all the autoreleased objects.
I would be surprised if this is actually a problem with ARC. An object that is still alive still has a strong reference to the layer.
What does [AOAssetManager saveImage:...] do with the image. Are you sure it is not holding onto it?
Is _pageWithID: doing something that is keeping a pointer to CUIPageViewController around?
I am new to these parts of iOS API and here are some questions that are causing an infinite loop in my mind
Why does ..BeginImageContext have a size but ..GetCurrentContext does not have a size? If ..GetCurrentContext does not have a size, where does it draw? What are the bounds?
Why did they have to have two contexts, one for image and one for general graphics? Isn't an image context already a graphic context? What was the reason for the separation (I am trying to know what I don't know)
UIGraphicsGetCurrentContext() returns a reference to the current graphics context. It doesn't create one. This is important to remember because if you view it in that light, you see that it doesn't need a size parameter because the current context is just the size the graphics context was created with.
UIGraphicsBeginImageContext(aSize) is for creating graphics contexts at the UIKit level outside of UIView's drawRect: method.
Here is where you would use them.
If you had a subclass of UIView you could override its drawRect: method like so:
- (void)drawRect:(CGRect)rect
{
//the graphics context was created for you by UIView
//you can now perform your custom drawing below
//this gets you the current graphic context
CGContextRef ctx = UIGraphicsGetCurrentContext();
//set the fill color to blue
CGContextSetFillColorWithColor(ctx, [UIColor blueColor].CGColor);
//fill your custom view with a blue rect
CGContextFillRect(ctx, rect);
}
In this case, you didn't need to create the graphics context. It was created for you automatically and allows you to perform your custom drawing in the drawRect: method.
Now, in another situation, you might want to perform some custom drawing outside of the drawRect: method. Here you would use UIGraphicsBeginImageContext(aSize)
You could do something like this:
UIBezierPath *circle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0, 0, 200, 200)];
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//this gets the graphic context
CGContextRef context = UIGraphicsGetCurrentContext();
//you can stroke and/or fill
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor lightGrayColor].CGColor);
[circle fill];
[circle stroke];
//now get the image from the context
UIImage *bezierImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *bezierImageView = [[UIImageView alloc]initWithImage:bezierImage];
I hope this helps to clear things up for you. Also, you should be using UIGraphicsBeginImageContextWithOptions(size, opaque, scale). For further explanation of custom drawing with graphics contexts, see my answer here
You are slightly confused here.
As the name suggests UIGraphicsGetCurrentContext grabs the CURRENT context, thus it doesn't need the size, it grabs an existing context and returns it to you.
So when is there an existing context? Always? No. When the screen is rendering a frame, a context is created. This context is available in the drawRect: function, which is called to draw the view.
Normally, your functions aren't called in drawRect:, so they don't actually have a context available. This is when you call UIGraphicsBeginImageContext.
When you do that, you create an image context, then you can grab said context with UIGraphicsGetCurrentContext and work with it. And thus, you have to remember to end it with UIGraphicsEndImageContext
To clear things up further - if you modify the context in drawRect:, your changes will be shown on screen. In your own function, your changes don't show up anywhere. You have to extract the image in the context through the UIGraphicsGetImageFromCurrentImageContext() call.
Hope this helps!
It looks like I might've potentially found an answer to one of my earlier problems and would be happy to post the solution on SO though I first need to confirm it works properly.
The problem is it seems to be - most of the time, though not always. I've isolated the problematic code - it's a method I created whose purpose is to return a UIImage of what is currently visible on the device's screen. It looks like this:
+ (UIImage *)getImageVisibleOnScreenWith: (CGRect) boundingRect rotationAngle: (CGFloat) angle scalingRatio: (CGFloat) scale entireImageView: (UIImageView *) imageView actualVisibleView: (UIView *) visibleView {
// Create a graphics context the size of the bounding rectangle
UIGraphicsBeginImageContext(boundingRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate and translate the context
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, boundingRect.size.width/2, boundingRect.size.height/2);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformScale(transform, scale, -scale);
CGContextConcatCTM(context, transform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(-imageView.image.size.width/2, -imageView.image.size.height/2, imageView.image.size.width, imageView.image.size.height), imageView.image.CGImage);
// Get an image from the context
UIImage *viewImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
// Clean up
UIGraphicsEndImageContext();
// Get the image currently on the screen (it's an intersection of specific UIImageViews)
CGRect visibleImageRect = CGRectIntersection(imageView.frame, visibleView.frame);
UIImage *visibleImage = (__bridge UIImage *)(CGImageCreateWithImageInRect((__bridge CGImageRef)(viewImage), visibleImageRect));
return visibleImage;
}
I pass on the result of this method to another one and noticed it sometimes returns nil - for no apparent reason, well at least I couldn't find any.
As usual, any ideas and help will be appreciated; also let me know if you need to see more code or if there's anything unclear as to what the purpose it is.
This question already exists:
Closed 10 years ago.
Possible Duplicate:
Drawing shape iOS
CGContextRef, CGPoint, and CGSize
I am trying to use a current method where I have to include the CGContextRef, CGPoint, and CGSize:
CGPoint p1 = {10, 10};
CGSize size;
CGContextRef context = UIGraphicsGetCurrentContext();
[self drawArrowWithContext:context atPoint:p1 withSize:size lineWidth:400 arrowHeight:400];
When I run the application I get this error:
Jan 21 21:41:56 Alexs-ipad Splash-it[1497] : CGContextDrawPath: invalid context 0x0
The problem must be in the context, but I can't find anywhere on the internet the solution to the problem. This whole code should call a method for drawing an arrow.
Thanks for any help.
In order to return a valid context you have to be in the appropriate area.
That basically means this code needs to be in drawRect: or you need to create an image context using UIGraphicsBeginImageContext
Update: the DrawRect:
The drawRect: is a special method called for each UIView that gives you an access point to do custom drawing using Core Graphics. The most common use for this is to create a custom UIView object in your case an ArrowView. Then in that you would override drawRect: using your code.
- (void)drawRect:(CGRect)rect
{
CGPoint p1 = {10, 10};
CGSize size;
CGContextRef context = UIGraphicsGetCurrentContext();
[self drawArrowWithContext:context atPoint:p1 withSize:size lineWidth:400 arrowHeight:400];
}
Update: the image context
A secondary way to tap into custom Core Graphics drawing is to create an imageContext then harvest its results.
So you'd start by creating an image context, running your drawing code, then converting that into an UIImage you can add to your existing views.
UIGraphicsBeginImageContext(CGSizeMake(400.0, 400.0));
CGPoint p1 = {10, 10};
CGSize size;
CGContextRef context = UIGraphicsGetCurrentContext();
[self drawArrowWithContext:context atPoint:p1 withSize:size lineWidth:400 arrowHeight:400];
// converts your context into a UIImage
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// Adds that image into an imageView and sticks it on the screen.
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:imageView];
I need to draw a line chart from values that come to me every half a seconds. I've come up with my custom CALayer for this graph which stores all the previous lines and every two seconds redraws all previous lines and adds one new line. I find this solution non-optimal because there's only need to draw one additional line to the layer, no reason to redraw potentially thousands of previous lines.
What do you think would be the best solution in this case?
Use your own NSBitmapContext or UIImage as a backing store. Whenever new data comes in draw to this context and set your layer's contents property to the context's image.
I am looking at an identical implementation. Graph updates every 500 ms. Similarly I felt uncomfortable drawing the entire graph each iteration. I implemented a solution 'similar' to what Nikolai Ruhe proposed as follows:
First some declarations:
#define TIME_INCREMENT 10
#property (nonatomic) UIImage *lastSnapshotOfPlot;
and then the drawLayer:inContext method of my CALayer delegate
- (void) drawLayer:( CALayer*)layer inContext:(CGContextRef)ctx
{
// Restore the image of the layer from the last time through, if it exists
if( self.lastSnapshotOfPlot )
{
// For some reason the image is being redrawn upside down!
// This block of code adjusts the context to correct it.
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, 0, layer.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
// Now we can redraw the image right side up but shifted over a little bit
// to allow space for the new data
CGRect r = CGRectMake( -TIME_INCREMENT, 0, layer.bounds.size.width, layer.bounds.size.height );
CGContextDrawImage(ctx, r, self.lastSnapshotOfPlot.CGImage );
// And finally put the context back the way it was
CGContextRestoreGState(ctx);
}
CGContextStrokePath(ctx);
CGContextSetLineWidth(ctx, 2.0);
CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor );
CGContextBeginPath( ctx );
// This next section is where I draw the line segment on the extreme right end
// which matches up with the stored graph on the image. This part of the code
// is application specific and I have only left it here for
// conceptual reference. Basically I draw a tiny line segment
// from the last value to the new value at the extreme right end of the graph.
CGFloat ppy = layer.bounds.size.height - _lastValue / _displayRange * layer.bounds.size.height;
CGFloat cpy = layer.bounds.size.height - self.sensorData.currentvalue / _displayRange * layer.bounds.size.height;
CGContextMoveToPoint(ctx,layer.bounds.size.width - TIME_INCREMENT, ppy ); // Move to the previous point
CGContextAddLineToPoint(ctx, layer.bounds.size.width, cpy ); // Draw to the latest point
CGContextStrokePath(ctx);
// Finally save the entire current layer to an image. This will include our latest
// drawn line segment
UIGraphicsBeginImageContext(layer.bounds.size);
[layer renderInContext: UIGraphicsGetCurrentContext()];
self.lastSnapshotOfPlot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Is this the most efficient way?
I have not been programming in ObjectiveC long enough to know so all suggestions/improvements welcome.