I'm new to Quartz2d so I hope this is an easy question to answer.
I have a scrollview whose content frame is 1024x768. Within this I push a custom view (with frame 1024x768) that uses Quartz2d to do some drawing. The idea is, I have a large area that the user can scroll around to see different parts.
The problem I'm having is, when I scroll on the device I get memory warning errors or terrible scrolling performance. This is no doubt because I am re-drawing the entire 1024x768 field. I thought that setting the clipping path to the UIScrollView offset would help.. and it does.. until i scroll.. in which case the view does not redraw until scrolling stops. telling the view to draw during scrollViewDidScroll also results in bad performance
can anyone tell me how i can optimize my drawing routine?
i have two images.. one of which is getting masked, and both being painted to the screen (one on top of the other)
- (void)drawRect:(CGRect)dirtyRect {
CGContextRef myContext = UIGraphicsGetCurrentContext();
// get our offset (where we've scrolled to)
CGPoint offset = scrollView.contentOffset;
// clip to our offset.. uncomment for good performance but terrible scrolling
//CGContextClipToRect(myContext, CGRectMake(offset.x, offset.y, 480, 320));
// make an image from the mask context
CGImageRef aMaskImage = CGBitmapContextCreateImage (myMaskContext);
// mask the first image
CGImageRef aImage = CGImageCreateWithMask(myImageRef1, aMaskImage);
CGContextDrawImage(myContext, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), myImageRef1);
CGContextDrawImage(myContext, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), aImage);
CGImageRelease(aImage);
CGImageRelease(aMaskImage);
}
thanks in advance for any help you can provide!
Generally to optimize scrolling big stuff you'd look into CATiledLayer.
I assume somewhere you call setNeedsDisplay. You need to call setNeedsDisplayInRect. Make sure to only pass the rect that changed.
Related
Using Core Graphics, I want the painting app functionality and here the user can have the custom image pattern drawing functionality.I followed the proceeding snippet :
enter code here
UIGraphicsBeginImageContext(self.view.frame.size);
[drawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:1.0f];
[T_BImage drawAtPoint:CGPointMake(rotX-(T_BImage.size.width)/2, rotY-(T_BImage.size.height)/2) blendMode:kCGBlendModeNormal alpha:1.0f];
drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Here , is the output.
But ,when the user continuously drags fast on the screen , then the image is not sequentially drawn whereas the same code works fine with slow drag
Any help will be appreciated.
The problem with your code is as the drag speeds up, there will be continuous calls to the same method which might be rendering this slow. If you are planning to paint the pattern image, I suggest you try and look into the apple sample code here
You will have to replace the pattern image in the above sample project with your custom pattern image. See how it goes. One point to be noted is that the pattern image has to be square shaped(same width and height)
Finally, the image is drawn with the direct proportion of user's drawing speed. This was achieved by calculating the smallest set of points between touches_began and touches_moved delegate methods.i.e. All the set of possible points from minimum (starting) CGPoint to Maximum(Inbetween moving or end)point was manually calibrated and those points are passed through the image drawing function concurrently with separate thread.
Thanks to Brad-Larson for his remarkable .
response
I'm well aware that there's no nice way to render a small part of a UIView to an image, besides rendering the whole view and cropping. (VERY expensive on something like an iPad 3, where the resulting image is huge). See here for a description of the renderInContext method (there's no alternatives).
The part of the view that I want to render can't be predetermined, meaning I can't set up the view hierarchy for the small section to be it's own UIView (and therefore CALayer).
My Idea...
I had an idea, but I need some direction if I'm going to succeed. What if I create a category on UIView (or CALayer) that adds a method along the lines of:
[UIView renderSubframe:(CGFrame)frame];
How? Well, I'm thinking that if a dummy view the size of the sub frame was created, then the view could be shifted temporarily onto this. Then the dummy view's layer could call renderInContext:, resulting in an efficient and fast way of getting views.
So...
I'm really not that up to speed with CALayer/Quartz core stuff... Will this have any chance of working? If not, what's another way I could achieve the same thing - what's the most efficient way I could achieve the same result (or has anyone else already faced this problem and come up with a different solution)?
Here's the category I ended up writing. Not as hard as I first thought, thanks to a handy CGAffineTransform.
#import "UIView+RenderSubframe.h"
#import <QuartzCore/QuartzCore.h>
#implementation UIView (RenderSubframe)
- (UIImage *) renderWithBounds:(CGRect)frame {
CGSize imageSize = frame.size;
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0);
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextConcatCTM(c, CGAffineTransformMakeTranslation(-frame.origin.x, -frame.origin.y));
[self.layer renderInContext:c];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return screenshot;
}
#end
A couple of different ways you might do this:
specify a CGContextClipToRect for your context before calling renderInContext;
use:
setNeedsDisplayInRect:
Marks the region within the specified rectangle as needing to be updated.
- (void)setNeedsDisplayInRect:(CGRect)theRect
This would make the rendering happen only in the specified rect. I am not sure though whether this would work as per your requirement.
I think that the first option should work seamlessly.
I'm in the process of making a simple drawing program for the iPhone. At the moment, touch events add their location to a list which is connected with a bunch of CGContextAddLineToPoint calls which are redrawn in every call to drawRect.
I'm running into performance issues at fairly low numbers of lines (they are transparent, though), so I tried drawing everything into a CGLayer. Now, instead of drawing each line every frame, it draws each line only once and draws the CGLayer to the screen every frame.
CGContextRef backgroundContext = CGLayerGetContext(pathBuffer);
CGContextSetLineWidth(backgroundContext, 2.0);
CGContextSetLineCap(backgroundContext, kCGLineCapButt);
CGContextSetStrokeColorWithColor(backgroundContext, [pathColor CGColor]);
if([[touchPoints objectAtIndex:0] continuesToNextPoint])
{
CGContextMoveToPoint(backgroundContext, [[touchPoints objectAtIndex:0] point].x, [[touchPoints objectAtIndex:0] point].y);
CGContextAddLineToPoint(backgroundContext, [[touchPoints objectAtIndex:1] point].x, [[touchPoints objectAtIndex:1] point].y);
CGContextStrokePath(backgroundContext);
}
//remove it from touchpoints
[touchPoints removeObjectAtIndex:0];
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawLayerAtPoint(context, CGPointMake(0, 0), pathBuffer);
This, if anything, is SLOWER than what I was doing before. Am I doing something wrong? Would I be better served drawing to a UIImage or something? Thanks.
What you should be doing is inserting a CALayer into one of your views, updating the layer, and then calling setNeedsDisplayInRect: with the bounds of the area that has changed on the layer, which causes it to update. If you try to update the entire screen, it's going to be slow.
Are you using Instruments to figure out what parts are slow? I recommend using it to learn about what parts are slowing you down.
I am making an iPhone application that loads an image from the camera, and then the user can select a second image from the library, move/scale/rotate that second image, and then save the result. I use two UIImageViews in IB as placeholders, and then apply transformations while touching/pinching.
The problem comes when I have to save both images together. I use a rect of the size of the first image and pass it to UIGraphicsBeginImageContext. Then I tried to use CGContextConcatCTM but I can't understand how it works:
CGRect rect = CGRectMake(0, 0, img1.size.width, img1.size.height); // img1 from camera
UIGraphicsBeginImageContext(rect.size); // Start drawing
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextClearRect(ctx, rect); // Clear whole thing
[img1 drawAtPoint:CGPointZero]; // Draw background image at 0,0
CGContextConcatCTM(ctx, img2.transform); // Apply the transformations of the 2nd image
But what do I need to do next? What information is being held in the img2.transform matrix? The documentation for CGContextConcatCTM doesn't help me that much unfortunately..
Right now I'm trying to solve it by calculating the points and the angle using trigonometry (with the help of this answer), but since the transformation is there, there has to be an easier and more elgant way to do this, right?
Take a look at this excellent answer, you need to create a bitmapped/image context, draw to it, and get the resultant image out. You can then save that. iOS UIImagePickerController result image orientation after upload
Can anyone recommend how I might be able to create a view transition that looks like a circle expanding from the center of the view?
Any pointers would be helpful, or maybe a link to some similar code, even in a different language.
If you want to do that for images.. then you could use a mask and then make it bigger
Here is how you do it for images
CGRect bounds = CGRectMake(0.0f, 0.0f, 100, 100);
// Create a new path
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
// Add circle to path
CGPathAddEllipseInRect(path, NULL, bounds);
CGContextAddPath(context, path);
// Clip to the circle and draw the image
CGContextClip(context);
[image drawInRect:bounds]; //HOW WOULD YOU CLIP A VIEW INSTEAD OF AN IMAGE?
CFRelease(path);
Perhaps fill the screen with black, and then remove the black using a EllipseInRect that starts at the center point of the animation, and slowly increases in size and then deletes another segment, which loops until it's completed or hits an upper limit.
The smoothness of the animation would be controlled by how much the size of the Rect (which the Ellipse is then created inside of) increases every x amount of time.
The speed of the animation would be controlled by how often you increase the size of the rect.
Check out CoreGraphics if you haven't already. It's pretty powerful.