I am using a TTImageView (from three20) to display an image from the web.
self.pic = [[TTImageView alloc] initWithFrame:CGRectMake(25, 25, 100, 100)];
self.pic.urlPath = #"http://www.google.com/images/logos/ps_logo2.png";
[self.pic sizeToFit];
NSLog(#"%f %f %f %f", self.pic.frame.origin.x, self.pic.frame.origin.y, self.pic.frame.size.width, self.pic.frame.size.height);
the image loads and displays perfectly but the NSLog returns "25.000000 25.000000 0.000000 0.000000". How do I get this to return the correct frame?
It's been a while since I used TTImageView, but what I imagine is happening is the following:
You ask TTImageView to load a new image from a URL
Immediately after you set the URL path, you call sizeTo Fit
...but the image actually hasn't been downloaded yet, so the frame is set to zero because no image exists at that time
TTImageView will download asynchronously - that is, in the background. You need to use the delegate function TTImageViewDelegate (documented here) to get a call back when the image has finished loading, at which point you should call sizeToFit.
Currently, you're firing off a request to download the image - a process that could take several seconds on 3G, and still a fair amount of time on WiFi- and then the next line asks to resize the frame. You need to call sizeToFit after you're sure the image download has finished, which is what the delegate methods are for.
Related
I am a little confused about the behavior of animationImages in UIImageView. The documentation says:
Setting this property to a value other than nil hides the image
represented by the image property.
Yet, if I initWithImage, then set animationImages property, the image passed to the init call is displayed. When I start animation, it goes through the array as expected, but once animation is done, it reverts back to image. Is the documentation inaccurate?
What I want ultimately is for the UIImageView to display the first image in the array, then once animation is complete, display the last image in the array. From what I can tell, it seems like I'll need to set image manually at the beginning to the first frame, then set it to the last frame right before I start animation?
you can make the start img as the img you want to start with, and when the animation is running you change the uiimage to the last img you want as the last img, so when the animation stops the last img will be showing.
Yes, set the image property to the image you want to see as the first image. Just before you call startAnimation, change that to be the last image so after the animation stops it rests on the last image:
In my setup:
animatedIV.animationImages = imageArray;
animatedIV.image = [imageArray objectAtIndex:0];
animatedIV.animationRepeatCount = 1;
before I start animation:
animatedIV.image = [imageArray lastObject];
[animatedIV startAnimation];
I want to make an animated background for iphone app. Something simple 5-6 frames changing in the loop. On the front there will be another animation running. How can this be done?
The easiest thing to do is probably to use the animationImages property of a UIImageView. Once you have the animationImages property correctly set up, just call startAnimating on your view. So your code would look something like:
imageView.animationImages = myNSArrayofUIImagesObjects;
imageView.animationDuration = 1; // by default this is equal to the number of images multiplied by 1/30th of a second
[imageView startAnimating];
An important thing to note is that you can't easily control how long each image is shown. But what you can do is use the same image in your NSArray of images multiple times. So, for example, you could have an NSArray of length 500, where the first 100 entries map to your first image, the second 100 entries map to your second image, etc. Make sure to minimize the amount of memory you're loading onto the heap by reusing the same UIImage object for each of your five or six images.
In the iPhone sample code "PhotoScroller" from WWDC 2010, they show how to do a pretty good mimmic of the Photos app with scrolling, zooming, and paging of images. They also tile the images to show how to display high resolution images and maintain good performance.
Tiling is implemented in the sample code by grabbing pre scaled and cut images for different resolutions and placing them in the grid which makes up the entire image.
My question is: is there a way to tile images without having to manually go through all your photos and create "tiles"? How is it the Photos app is able to display large images on the fly?
Edit
Here is the code from Deepa's answer below:
- (UIImage *)tileForScale:(float)scale row:(int)row col:(int)col size:(CGSize)tileSize image:(UIImage *)inImage
{
CGRect subRect = CGRectMake(col*tileSize.width, row * tileSize.height, tileSize.width, tileSize.height);
CGImageRef tiledImage = CGImageCreateWithImageInRect([inImage CGImage], subRect);
UIImage *tileImage = [UIImage imageWithCGImage:tiledImage];
return tileImage;
}
Here goes the piece of code for tiled image generation:
In PhotoScroller source code replace tileForScale: row:col: with the following:
inImage - Image that you want to create tiles
- (UIImage *)tileForScale: (float)scale row: (int)row column: (int)col size: (CGSize)tileSize image: (UIImage*)inImage
{
CGRect subRect = CGRectMake(col*tileSize.width, row * tileSize.height, tileSize.width, tileSize.height);
CGImageRef tiledImage = CGImageCreateWithImageInRect([inImage CGImage], subRect);
UIImage *tileImage = [UIImage imageWithCGImage: tiledImage];
return tileImage;
}
Regards,
Deepa
I've found this which may be of help: http://www.mikelin.ca/blog/2010/06/iphone-splitting-image-into-tiles-for-faster-loading-with-imagemagick/
You just run it in the Terminal as a shell script on your Mac.
Sorry Jonah, but I think that you cannot do what you want to.
I have been implementing a comic app using the same example as a reference and had the same doubt. Finally, I realized that, even if you could load the image and cut it into tiles the first time that you use it, you shouldn't. There are two reasons for that:
You do the tiling to save time and be more responsive. Loading and tiling takes time for a large image.
Previous reason is particularly important the first time the user runs the app.
If these two reasons make no sense to you, and you still want to do it, I would use Quartz to create the tiles. CGImage function CGImageCreateWithImageInRect would be my starting point.
Deepa's answer above will load the entire image into memory as a UIImage (the input variable in his function), defeating the purpose of tiling.
Many image formats support region-based decoding. Instead of loading the whole image into memory, decompressing the whole thing, and discarding all but the region of interest (ROI), you can load and decode only the ROI, on-demand. For the most part, this eliminates the need to pre-generate and save image tiles. I've never worked with ImageMagick but I'd be amazed if it couldn't do it. (I have done it using the Java Advanced Imaging (JAI) API, which isn't going to help you on the iPhone...)
I've played with the PhotoScroller example and the way it works with pre-generated tiles is only to demonstrate the idea behind CATiledLayer, and make a working-self contained project. It's straightforward to replace the image tile loading strategy - just rewrite the TilingView tileForScale:row:col: method to return a UIImage tile from some other source, be it Quartz or ImageMagick or whatever.
I have a PDF file that I want to draw in outline form. I want to draw the first several pages on the document each in their own UIImage to use on a button so that when clicked, the main display will navigate to the clicked page.
However, CGContextDrawPDFPage seems to be using copious amounts of memory when attempting to draw the page. Even though the image is only supposed to be around 100px tall, the application crashes while drawing one page in particular, which according to Instruments, allocates about 13 MB of memory just for the one page.
Here's the code for drawing:
//Note: This is always called in a background thread, but the autorelease pool is setup elsewhere
+ (void) drawPage:(CGPDFPageRef)m_page inRect:(CGRect)rect inContext:(CGContextRef) g {
CGPDFBox box = kCGPDFMediaBox;
CGAffineTransform t = CGPDFPageGetDrawingTransform(m_page, box, rect, 0,YES);
CGRect pageRect = CGPDFPageGetBoxRect(m_page, box);
//Start the drawing
CGContextSaveGState(g);
//Clip to our bounding box
CGContextClipToRect(g, pageRect);
//Now we have to flip the origin to top-left instead of bottom left
//First: flip y-axix
CGContextScaleCTM(g, 1, -1);
//Second: move origin
CGContextTranslateCTM(g, 0, -rect.size.height);
//Now apply the transform to draw the page within the rect
CGContextConcatCTM(g, t);
//Finally, draw the page
//The important bit. Commenting out the following line "fixes" the crashing issue.
CGContextDrawPDFPage(g, m_page);
CGContextRestoreGState(g);
}
Is there a better way to draw this image that doesn't take up huge amounts of memory?
Try to add :
CGContextSetInterpolationQuality(g, kCGInterpolationHigh);
CGContextSetRenderingIntent(g, kCGRenderingIntentDefault);
before :
CGContextDrawPDFPage(g, m_page);
I had a similar issue and adding the 2 function call above resulted in the rendering using 5x less memory. Might be a bug in the CGContextXXX drawing functions
Take a look at my code for a PDF image slicer on github:
http://github.com/luciuskwok/Maps-Slicer
There should be enough memory on the device that a 13 MB allocation isn't going to kill the app. Are you draining the autorelease pool each time you render a PDF? You might also want to cache the rendering into a UIImage so that it doesn't have to render it every time it's displayed.
I have a UIImageView object that is just a plain black rectangle.
This is what i use to select a button in the view.
Problem is, I have 49 of these buttons in my view, and all of them can be selected at the same time.
What I use for adding a subview to a button is:
UIImageView* selectedSquareView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,40,40)];
[selectedSquareView setImage:[UIImage imageNamed:#"SelectedSquare.png"]];
[button addSubview: selectedSquareView];
I would like the selectedSquareView to be reused multiple times as subviews for the other buttons, but only keep one allocation of it. I would prefer not having 49 UIImageViews created at the same time just for this purpose. Is this possible?
If not, should I store them in a NSMutableArray for easy removal later?
Regards
-Raymond
You will need 49 UIImageViews, you only need 1 UIImage. The UIImageViews contain position, size, is_higlighted, etc information for each button.
That being said even if you had a lot of UIImage's, UIImage is supposed to be pretty intelligent about these things as Apple describes in their documentation:
In low-memory situations, image data may be purged from a UIImage object to free up memory on the system. This purging behavior affects only the image data stored internally by the UIImage object and not the object itself. When you attempt to draw an image whose data has been purged, the image object automatically reloads the data from its original file. This extra load step, however, may incur a small performance penalty.
You should avoid creating UIImage objects that are greater than 1024 x 1024 in size. Besides the large amount of memory such an image would consume, you may run into problems when using the image as a texture in OpenGL ES or when drawing the image to a view or layer. This size restriction does not apply if you are performing code-based manipulations, such as resizing an image larger than 1024 x 1024 pixels by drawing it to a bitmap-backed graphics context. In fact, you may need to resize an image in this manner (or break it into several smaller images) in order to draw it to one of your views.
Alternatively if you really feel like you need to do delete the UIImageView's when not in use you can do as you suggest store them in an array and release them on viewDidDisappear and then recreate them all on viewWillAppear.
Each UIView appears only once, so you will definitely need to create 49 copies of it.
Your current code is probably fine, since UIImage will probably cache the image, but you might like to create the image only once and then set it each time, something like:
UIImageView* selectedSquareView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,40,40)];
static UIImage* kSelectedSquareImage = [UIImage imageNamed:#"SelectedSquare.png"] retain];
[selectedSquareView setImage:kSelectedSquareImage];
If not, should I store them in a
NSMutableArray for easy removal later?
It depends if there are any other views in the container view - if not, then there is no need to store them in an NSMutableArray as you can just use container.subviews to get an array of views. Otherwise, sure, you could store them in an NSMutableArray and remove them that way (just make sure you remove them from the array or release the array as well, otehrwise they will remain in memory simply because they are stored in the array).