speeding up drawing of an OverlayView - objective-c

I'm drawing an Image (2.5 MB, PNG image data, 1240 x 1240, 8-bit/color RGBA, non-interlaced) on a MKMapView by Subclassing MKPolygonView and overriding the drawMapRect:zoomScale:inContext:-method as follows:
-(void) drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGRect theRect = [self rectForMapRect:overlayRect];
CGRect clipRect = [self rectForMapRect:mapRect];
CGContextClipToRect(context, clipRect);
CGContextDrawImage(context, theRect, imageReference);
}
overlayRect is an MKMapRect defining the position and size of the image on the map (hard coded and initialized in initWithOverlay:)
imageReference holds a reference to the image, loaded in an UIImage and obtained by calling CGImage on the UIImage instance (also in initWithOverlay:)
My MKMapView takes between 8-14 seconds to draw the image on the map the first time, taking again about the same time when zooming in to redraw the tiles with better resolution.
That seems really long and i'm wondering if I'm doing anything fundamentally wrong as it is my first time with MapKit.

You can use tile images to improve the performance. Apple used this technique in the WWDC 2010. You can see the example here: https://github.com/klokantech/Apple-WWDC10-TileMap.
Also in this blog explain the process: http://shawnsbits.com/blog/2010/12/23/mapkit-overlays-session-1-overlay-map/

Related

-(void) drawRect:(CGRect)rect; is using up nearly all of iPhone CPU

