How to rasterize a view from another thread? - ios

I am converting a view into a bitmap image. This is called rasterization. I tried to offload the rasterization to another thread, so my UI doesn't freeze for split second during the rasterization. To my chagrin, attempting to do so does the opposite: it freezes the entire app forever. Activity indicators stop spinning, buttons stop responding to taps, etc...
- (void)setContainerView:(UIView*)containerView
{
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
^{
UIImage* hugeImage = containerView.rasterizedImage;
dispatch_async(
dispatch_get_main_queue(),
^ {
self.someImageView.image = hugeImage;
}
);
}
);
}
The rasterize function is defined in a UIView category:
- (UIImage*)rasterizedImage
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0.0);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
This function runs fine on the main thread. Why would it freeze the UI when run on another thread?

Almost all UIKit objects are not thread-safe. You CAN'T run them in a separate thread without either getting undefined results or locking up/crashing your program.

Related

dispatch_barrier_async doesn't wait for CoreImage to finish with processing

before I ask my question I should say I have read so much about it and I've tried so many ways but none has worked. I'm doing dozens of Core Image processing in a concurrent Queue, and I need to wait for them by using a dispatch_barrier_async to finish so only then I can do my final render and go to the next view controller but ironically, dispatch_barrier doesn't wait for my concurrent queue to finish, Why is that? is it because Im doing core image processing in the wrong thread?
//Here is my cocurrent queue.
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
Using it for processing effects here as an example
-(void)setupEffects{
//It's one of my effects as an example which renders for previewing the //effect.
case Effect4:{
dispatch_async(concurrentQueue, ^{
//BG
self.firstCIFilter = [CIFilter filterWithName:#"CIHexagonalPixellate"
withInputParameters:#{#"inputImage": [self getFirstCIImage],#"inputScale":#26}];
self.lastSelectedInputImgforBG =[self applyCIEffectWithCrop];
//FG
self.firstCIFilter = [CIFilter filterWithName:#"CIPhotoEffectProcess"
withInputParameters:#{#"inputImage":[self getFirstCIImage]}];
self.fgImgWithEffect = [self applyCIEffect];
dispatch_async(dispatch_get_main_queue(), ^{
self.lastSelectedInputImgforFG= [self cropAndFadeAndRenderFGImage];
[self saveEffect];
[self loadEffectsWithIndex:effectIndex];
});
});
}
//Once user is done, it renders the image once again
-(UIImage *)applyCIEffectWithCrop{
__weak typeof(self) weakSelf = self;
#autoreleasepool{
weakSelf.firstCIContext =nil;
weakSelf.firstResultCIImage=nil;
weakSelf.croppingCIImage=nil;
weakSelf.firstCIContext = [CIContext contextWithOptions:nil];
weakSelf.firstResultCIImage = [weakSelf.firstCIFilter valueForKey:kCIOutputImageKey];
weakSelf.croppingCIImage=[weakSelf.firstResultCIImage imageByCroppingToRect:CGRectMake(0,0, weakSelf.affineClampImage1.size.width*scale , weakSelf.affineClampImage1.size.height*scale)];
return [UIImage imageFromCIImage:weakSelf.croppingCIImage scale:1.0 orientation:weakSelf.scaledDownInputImage.imageOrientation cropped:YES withFirstCIImage:[weakSelf getFirstCIImage]];
}
}
And then for my final render, this method needs to wait for my setupEffect to finish and then use the segue but it doesn't.
- (void)doneButtonAction {
_finalRender =YES;
CGFloat max=MAX(self.originalSizeInputImage.size.width,self.originalSizeInputImage.size.height);
if (max<=1700){
//Do nothing for Final Render
self.scaledDownInputImage= self.originalSizeInputImage;
}else{
CGSize scaledDownSize = [self getScalingSizeForFinalRenderForImage: self.originalSizeInputImage];
self.scaledDownInputImage = [self scaleThisImage:self.originalSizeInputImage scaledToFillSize:scaledDownSize];
}
imageRect = AVMakeRectWithAspectRatioInsideRect(self.scaledDownInputImage.size, self.viewWithLoadedImages.bounds);
//Preparation for high quality render with high resolution input
//image.
self.affineClampImage1 = [self affineClampImage];
self.selectionCropAndBlurredImage = [self croppedFGtoGetBlurred];
[self.imgData appendData:UIImagePNGRepresentation(self.scaledDownInputImage)];
[self.effectClass getimageWithImageData:self.imgData];
if (_effectMode) {
//Applying effects again for the high resolution input image.
[self setupEffects];
}else{
[self setupFilters];
}
dispatch_async(concurrentQueue, ^{
//Rendering the high quality Images in full resolution here.
CGRect frame = CGRectMake(0.0, 0.0,
self.lastSelectedInputImgforBG.size.width *self.lastSelectedInputImgforBG.scale,
self.lastSelectedInputImgforBG.size.height *self.lastSelectedInputImgforBG.scale);
UIGraphicsBeginImageContextWithOptions(frame.size, NO, 1.0);
// Draw transparent images on top of each other
[self.lastSelectedInputImgforBG drawInRect:frame];
[self.lastSelectedInputImgforFG drawInRect:frame];
self.tempImage=nil;
self.tempImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
dispatch_barrier_async(concurrentQueue, ^{
// Getting the full resolution rendered image and going to
//the next viewcontroller when the setupEffect and render is
//finished... which it doesn't wait until they're finished...
self.finalHightqualityRenderedImage = self.tempImage;
[self performSegueWithIdentifier:#"showShareVC" sender:self];
});
}
I should mention My code works without problem without using my concurrent queue but of course that blocks the UI until its done which is not my goal.
Your help will be truly appreciated.
I think the explanation is at the bottom of the dispatch_barrier_async:
The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_async function.
So instead of grabbing DISPATCH_QUEUE_PRIORITY_BACKGROUND as per your
first line of code, create concurrentQueue yourself using dispatch_queue_create.

Receive Memory warning in combining two UIImage

I try to combine two UIImage with the following code:
- (void)combineImage:(UIImage *)image WithFrame:(CGRect)frame Completion:(ImageProcessorCompletionBlock)block {
__weak typeof(self) wSelf = self;
dispatch_async(_queue, ^{
if (wSelf) {
typeof(wSelf) sSelf = wSelf;
UIGraphicsBeginImageContextWithOptions(sSelf.originalImage.size, NO, 0.0);
[sSelf.originalImage drawInRect:CGRectMake(0, 0, sSelf.originalImage.size.width, sSelf.originalImage.size.height)];
[image drawInRect:frame];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block(result);
}
});
}
});
}
That works but when I check out the usage of memory, it scared me. Every time I run the method the memory rise up and never release. Sometimes I receive the memory warning. Can anyone tell me why and give me a solution to solve the problem? Thanks a lot!
Finally I figure out the problem.
UIGraphicsBeginImageContextWithOptions(sSelf.originalImage.size, NO, 0.0);
The first parameter is the size of the image and the last one is the scale factor. At the beginning I have already set the image size same as the original one. But I also set the scale as 0.0, which means it is set to the scale factor of the device’s main screen. So the result image is enlarged.
If I run the code several times, the result's size gets bigger and bigger, finally it use up the memory and I receive the warning.

IOS warning on background thread

I am downloading image on background thread and setting this image to UIImageView on main thread. But it is giving me some warning like
Using low GPU priority for background rendering
Also please check the below code for reference
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
int randomImgNum = [[contentDict valueForKey:#"imageIndex"]intValue];
UIImage *image = [UIImage imageNamed:[Utils getImageFromIndex:randomImgNum]];
UIImage *newBlurredImage = [image blurredImageWithRadius:5.0];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
customVieww.imgCardView.image = image;
[self setBlurrImage:newBlurredImage];
});
});
Please let me know what could be the issue and how we can resolve it.
Also please let me know for any explanation if I miss anything.
Thank you in advance.
if you want to work on the main view you have to use the main queue
because it can cause a problem if you use a background queue to change the UIView
Update code:
dispatch_async(dispatch_get_main_queue(), ^{
int randomImgNum = [[contentDict valueForKey:#"imageIndex"]intValue];
UIImage *image = [UIImage imageNamed:[Utils getImageFromIndex:randomImgNum]];
UIImage *newBlurredImage = [image blurredImageWithRadius:5.0];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
customVieww.imgCardView.image = image;
[self setBlurrImage:newBlurredImage];
});
});
check this :
According to apple documentation, iOS does not allow its GPU to any background application for obvious reason that its not on Foreground.
https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/ImplementingaMultitasking-awareOpenGLESApplication/ImplementingaMultitasking-awareOpenGLESApplication.html

