How to render UIView into 2 parts - iphone

I am trying to develop transition effect, when one view splits into 2 parts with upper part animated upwards and lower part animated downwards to revel the view behind it.
I am using UIView and UIImageView approach to accomplish that:
// 1. Make a screenshot:
UIGraphicsBeginImageContext(parentView.frame.size);
[parentView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 2. Calculate rectangles for top and bottom part:
CGRect rectTop, rectBottom;
CGFloat W = rectBig.size.width;
CGFloat H = rectBig.size.height;
rectTop = CGRectMake(0, 0, W, y_cutoff);
rectBottom = CGRectMake(0, y_cutoff, W, H - y_cutoff);
// 3. Create top and bottom images:
CGImageRef imageRefTop = CGImageCreateWithImageInRect([screenshot CGImage], rectTop);
CGImageRef imageRefBottom = CGImageCreateWithImageInRect([screenshot CGImage], rectBottom);
UIImage *imageTop = [UIImage imageWithCGImage:imageRefTop];
UIImage *imageBottom = [UIImage imageWithCGImage:imageRefBottom];
// 4. Assign images to image views:
imageViewTop.image = imageTop;
imageViewBottom.image = imageBottom;
// 5. Animate image views:
[UIView beginAnimation:nil context:NULL];
.... animation code here
[UIView commitAnimations];
This code however, is extremely slow on the device and I am sure there is a more efficient way to implement such a transition. Most probably using CALayers, etc..
Can you point me to the right direction?

This is not animation. Your drawing code is slow. If you profile, you will see that renderInContext: (btw. always executed on the main thread) and CGImageCreateWithImageInRect are the limiting factors. What your view (the one you want to split) is about? Is it an option to create two views instead of one from start?

The animation itself shouldn't be slow. All you really need to be doing is changing the Y position of the two views for the animation.
Why don't you try putting static images into the UIImageViews and seeing how the animation performs then?
If the lag is happening when you are creating the images, maybe you should consider moving that code to a separate thread so that the main thread doesn't freeze up. When the images are done being created, notify the main thread, put them in the UIImageViews, and perform the animation.

Related

Set the size of Main View according to its subView

I have a Main View with frame
0, 60, 768, 1024,
It has many views as subView. First subview is an UIImageView. The property associated with ImageView is AspectFit. Now I want to take screenshot of the only part of screen which bounds that of UIImageView (There are at least 4-5 Subviews which CAN or can not be above This UIImageView) The standard coding given by iPhone has been used here. The image i get is of Main View. Now to get only screenshot of UIImageView and whatever above is, I need to set the dimension of my main view to envelop only UIImageView.
Suppose The image size is 100,100. When I press button, How should I set the Bounds/fRAME OF my mainView? Or In other terms how can I get the frame of UIImageView relative to self.view
Instead of changing the frame of main view, try to calculate coordinates of the UIImageView.
Assume that frames of your main view and image view are (0, 60, 768, 1024) and (20, 20, 100, 100), respectively.
So frame of your image view with respect to global coordinate system is (20, 80, 100, 100). Just add x, y coordinates of main view to its subviews to achieve this. Then, you may get the screenshot of desired frame.
One way would be to grab the entire view's image, then crop it. No reframing needed. Combining best answers from here and here, it might look like this:
- (UIImage *)imageWithView:(UIView *)view croppedBy:(CGRect)rect {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], rect);
UIImage *result = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
return result;
}
For your app, call it like this (assuming we're in a view controller):
UIImage *justMyImageView = [self imageWithView:self.view croppedBy:self.myImageView.frame];

Rendering views, scaled #2x, renderInContext, iPhone

I have a view (called outPutView) that contains graphics, like uIImageViews and labels. I need to render an image of the outPutView and it's sub-views. I am using renderInContext:UIGraphicsGetCurrentContext() to do so. Works fine, except that I need to scale the views. I am using a transform on the outPutView. This successfully scales the view and it's sub-views, but the transform does not render. While the views are scaled onscreen. the final render displays the vies at their original size, while the render context is at the target size (here #2x iPhone view size).
Thanks for reading!!
[outPutView setTransform:CGAffineTransformMake(2, 0, 0, 2, 0, 0)];
CGSize renderSize = CGSizeMake(self.view.bounds.size.width*2, self.view.bounds.size.height*2);
UIGraphicsBeginImageContext(renderSize);
[[outPutView layer] renderInContext:UIGraphicsGetCurrentContext()];
renderedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I've just made this work, although in the opposite direction (scaling down). Here's a summary of the relevant code:
// destination size is half of self (self is a UIView)
float rescale = 0.5;
CGSize resize = CGSizeMake(self.width * rescale, self.height * rescale);
// make the destination context
UIGraphicsBeginImageContextWithOptions(resize, YES, 0);
// apply the scale to dest context
CGContextScaleCTM(UIGraphicsGetCurrentContext(), rescale, rescale);
// render self into dest context
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
// grab the resulting UIImage
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
To scale up instead of down, it should be fine to have rescale = 2.
I solved this by re-ordering my views. Actually adding another view between the output view: the view that the rendered context is taken from, and the view that is scaled via transform. It worked, but I have no idea why at this point. Any thoughts on this would be appreciated. Thanks for reading.

Drawrect with CGBitmapContext is too slow

So I've got a basic drawing app in the process that allows me to draw lines. I draw to an off screen bitmap then present the image in drawRect. It works but its way too slow, updating about half a second after you've drawn it with your finger. I took the code and adapted it from this tutorial, http://www.youtube.com/watch?v=UfWeMIL-Nu8&feature=relmfu , as you can see in the comments people are also saying its too slow but the guy hasn't responded.
So how can I speed it up? or is there a better way to do it? any pointers will be appreciated.
Heres the code in my DrawView.m.
-(id)initWithCoder:(NSCoder *)aDecoder {
if ((self=[super initWithCoder:aDecoder])) {
[self setUpBuffer];
}
return self;
}
-(void)setUpBuffer {
CGContextRelease(offscreenBuffer);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
offscreenBuffer = CGBitmapContextCreate(NULL, self.bounds.size.width, self.bounds.size.height, 8, self.bounds.size.width*4, colorSpace, kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
CGContextTranslateCTM(offscreenBuffer, 0, self.bounds.size.height);
CGContextScaleCTM(offscreenBuffer, 1.0, -1.0);
}
-(void)drawToBuffer:(CGPoint)coordA :(CGPoint)coordB :(UIColor *)penColor :(int)thickness {
CGContextBeginPath(offscreenBuffer);
CGContextMoveToPoint(offscreenBuffer, coordA.x,coordA.y);
CGContextAddLineToPoint(offscreenBuffer, coordB.x,coordB.y);
CGContextSetLineWidth(offscreenBuffer, thickness);
CGContextSetLineCap(offscreenBuffer, kCGLineCapRound);
CGContextSetStrokeColorWithColor(offscreenBuffer, [penColor CGColor]);
CGContextStrokePath(offscreenBuffer);
}
- (void)drawRect:(CGRect)rect {
CGImageRef cgImage = CGBitmapContextCreateImage(offscreenBuffer);
UIImage *image =[[UIImage alloc] initWithCGImage:cgImage];
CGImageRelease(cgImage);
[image drawInRect:self.bounds];
}
Works perfectly on the simulator but not device, I imagine that's something to do with processor speed.
I'm using ARC.
I tried to fix your code, however as you only seem to have posted half of it I couldn't get it working (Copy+pasting code results in lots of errors, let alone start performance tuning it).
However there are some tips you can use to VASTLY improve performance.
The first, and probably most noticeably, is -setNeedsDisplayInRect: rather then -setNeedsDisplay. This will mean that it only redraws the little rect that changed. For an iPad 3 with 1024*768*4 pixels that is a lot of work. Reducing that down to about 20*20 or less for each frame will massively improve performance.
CGRect rect;
rect.origin.x = minimum(coordA.x, coordB.x) - (thickness * 0.5);
rect.size.width = (maximum(coordA.x, coordB.x) + (thickness * 0.5)) - rect.origin.x;
rect.origin.y = minimum(coordA.y, coordB.y) - (thickness * 0.5);
rect.size.height = (maximum(coordA.y, coordB.y) + (thickness * 0.5)) - rect.origin.y;
[self setNeedsDisplayInRect:rect];
Another big improvement you could make is to only draw the CGPath for this current touch (which you do). However you then draw that saved/cached image in the draw rect. So, again, it is redrawn each frame. A better approach is to have the draw view being transparent and then to use a UIImageView behind that. UIImageView is the best way to display images on iOS.
- DrawView (1 finger)
-drawRect:
- BackgroundView (the image of the old touches)
-self.image
The draw view would itself then only ever draw the current touch only the part that changes each time. When the user lifts their finger you can cache that to a UIImage, draw that over the current background/cache UIImageView's image and set the imageView.image to the new image.
That final bit when combining the images involves drawing 2 full screen images into an off screen CGContext and so will cause lag if done on the main thread, instead this should be done in a background thread and then the result pushed back to the main thread.
* touch starts *
- DrawView : draw current touch
* touch ends *
- 'background thread' : combine backgroundView.image and DrawView.drawRect
* thread finished *
send resulting UIImage to main queue and set backgroundView.image to it;
Clear DrawView's current path that is now in the cache;
All of this combined can make a very smooth 60fps drawing app. However, views are not updated as quickly as we'd like so the drawing when moving the figure faster looks jagged. This can be improved by using UIBezierPath's instead of CGPaths.
CGPoint lastPoint = [touch previousLocationInView:self];
CGPoint mid = midPoint(currentPoint, lastPoint);
-[UIBezierPath addQuadCurveToPoint:mid controlPoint:lastPoint];
The reason it is slow is because every frame you are creating a bitmap and trying to draw that.
You asked for better ways of doing it? Have you looked at the apple sample code for a drawing app on iOS? If you don't like that, then you can always use cocos2d which provides a CCRenderTexture class (and sample code).
Currently, you are using a method which you already know is not efficient.
With this approach I suppose you should consider using background thread for all hard work of image rendering and main thread for UI updates only, i. e.
__block UIImage *__imageBuffer = nil;
- (UIImage *)drawSomeImage
{
UIGraphicsBeginImageContext(self.bounds);
// draw image with CoreGraphics
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (void)updateUI
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// prepare image on background thread
__imageBuffer = [self drawSomeImage];
dispatch_async(dispatch_get_main_queue(), ^{
// calling drawRect with prepared image
[self setNeedsDisplay];
});
});
}
- (void)drawRect
{
// draw image buffer on current context
[__imageBuffer drawInRect:self.bounds];
}
I am omitting some details for making the optimization more clear. Even better to switch to UIImageView. This way you could get rid from critically important - (void)drawDect method and update image property of the UIImageView when the image is ready.
Well I think you need to change your logic. You may get some very good idea with the help of this link
http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/
and if you think that you have no time to make understanding then you may go directly to this code https://github.com/levinunnink/Smooth-Line-View :) I hop this will help you a lot.
Use CgLayer for caching your paths, read the docs, Its best for optimization.
I did something exactly like this. Check out the Pixelate app on AppStore. In order to draw , I used tiles in my code. After all , when you toch the screen and draw something you need to re-draw the entire image which is a very heavy operation. If you like the way Pixelate is moving , here's how I did it:
1)Split my image in n x m tiles. That was so I can change those values and obtain bigger/smaller tiles. In the worst case scenario (the user taps at the intersection of 4 tiles) you have to re-draw those 4 tiles. Not the entire image.
2) Make a 3 dimensional matrix in which I was storring the pixel information of each tile. So matrix[0][0][0] was the red value ( each pixel has a RGB or RGBA value depending if you are using pngs or jpgs) of the first pixel of the first tile.
3) Get the location the user pressed and calculate the tiles that need to be modified.
4) Modify the values in the matrix and update the tiles that need to update.
NOTE: This most certainly isn't the best option. It's just an alternative. I mentioned it because I think it is close to what you have right now. And it worked for me on an iPhone 3GS. If you are targeting >= iPhone 4 , you should be more than ok.
Regards,
George
Whatever the method u've suggested is way too inefficient, because creating the image every time you move the finger is inappropriate.
If its just paths that you need to draw, then have a CGMutablePathref as a member variable,
and in draw rect just move to the specified point using CGPath functions.
And more importantly, while refreshing the view, call setNeedsDisplayInRect passing only the area that you need to draw. Hope it will work for you.

