EAGLView to UIImage timing question - iphone

I have a EAGLView that I wish to convert into a UIImage. I can do this with the solution posted here:
How to get UIImage from EAGLView?
However, I can only accomplish this if a small amount of time has gone by between the creation of the EAGLView and the UIImage.
This code, which creates a EAGLView, and then a UIImageView right afterwards, does not work:
EAGLView *EAGLphoto = [[EAGLView alloc] initWithImage:photo.workAreaImage];
[theWorkArea.photoArea1 addSubview:EAGLphoto];
//I put a glfinish() here but it didn't help
UIImageView *photoView = [[UIImageView alloc] initWithImage:[self glToUIImage]];
//glToUIImage is taken from the link above
[theWorkArea.photoArea2 addSubview:photoView];
I'm assuming the UIImageView tries to get created before the EAGLView is finished being created. I tried to put a glfinish() in between but it did nothing. When I run the code, the EAGLView shows up fine but the UIImageView shows up as black.
However, this modified version of the above code works:
EAGLView *EAGLphoto = [[EAGLView alloc] initWithImage:photo.workAreaImage];
[theWorkArea.photoArea1 addSubview:EAGLphoto];
[self performSelector:#selector(getUIImage) withObject:nil afterDelay:0.1];
- (void)getUIImage {
UIImageView *photoView = [[UIImageView alloc] initWithImage:[self glToUIImage]];
//glToUIImage is taken from the link above
[theWorkArea.photoArea2 addSubview:photoView];
}
Am I using glfinish() incorrectly? Is there a better method than my hack?

Usually when you make visible changes to UIKit objects instead of updating instantly they merely flag themselves to make those changes in the future, then do the whole set of changes as a batch next time you relinquish the main thread (by returning to the runloop). That's not a misfeature or a failure in the implementation, it's actually usually what you implicitly expect, being the reason you can write code like:
view.frame = someNewFrame;
view.someOtherProperty = someOtherValue;
And not worry that every so often the view will visibly adopt the new frame before adopting the other changes. As a rule, you want the things you do to views to appear to be atomic.
Occasionally you run into a situation, as you have here, where the fact that changes you've already requested haven't come into effect yet is exposed. frame and someOtherProperty would return their new values in the above example so that you don't care whether the changes took effect immediately or not, but based on your observation about performSelector:withObject:afterDelay: it seems likely that you've stumbled upon a situation where the change doesn't pretend to be immediate.
EAGLView is difficult to diagnose because it's not really a UIKit feature. The CAEAGLLayer that it's built upon is, but EAGLView is just the name Apple have adopted for a custom UIView subclass built on CAEAGLLayer in various example projects. They've not been consistent about the interface or implementation of EAGLView across their examples, but at a guess I'd say that probably it's creating the OpenGL frame buffer object (that is, the thing that OpenGL uses to store numbers related to pixels, allowing glReadPixels to work) only when asked to lay itself out, and it doesn't lay itself out until UIKit asks it to do so. Which isn't until you've dropped out to the runloop.
That diagnosis can be confirmed by checking the code of the EAGLView that you have. If there are a bunch of calls to things like glGenFramebuffers that are triggered as a result of layoutSubviews, drawRect or some other method that isn't called directly by the relevant init then that proves it.
Assuming that diagnosis to be correct, you could adapt EAGLView but I think probably the best solution is just to stick to performSelector:withObject:afterDelay:0. This isn't a race condition, so that solution isn't in the slightest bit flakey or unreliable, it's just a slightly roundabout way of saying "let UIKit catch up with all of those instructions, given that I know that'll let EAGLView get into a meaningful state, then continue with my stuff".

Related

How do I “redraw layers” in Core Graphics?

I remember that when I was reading the Apple documentation, it would mention that when you call a function such as addSubview, you are adding a “layer of paint,” so to speak, and every time it is called, another layer is overlaid.
This should be an easy question to answer, but I had a hard time thinking of keywords to google for, so please excuse the asking of such a simple question.
How do I clear the “layers” of a custom UIView?
My situation, as it may be relevant: I have these “user cards” that are displayed on the screen. They are initialized with some user images. The cards stay the same, but I call a method in my custom UIView (the card UIView) to redraw the images when I want to display a different user. The problem is that some elements of this custom UIView are transparent, and redrawing these images each time builds on that transparency (an obvious problem).
In Core Graphics, what you draw is what gets shown. The painter’s analogy only refers to a single frame. So if you’re using drawRect, you just don’t cache the previous drawing.
But I suspect you’re talking about some UIKit stuff where you’ve added subviews or sublayers. This will remove those leftover views if you just want to clear everything:
for (UIView *view in customView) {
[view removeFromSuperview];
}
for (CALayer *layer in customView.layer) {
[layer removeFromSuperlayer];
}
Ryan's code for clearing UIViews is more or less correct, but if you came here from Google looking for how to clear CLLayers from a view, I was getting a crash when I attempted to fast enumerate customView.layer like Ryan has in his example.
When I switched it to regular enumeration, it worked for me. Here's a code snippet:
for (int i = 0; i > yourView.layer.sublayers.count; i++) {
CALayer *layer = [self.yourView.layer.sublayers objectAtIndex:i];
[layer removeFromSuperlayer];
}

UIImageView Intro Animation issue

I am trying to create an intro animation for my iOS app and am having issues with timing. In particular I would like to change screens after the intro animation plays. I currently use a UIImageView and there does not appear to be a way to do this. Many stackoverflow questions say to use an NSTimer or performSelector:afterDelay but these are not accurate timers and in my case are completely wrong. Here is what I am doing.
Set UILaunchImageFile to LaunchImage.png
AppDelegate allocs an IntroViewController
IntroViewController.LoadView allocs IntroView
IntroView.initWithFrame performs the following
UIImageView* iv =
iv.animationImages =
iv.animationDuration = 2.0
iv.animationRepeatCount = 1
[iv startAnimating]
Set NSTimer/performSelector:afterDelay?
When timer triggers change from IntroViewController to something else.
If I perform either step 5 or 6 it does not work correctly. It does correctly play the animation and it will correctly change the view/view controller, but the timing is horribly horribly wrong. When you call startAnimating in this manner it may not actually start the animation for a full second or two. I presume because the app is still loading in resources somehow. This time however is not consistent across the simulator or all devices. Infact several runs on the same device may have different results. Thus I can not hard code some delay.
All I want to do is detect that a UIImageView animation has played the last frame and do something. That's it. The best solution I've found so far is to set a timer in some manner and then do something, but in my situation a timer is not a solution.
Any ideas?
The long delay you observe is due to reading and decoding the images, which UIImageView does before the animation begins.
Core Animation performs the animation for you, and it does its drawing in the render server, which is in a separate process. Remember that what you see on the screen doesn't necessarily represent your app's instantaneous picture of your layer tree: Core Animation Rendering Architecture.
UIImageView doesn't provide facilities to give you accurate results here. I'd suggest:
Make a UIView of your own.
Create a CAKeyframeAnimation with discrete calculation mode and your images' CGImageRefs as its values.
Set the animation's delegate to your IntroViewController.
Add the animation to your view's layer for the "contents" key.
Your IntroViewController will get animationDidStop:finished: when it's done.
Two things to consider, though:
First, you'll get better results using a movie rather than a series of images, since the movie can be streamed from storage.
Second, and more importantly, this solution will improve the timing situation but will not totally mitigate it. animationDidStop:finished: is called when your app thinks the animation is done… which is not necessarily exactly when it appears to finish.
You'll do better if you don't rely on delegate callbacks for media timing: instead, add this animation and the animation transitioning your views (using a CAAnimationGroup if necessary) in the same turn of the run loop. Delay the latter with a beginTime of the first animation's duration. Depending on what you're doing, you may have to set the second animation's fill mode as well to get the correct behavior during the first.

Scrolling performance and UIImage drawing

I'm building a UITableView similar to iPod.app's album browsing view:
I'm importing all the artists and album artworks from the iPod library on first launch. Saving everything to CoreData and getting it back into an NSFetchedResultsController. I'm reusing cell identifiers and in my cellForRowAtIndexPath: method I have this code:
Artist *artist = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *identifier = #"bigCell";
SWArtistViewCell *cell = (SWArtistViewCell*)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil)
cell = [[[SWArtistViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
cell.artistName = artist.artist_name;
cell.artworkImage = [UIImage imageWithData:artist.image];
[cell setNeedsDisplay];
return cell;
My SWArtistViewCell cell implements the drawRect: method to draw both the string and image:
[artworkImage drawInRect:CGRectMake(0,1,44,44)]
[artistName drawAtPoint:CGPointMake(54, 13) forWidth:200 withFont:[UIFont boldSystemFontOfSize:20] lineBreakMode:UILineBreakModeClip];
Scrolling is still choppy and I just can't figure out why. Apps like iPod and Twitter have butter smooth scrolling and yet they both draw some small image in the cell as I do.
All my views are opaque. What am I missing?
EDIT: here's what Shark says:
I'm not familiar with Shark. Any pointer as of what are these symbols related to? When I look at the trace of these, they all point to my drawRect: method, specifically the UIImage drawing.
Would it point to something else if the chokehold was the file reading? Is it definitely the drawing?
EDIT: retaining the image
I've done as pothibo suggested and added an artworkImage method to my Artist class that retains the image created with imageWithData:
- (UIImage*)artworkImage {
if(artworkImage == nil)
artworkImage = [[UIImage imageWithData:self.image] retain];
return artworkImage;
}
So now I can directly set the retained image to my TableViewCell as follow:
cell.artworkImage = artist.artworkImage;
I also set my setNeedsDisplay inside the setArtworkImage: method of my tableViewCell class. Scrolling is still laggy and Shark shows exactly the same results.
Your profiling data strongly suggests that the bottleneck is in the unpacking of your PNG images. My guess is that 58.5 % of your presented CPU time is spent unpacking PNG data (i.e. if the memcpy call is also included in the loading). Probably even more of the time is spent there, but hard to say without more data. My suggestions:
As stated before, keep loaded images in UIImage, not in NSData. This way you won't have to PNG-unpack every time you display an image.
Put the loading of your images in a worker thread, to not affect the responsiveness of the main thread (as much). Creating a worker is real easy:
[self performSelectorOnMainThread:#selector(preloadThreadEntry:) withObject:nil waitUntilDone:NO];
Preload images far ahead, like 100 rows or more (like 70 in the direction you're scrolling, keep 30 in the opposite direction). If all your images need to be 88x88 pixels on retina, 100 images would require no more than two MB.
When you profile more the calls to stuff named "png", "gz", "inflate" and so forth might not go way down your list, but they will certainly not affect the feeling of the application in such a bad way.
Only if you still have performance problems after this, I would recommend you look into scaling, and for instance loading "...#2x.png" images for retina. Good luck!
[UIImage imageWithData:] doesn't cache.
This means that CoreGraphic uncompress and process your image every single time you pass in that dataSource method.
I would change your artist's object to hold on a UIImage instead of NSData. You can always flush the image on memoryWarning if you get them a lot.
Also, I would not recommend using setNeedsDisplay inside the dataSource call, I would use that from within your cell.
SetNeedsDisplay is not a direct call to drawRect:
It only tells the os to draw the UIVIew again at the end of the runloop. You can call setNeedsDisplay 100 times in the same runloop and the OS will only call your drawRect method once.
If the delay's happening in your -drawRect, then you might want to take a look at this article: Tweetie's developer explains pretty thoroughly the method he used to get that smooth scrolling you're after. This has become a bit easier since then, though: CALayer has a shouldRasterize property that basically flattens its sublayers into a bitmap, which can then—as long as nothing changes inside the layer—give you much better performance when animating the layer around, as UITableView does when you scroll it. In this case, you'd probably apply that property to your individual UITableViewCells' layers.
My guess is that the delay is from storing images in Core Data. Core Data is usually not a good way to store large blobs of data.
A better solution would be to store the images as individual files on disk, using an album id to identify each image. Then you would setup an in memory cache to store the images in RAM for fast loading into your UIImageViews. The loading of the images from disk to RAM would ideally need to be done on a background thread (e.g. try performSelectorOnBackgroundThread) so that the I/O doesn't bog down the main thread (which will impact on your scrolling performance).
Addendum 1
If you're only calling -drawRect: once per cell load (as you should be), then the problem might be the scaling of the images. Scaling an image by drawing it in code using drawInRect will use CPU time, so an alternative approach is to scale the images as you receive them from the iPod library (if you need them in multiple sizes, save a version in each size you require). You may need to do this on a background thread when you import the data to avoid blocking the main (UI) thread.
One alternative thing to consider is that UIImageView may do it's scaling using Core Animation which would mean it is hardware accelerated (I'm not sure if this is actually the case, I'm just guessing). Switching to a UIImageView for the image would therefore get rid of the CPU burden of image scaling. You would have a slight increase in compositing overhead, but it might be the easiest way to get closer to "optimum" scrolling performance.
At this point your best bet is to use Instruments (previously Shark) to try and find bottlenecks in your code.

how to obtain CGContextRef from UIView or UIWindow for debugging outside draw method?

[This is my first post here (but I've browsed answers many times; thanks to all for a very useful site). If I'm committing any great sins with this post, please forgive me and point me in the right direction.]
I am trying to debug some complicated startup code in my iPhone application; I really need to draw some images to make debugging more insightful, and to radically reduce debug tedium.
The state I need to draw does not really exist as a model object; it is somewhat transient during initialization. I especially want to be able to draw one bit of state at a time, rather than all of it in one big image (to help me see which components are right/wrong). In my case, there is lots of potentially overlapping stuff and a single image cannot easily, clearly display what's going on.
Creating a static view which draws to the screen using the normal drawing architecture might be possible, if I add a special debug mode, and additional controls to navigate / highlight / draw components, but it will require a lot of unnatural scaffolding, and is not, IMO, the right way to debug my problem.
So, what I hope to do is:
get or create an appropriate CGContextRef
draw in it
flush it to screen immediately
wait for some input from the user, that doesn't involve UIEvents
repeat 1-4 many times
Item 4 seems to be almost impossible (is there a way to get something like simple C or C++ console I/O working in the iPhone, or the iPhone simulator?), so I've given up on it for now; I'll settle for just using the Xcode debugger to stop between drawing operations.
But more of interest is how to get a CGContextRef outside of a UIView draw method, in which to draw the debug output. Caching a previously valid context, even if it were possible in my situation (not possible, since I'm debugging at app startup), appears not to work. It seems as if contexts may be created anew for each drawing operation, which strikes me as incurring a lot of overhead; I would have assumed a UIWindow, and maybe a UIView, saved and could provide a reference to an appropriate drawing context. Seems I must be wrong (but if not, how do I get a context from a view or window? I've found no methods for this).
So, if I must create a drawing context, how do I do it? I guess I really don't quite understand what exactly is bound together by a context: to what extent does it know about, and bind to, some specific display output device? How do I control what portion of the screen a context maps to? There seem to be very few ways to create a context, and they don't seem to provide such control. UIGraphicsBeginImageContext seems to be the only routine that might help me, and I don't see how creating and drawing into one, only to produce a CGImage as a result will be useful; how would I get the resulting image onto the screen?
I won't be surprised to find I'm confused about some basic stuff related to CG contexts. Hopefully, this question isn't so basic and obvious that I'll be annoying everyone who reads it, but if I've completely missed the right documentation, I'd be very appreciative of a few pointers.
Thanks for your mental bandwidth, and any help. -jar
You are correct in that you cannot draw directly to the screen. That being said, a UIView can be instructed to refresh at will if you call [view setNeedsDisplay]. If you override - (void)drawRect:(CGRect)rect, this will be called upon refresh. My suggestion for your problem is to subclass UIView, expose your debug state on this class, and when you update the state, force a refresh. If you are updating state at a high frequency, you should use CADisplayLink to tie the view refresh to the display refresh. If you call [view setNeedsDisplay] multiple times before the view redrawn it will not matter.
For example:
#import "QuartzTestView.h"
#import <QuartzCore/QuartzCore.h>
#interface QuartzTestView()
- (void)refreshView:(CADisplayLink*)displayLink;
#end
#implementation QuartzTestView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(drawView:)];
[displayLink setFrameInterval:1];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
return self;
}
- (void)refreshView:(CADisplayLink*)displayLink {
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
// do updates
}
#end
NOTE: You must add the QuartzCore.framework to run this

Custom view transition in OpenGL ES

I'm trying to create a custom transition, to serve as a replacement for a default transition you would get here, for example:
[self.navigationController pushViewController:someController animated:YES];
I have prepared an OpenGL-based view that performs an effect on some static texture mapped to a plane (let's say it's a copy of the flip effect in Core Animation). What I don't know how to do is:
grab current view content and make a texture out of it (I remember seeing a function that does just that, but can't find it)
how to do the same for the view that is currently offscreen and is going to replace current view
are there some APIs I can hook to in order to make my transition class as native as possible (make it a kind of Core Animation effect)?
Any thoughts or links are greatly appreciated!
UPDATE
Jeffrey Forbes's answer works great as a solution to capture the content of a view.
What I haven't figured out yet is how to capture the content of the view I want to transition to, which should be invisible until the transition is done.
Also, which method should I use to present the OpenGL view?
For demonstration purposes I used pushViewController. That affects the navbar, though, which I actually want to go one item back, with animation, check this vid for explanation:
http://vimeo.com/4649397.
Another option would be to go with presentViewController, but that shows fullscreen.
Do you think maybe creating another window (or view?) could be useful?
While I cannot completely answer your question without doing some more research of my own, I can help a bit:
-In order to get the view of a UINavigationController, you need to take a screenshot. The easiest way to do this is by grabbing it into a UIImage:
UIGraphicsBeginImageContext(self.view.frame.size);
[[self.view layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage* test = UIGraphicsGetImageFromCurrentImageContext();
UIImageView* view = [[UIImageView alloc] initWithImage:test];
UIGraphicsEndImageContext();
I am not sure if you can render a GLContext (not familiar on the phone) into a CGImage, but I would do something like that (and init a UIImage from that). I would prerender every frame of the animation you are trying to do and slap it into an UIImageView using the animation stuff provided within. That is, if your animation is simple enough. Otherwise, it might come down to writing your own animation function :-/
I have just put together a transition class to implement your own transition animation in OpenGL ES.
Feel free to read about it here
There are two example transitions in the project, feel free to add you own to it.
I think the function you might be thinking of is http://www.opengl.org/sdk/docs/man/xhtml/glCopyTexImage2D.xml ... you set the viewport to the texture size and then draw as usual, then do glCopyTexImage2D to copy the scene onto a texture.
or you should look into FrameBuffer Objects. The default OpenGL template in XCode uses these. Just generate the example project to see how those work.
I recently write some transitioning animation betweeen view controllers like you. If you want to get any extra info from the invisible view, you can try delaying the transition like this :
- (void)animationFromModalView:(UIView *)modalView toMasterView:(UIView *)masterView
{
[masterView setNeedsLayout];
[masterView layoutIfNeeded];
[self performSelector:#selector(delayAnimationFromModalViewToMasterView) withObject:nil afterDelay:.1f];
}