iOS: can I take screenshot on global queue?

I wonder how can I take a screenshot in a global queue? right now I'm doing it in the main queue and it works, if I do it in the global queue, things freeze. I'm using this screenshot code: iOS: what's the fastest, most performant way to make a screenshot programmatically?
I also tried the following code to take the snapshot of self.view, but it also doesn't work:
+(UIImage *)snapshot_of_view:(UIView*)view {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, [UIScreen mainScreen].scale);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
any ideas?
Any UI operation MUST be performed in the main thread.
You are calling user interface code. UI code must be on the main thread, since that thread has the main run loop and stuff.
You need to do it on the main thread:
+(UIImage *)snapshot_of_view:(UIView*)view {
dispatch_async(dispatch_get_main_queue(), ^{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, [UIScreen mainScreen].scale);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
});
}

How to asynchronously load an image in an UIImageView?

I have an UIView with an UIImageView subview. I need to load an image in the UIImageView without blocking the UI. The blocking call seems to be: UIImage imageNamed:. Here is what I thought solved this problem:
-(void)updateImageViewContent {
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage * img = [UIImage imageNamed:#"background.jpg"];
dispatch_sync(dispatch_get_main_queue(), ^{
[[self imageView] setImage:img];
});
});
}
The image is small (150x100).
However the UI is still blocked when loading the image. What am I missing ?
Here is a small code sample that exhibits this behaviour:
Create a new class based on UIImageView, set its user interaction to YES, add two instances in a UIView, and implement its touchesBegan method like this:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.tag == 1) {
self.backgroundColor= [UIColor redColor];
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:#"woodenTile.jpg"]];
});
[UIView animateWithDuration:0.25 animations:
^(){[self setFrame:CGRectInset(self.frame, 50, 50)];}];
}
}
Assign the tag 1 to one of these imageViews.
What happens exactly when you tap the two views almost simultaneously, starting with the view that loads an image? Does the UI get blocked because it's waiting for [self setImage:[UIImage imageNamed:#"woodenTile.jpg"]]; to return ? If so, how may I do this asynchronously ?
Here is a project on github with ipmcc code
Use a long press then drag to draw a rectangle around the black squares. As I understand his answer, in theory the white selection rectangle should not be blocked the first time the image is loaded, but it actually is.
Two images are included in the project (one small: woodenTile.jpg and one larger: bois.jpg). The result is the same with both.
Image format
I don't really understand how this is related to the problem I still have with the UI being blocked while the image is loaded for the first time, but PNG images decode without blocking the UI, while JPG images do block the UI.
Chronology of the events
The blocking of the UI begins here..
.. and ends here.
AFNetworking solution
NSURL * url = [ [NSBundle mainBundle]URLForResource:#"bois" withExtension:#"jpg"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[self.imageView setImageWithURLRequest:request
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
NSLog(#"success: %#", NSStringFromCGSize([image size]));
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"failure: %#", response);
}];
// this code works. Used to test that url is valid. But it's blocking the UI as expected.
if (false)
if (url) {
[self.imageView setImage: [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; }
Most of the time, it logs: success: {512, 512}
It also occasionnaly logs: success: {0, 0}
And sometimes: failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...
But the image is never changed.
The problem is that UIImage doesn't actually read and decode the image until the first time it's actually used/drawn. To force this work to happen on a background thread, you have to use/draw the image on the background thread before doing the main thread -setImage:. This worked for me:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage * img = [UIImage imageNamed:#"background.jpg"];
// Make a trivial (1x1) graphics context, and draw the image into it
UIGraphicsBeginImageContext(CGSizeMake(1,1));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [img CGImage]);
UIGraphicsEndImageContext();
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
[[self imageView] setImage: img];
});
});
EDIT: The UI isn't blocking. You've specifically set it up to use UILongPressGestureRecognizer which waits, by default, a half a second before doing anything. The main thread is still processing events, but nothing is going to happen until that GR times out. If you do this:
longpress.minimumPressDuration = 0.01;
...you'll notice that it gets a lot snappier. The image loading is not the problem here.
EDIT 2: I've looked at the code, as posted to github, running on an iPad 2, and I simply do not get the hiccup you're describing. In fact, it's quite smooth. Here's a screenshot from running the code in the CoreAnimation instrument:
As you can see on the top graph, the FPS goes right up to ~60FPS and stays there throughout the gesture. On the bottom graph, you can see the blip at about 16s which is where the image is first loaded, but you can see that there's not a drop in the frame rate. Just from visual inspection, I see the selection layer intersect, and there's a small, but observable delay between the first intersection and the appearance of the image. As far as I can tell, the background loading code is doing its job as expected.
I wish I could help you more, but I'm just not seeing the problem.
You can use AFNetworking library , in which by importing the category
"UIImageView+AFNetworking.m"
and by using the method as follows :
[YourImageView setImageWithURL:[NSURL URLWithString:#"http://image_to_download_from_serrver.jpg"]
placeholderImage:[UIImage imageNamed:#"static_local_image.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
//ON success perform
}
failure:NULL];
hope this helps .
I had a very similar issue with my application where I had to download lot of images and along with that my UI was continuously updating. Below is the simple tutorial link which resolved my issue:
NSOperations & NSOperationQueues Tutorial
this is the good way:
-(void)updateImageViewContent {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage * img = [UIImage imageNamed:#"background.jpg"];
[[self imageView] setImage:img];
});
}
Why don't you use third party library like AsyncImageView? Using this, all you have to do is declare your AsyncImageView object and pass the url or image you want to load. An activity indicator will display during the image loading and nothing will block the UI.
-(void)touchesBegan: is called in the main thread. By calling dispatch_async(dispatch_get_main_queue) you just put the block in the queue. This block will be processed by GCD when the queue will be ready (i.e. system is over with processing your touches). That's why you can't see your woodenTile loaded and assigned to self.image until you release your finger and let GCD process all the blocks that have been queued in the main queue.
Replacing :
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:#"woodenTile.jpg"]];
});
by :
[self setImage:[UIImage imageNamed:#"woodenTile.jpg"]];
should solve your issue… at least for the code that exhibits it.
Consider using SDWebImage: it not only downloads and caches the image in the background, but also loads and renders it.
I've used it with good results in a tableview that had large images that were slow to load even after downloaded.
https://github.com/nicklockwood/FXImageView
This is an image view which can handle background loading.
Usage
FXImageView *imageView = [[FXImageView alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 150.0f)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.asynchronous = YES;
//show placeholder
imageView.processedImage = [UIImage imageNamed:#"placeholder.png"];
//set image with URL. FXImageView will then download and process the image
[imageView setImageWithContentsOfURL:url];
To get an URL for your file you might find the following interesting:
Getting bundle file references / paths at app launch
When you are using AFNetwork in an application, you do not need to use any block for load image because AFNetwork provides solution for it. As below:
#import "UIImageView+AFNetworking.h"
And
Use **setImageWithURL** function of AFNetwork....
Thanks
One way i've implemented it is the Following: (Although i do not know if it's the best one)
At first i create a queue by using Serial Asynchronous or on Parallel Queue
queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);**
or
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
**
Which ever you may find better for your needs.
Afterwards:
dispatch_async( queue, ^{
// Load UImage from URL
// by using ImageWithContentsOfUrl or
UIImage *imagename = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// Then to set the image it must be done on the main thread
dispatch_sync( dispatch_get_main_queue(), ^{
[page_cover setImage: imagename];
imagename = nil;
});
});
There is a set of methods introduced to UIImage in iOS 15 to decode images and create thumbnails asynchronously on background thread
func prepareForDisplay(completionHandler: (UIImage?) -> Void)
Decodes an image asynchronously and provides a new one for display in views and animations.
func prepareThumbnail(of: CGSize, completionHandler: (UIImage?) -> Void)
Creates a thumbnail image at the specified size asynchronously on a background thread.
You can also use a set of similar synchronous APIs, if you need more control over where you want the decoding to happen, e.g. specific queue:
func preparingForDisplay() -> UIImage?
func preparingThumbnail(of: CGSize) -> UIImage?

Resources