- (void)drawRect:(CGRect)rect{
float sliceSize = rect.size.width / imagesShownAtOnce;
//Apply our clipping region and fill it with black
[clippingRegion addClip];
[clippingRegion fill];
//Draw the 3 images (+1 for inbetween), with our scroll amount.
CGPoint loc;
for (int i=0;i<imagesShownAtOnce+1;i++){
loc = CGPointMake(rect.origin.x+(i*sliceSize)-imageScroll, rect.origin.y);
[[buttonImages objectAtIndex:i] drawAtPoint:loc];
}
//Draw the text region background
[[UIColor blackColor] setFill];
[textRegion fillWithBlendMode:kCGBlendModeNormal alpha:0.4f];
//Draw the actual text.
CGRect textRectangle = CGRectMake(rect.origin.x+16,rect.origin.y+rect.size.height*4/5.6,rect.size.width/1.5,rect.size.height/3);
[[UIColor whiteColor] setFill];
[buttonText drawInRect:textRectangle withFont:[UIFont fontWithName:#"Avenir-HeavyOblique" size:22]];
}
clippingRegion and textRegion are UIBezierPaths to give me the rounded rectangles I want (First for a clipping region, 2nd as an overlay for my text)
The middle section is drawing 3 images and letting them scroll along, which im updating every 2 refreshes from a CADisplayLink, and that invalidates the draw region by calling [self setNeedsDisplay], and also increasing my imageScroll variable.
Now that that background information is done, here is my issue:
It runs, and even runs smoothly. But it is using up an absolutely high amount of CPU time (80%+)!! How do I push this off to the GPU on the phone instead? Someone told me about CALayers but I've never dealt with them before
Draw each component of your drawing once into something (a view or layer) and let it hold the cached the drawing. Then you just move or transform each component, and exactly as you say, it's all done by the GPU.
You could do this with individual views or with individual layers, but that doesn't really matter (a view is a layer, under the hood). The point is that there is no need to be constantly redrawing from scratch when all you really want is to move the same persistent pieces around.
Learning about CALayer would be a good idea, as it is in fact the basis of all drawing on iOS. What could be more important to know about than that?

Problems scaling a UIImage to fit a UIButton

I have a set of buttons and different sized images. I want to scale each image in order that it fits the button in the correct aspect ratio. Once I've scaled the image, I set the button's image property to the scaled version.
UIImage *scaledImage = [image scaledForButton:pickerButton];
[pickerButton setImage:scaledImage forState:UIControlStateNormal];
my scaledForButton method is defined in a class extension for UIImage. It looks like this:
- (UIImage *)scaledForButton:(UIButton *)button
{
// Check which dimension (width or height) to pay respect to and
// calculate the scale factor
CGFloat imageRatio = self.size.width / self.size.height;
CGFloat buttonRatio = button.frame.size.width / button.frame.size.height;
CGFloat scaleFactor = (imageRatio > buttonRatio ? self.size.width/button.frame.size.width : self.size.height/button.frame.size.height);
// Create image using scale factor
UIImage *scaledimage = [UIImage imageWithCGImage:[self CGImage]
scale:scaleFactor
orientation:UIImageOrientationUp];
return scaledimage;
}
When I run this on an iPad2 it works fine and the images are scaled correctly. However if I run it on a retina display (both in the simulator and on a device) the image does not scale correctly and is squished into the button.
Any ideas why this would happen on retina only? I've been scratching my head for a couple of days but can't figure it out. They're both running the same iOS and I've checked the scale and ratio outputs, which are always the same, regardless of device. Many thanks.
Found the answer here: UIButton doesn't listen to content mode setting?
If you're setting the .contentMode, it seems you have to set the imageView property of the UIButton, not just the UIButton, then it worked properly.
The problem on iPad 3 was as Herman suggested - the CGImage was still a lot larger than the UIButton, so even though it was scaled down, it still had to be resized to fit the button.

Blending two images and drawing resized image from two UIImageViews

I have two ImageViews, one called imageView and the other called subView (which is a subview of imageView).
I want to blend the images on these views together, with the user being able to switch the alpha of the blend with a pan. My code works, but right now, the code is slow as we are redrawing the image each time the pan gesture is moved. Is there a faster/more efficient way of doing this?
BONUS Q: I want to allow for my subView image to drawn zoomed in. Currently I've set my subView to be UIViewContentModeCenter, however I can't seem to draw a zoomed in part of my image with this content mode. Is there any way around this?
My drawrect:
- (void)drawRect:(CGRect)rect
{
float xCenter = self.center.x - self.currentImage1.size.width/2.0;
float yCenter = self.center.y - self.currentImage1.size.height/2.0;
subView.alpha = self.blendAmount; // Customize the opacity of the top image.
UIGraphicsBeginImageContext(self.currentImage1.size);
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(c, kCGBlendModeColorBurn);
[imageView.layer renderInContext:c];
self.blendedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.blendedImage drawAtPoint:CGPointMake(xCenter,yCenter)];
}
You need to use GPU for image processing which is far faster than using CPU (as you're doing right now).
You can use Core Image framework which is very fast and easy to use but requires iOS 5, or you can use Open GL directly but you need to be experienced and have some knowledge about Open GL Shading.

Zooming image after rotation

I have an Image inside UIScrollView which i can zoom in and out.
I have a button that let the user rotate the Image 90 degrees:
(void)RotateImage {
CGAffineTransform rotateTrans = CGAffineTransformMakeRotation(-90.0 / 180.0 * 3.14);
BaseImg.transform = rotateTrans;
}
After the imaged is rotated i cannot zoom in and out.. the image is going crazy on the screen and going back to the UNRotated state.
What am i doing wrong? code examples will be great!
Thanks :)
UIScrollView likes to take over the transforms of the views it contains. There are two solutions:
Rotate the image without changing the containing view.
Create a UIView subclass that displays an image within a sublayer.
To rotate the image, see How to Rotate a UIImage 90 degrees?. If you're always and only doing 90 degree rotation, see #Peter Sarnowski's solution. To adapt it to what you're doing here, assuming that BaseImg is a UIImageView:
- (void) rotateImage
{
UIImage *sourceImage = [baseImg image];
UIImage *rotatedImage = [UIImage imageWithCGImage:[sourceImage CGImage] scale:1.0 orientation:UIImageOrientationRight];
[baseImg setImage:rotatedImage];
}
This will only rotate once. To have rotateImage work repeatedly, read the existing orientation property and move it on to the next in clockwise or anticlockwise order.
If the image is not square, you may also need to resize baseImg to reflect its new aspect ratio.
To create a UIView subclass, you need to have it store a CALayer as a sublayer of the view layer. Store the image in the sublayer, and transform the sublayer at will. This is faster, and allows arbitrary rotation, but you need to calculate your own scaling to prevent the rotate image going outside the view bounds.
To simply rotate image the code is perfect but to add zoom in and out and then add rotation you need to retain its transforms.Here is a sample code that can help you.
https://github.com/elc/iCodeBlogDemoPhotoBoard

Blending a UITableViewCell with a background

I have a dilemma I can't seem to solve. I have a UITableView populated by custom UITableViewCells and I need the background of each cell to be blended (like Photoshop multiply) with the background image (the background image is part of the UIView behind the UITableView).
I've found several ways to do the blend (and it does somewhat work). The blending function I'm using is:
- (UIImage *)blendOverlay:(UIImage *)topImage withBaseImage:(UIImage *)baseImage toSize:(CGFloat)imageSize
{
UIGraphicsBeginImageContext(CGSizeMake(imageSize, imageSize));
[baseImage drawInRect:CGRectMake(0.0, 0.0, imageSize, imageSize)];
[topImage drawInRect:CGRectMake(0.0, 0.0, imageSize, imageSize) blendMode:kCGBlendModeMulitply alpha:0.5];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
which is from http://www.cocoabuilder.com/archive/cocoa/280142-uiimageview-display-as-multiply.html.
Basically what I do now is chop out the piece of the background I'm over by getting the cell's frame and creating a new image which is the piece of the background in that frame. However, in order for the effect to work, it essentially needs to recalculate and update every frame (much like an alpha effect). Oh, and I tried just setting a timer to 1/60 seconds and calling
[tableView reloadData];
But that just slowed everything down and the redraws never actually occurred. Any help is much appreciated, I've been googling for nearly 3 hours to avail.
Oh, and for posterity purposes, I chop the background image like so:
CGRect r = [tableView rectForRowAtIndexPath:indexPath];
CGRect r2 = [tableView convertRect:r toView:self.view];
CGImageRef imageRef = CGImageCreateWithImageInRect([m_background.image CGImage], r2);
// Get the image by calling [UIImage imageWithCGImage:imageRef]
CGImageRelease(imageRef);
Thanks!
The problem is that a UITableView doesn't call the drawRect: method of the each individual UITabelViewCell while moving the rows. It seems to cache the view at the start of the table movement, and use that image for moving the whole table. Therefore the custom UITableViewCell has no opportunity to update the background image to show the movement of the blended backgrounds.
The one possible way I can see to animate the background image blending as the table cells move is to subclass UITableView and override drawRect: in that subclass.