How to I rotate UIImageView by 90 degrees inside a UIScrollView with correct image size and scrolling?

I have an image inside an UIImageView which is within a UIScrollView. What I want to do is rotate this image 90 degrees so that it is in landscape by default, and set the initial zoom of the image so that the entire image fits into the scrollview and then allow it to be zoomed up to 100% and back down to minimum zoom again.
This is what I have so far:
self.imageView.transform = CGAffineTransformMakeRotation(-M_PI/2);
float minimumScale = scrollView.frame.size.width / self.imageView.frame.size.width;
scrollView.minimumZoomScale = minimumScale;
scrollView.zoomScale = minimumScale;
scrollView.contentSize = CGSizeMake(self.imageView.frame.size.height,self.imageView.frame.size.width);
The problem is that if I set the transform, nothing shows up in the scrollview. However if I commented out the transform, everything works except the image is not in the landscape orientation that I want it to be!
If I apply the transform and remove the code that sets the minimumZoomScale and zoomScale properties, then the image shows up in the correct orientation, however with the incorrect zoomScale and seems like the contentSize property isn't set correctly either - since the doesn't scroll to the edge of the image in the left/right direction, however does top and bottom but much over the edge.
NB: image is being loaded from a URL
Maybe rotating the image itself fits your needs:
UIImage* rotateUIImage(const UIImage* src, float angleDegrees) {
UIView* rotatedViewBox = [[UIView alloc] initWithFrame: CGRectMake(0, 0, src.size.width, src.size.height)];
float angleRadians = angleDegrees * ((float)M_PI / 180.0f);
CGAffineTransform t = CGAffineTransformMakeRotation(angleRadians);
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
[rotatedViewBox release];
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
CGContextRotateCTM(bitmap, angleRadians);
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-src.size.width / 2, -src.size.height / 2, src.size.width, src.size.height), [src CGImage]);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
I believe the easiest way (and thread safe too) is to do:
//assume that the image is loaded in landscape mode from disk
UIImage * LandscapeImage = [UIImage imageNamed: imgname];
UIImage * PortraitImage = [[UIImage alloc] initWithCGImage: LandscapeImage.CGImage
scale: 1.0
orientation: UIImageOrientationLeft];
Any calculations that you do based on the imageView's frame should probably be done before you apply any transformations to it. But I would actually suggest doing those calculations based on the size of the UIImage, not the UIImageView. Then set both the UIImageView's frame and the UIScrollView's contentSize based on that.
Max's suggestion is a good one, although with a larger image it could be a performance killer. Are you displaying this image from your app's resources? If so, why not just rotate the images before you even build the app?
There's a much easier solution that is also faster, just do this:
- (void) imageRotateTapped:(id)sender
{
[UIView animateWithDuration:0.33f animations:^()
{
self.imageView.transform = CGAffineTransformMakeRotation(RADIANS(self.rotateDegrees += 90.0f));
self.imageView.frame = self.imageView.superview.bounds; // change this to whatever rect you want
}];
}
When the user is done, you will need to actually create a new rotated image, but that is very easy to do.
I was using the accepted answer for a while until we noticed that non-square rotations based on images taken directly from the camera seemed stretched (they were rotated as desired, just the frame width/height wasn't adjusted).
Great explanation/post here from Trevor: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/
In the end, it was a very simple import of Trevor's code which uses categories to add a resizedImage:interpoationQuality method to UIImage. So yeah, user beware, if it still works for you, great. But if it doesn't, I'd take a look at the library instead.

How do I join x number of UIImages together?

I'm looking to stitch some images together, with the images always being added to the bottom of the previous image.
e.g. I have images A,B and C. I would like them to appear on top of one another like:
A
B
C
What's the best way to do this?
Thanks!
I did this in the end:
+ (UIImage *)joinImages:(UIImage *)im1 secondImage:(UIImage *)im2 thirdImage:(UIImage *)im3
{
//Joins 3 UIImages together, stitching them vertically
CGSize size = CGSizeMake(320, 480);
UIGraphicsBeginImageContext(size);
CGPoint image1Point = CGPointMake(0, 0);
[im1 drawAtPoint:image1Point];
CGPoint image2Point = CGPointMake(0, im1.size.height);
[im2 drawAtPoint:image2Point];
CGPoint image3Point = CGPointMake(0, im1.size.height +im2.size.height);
[im3 drawAtPoint:image3Point];
UIImage* finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return finalImage;
}
Create a UIView, then add multiple UIImageView instances as subviews. Set the image for each subview, and you're all set. If you have a fixed set of images (or at least a fixed number), you can do all of the layout in InterfaceBuilder. Otherwise, it's just a matter of creating a new UIImageView, then calling addSubView: on the parent view.
http://developer.apple.com/iPhone/library/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/addSubview:
If the set of images will be taller than the screen, you'll want to use a UITableView.