CGContextDrawImage off Screen performance - ios

I have been using the following code to create an image off the screen (creating a
context, off-screen in background thread using GDC ) and later render it in the
main thread on the screen only when i receive updated "array of UIImages"
.
I found by doing so the rending path , lines .etc was much faster then dowing it the
drawRect: method of the UIView .
The issue that I'm facing now is that when trying to rending UIImage class using
CGContextDrawImage() it get really slow over ~1.4-2.5 sec to render. From searching
the main issue was that it spends most of its time deflating the png file. I have
tried infating the png image and use CGImageRef still long rending time. I dont have
a control on the data array that contain list if png files it comes in.
Would breaking the image to small pieces or and, using CALayer help , how i can stitch
those images without using "CGContextDrawImage" is it possible ?
I'm not sure what is best way to fix this issues and how , any Idea ?
Thanks In Advance
void creatImageInBackground
{
size = CGSizeMake (self.frame.size.width,self.frame.size.height);
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
UIGraphicsPushContext(context);
for(UIImage * img in imgList )
{
ulXY = <calcuate >;
lrXY = <calcuate >;
imRec = CGRectMake( ulXY.x,-ulXY.y,(lrXY.x-ulXY.x) ,(lrXY.y -ulXY.y) );
CGContextTranslateCTM( context, 0,(lrXY.y-ulXY.y) );
CGContextScaleCTM(context, 1, -1);
CGContextDrawImage(context, imRec,[[img image] CGImage]);
//[img drawInRect:imRec];
}
UIGraphicsPopContext();
[outputImage release];
outputImage = UIGraphicsGetImageFromCurrentImageContext() ;
[outputImage retain];
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self setNeedsDisplay];
[self setHidden:NO];
});
}
Then
- (void)drawRect:(CGRect)rect
{
CGPoint imagePoint = CGPointMake(0, 0);
[outputImage drawAtPoint:imagePoint];
}

Related

Fastest way to take screenShot of UIView

I've searched a lot but only found two methods to take screen shot of UIView.
first renderInContext:
I've used it in a way
CGContextRef context = [self createBitmapContextOfSize:CGSizeMake(nImageWidth, nImageHeight)];
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, nImageHeight);
CGContextConcatCTM(context, flipVertical);
[self.layer setBackgroundColor:[UIColor clearColor].CGColor];
[self.layer renderInContext:context];
CGImageRef cgImage = CGBitmapContextCreateImage(context);
UIImage* background = [UIImage imageWithCGImage: cgImage];
CGImageRelease(cgImage);
Second drawViewHierarchyInRect: which I've used as
UIImage *background = nil;
UIGraphicsBeginImageContextWithOptions (self.bounds.size, NO, self.window.screen.scale);
if ([self respondsToSelector:#selector(drawViewHierarchyInRect:afterScreenUpdates:)])
{
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
}
background = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I know that the second one is faster than first and it work for me for iPhone because the view has low size. but when I capturing from iPad the video become jerky.
Can Any body tell me faster way of taking screen shot.
any help would be highly appreciated
Regarding performance, the Apple Docs state the following:
In addition to -drawViewHierarchyInRect:afterScreenUpdates:, UIView
now provides another two snapshot related methods,
-snapshotViewAfterScreenUpdates: and -resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:. UIScreen also has -snapshotViewAfterScreenUpdates:.
Unlike UIView's -drawViewHierarchyInRect:afterScreenUpdates:, these
methods return a UIView object. If you are looking for a new snapshot
view, use one of
these methods. It will be more efficient than calling
-drawViewHierarchyInRect:afterScreenUpdates: to render the view contents into a bitmap image yourself. You can use the returned view
as a visual stand-in for the current view/screen in your app. For
example, you might use a snapshot view for animations where updating a
large view hierarchy might be expensive.
There is a third method for taking a snapshot that is much much quicker than either of these but it returns a UIView.
- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates
If you are just using the snapshot to place as a background "image" etc... then I'd use this instead.
However, this is only available for iOS8.
To use it just do...
UIView *snapshotView = [someView snapshotViewAfterScreenUpdates:YES];
This Method will return you A snapshot images of particular view
-(UIImage *)createSnapShotImagesFromUIview
{
UIGraphicsBeginImageContext(CGSizeMake(view.frame.size.width,view.frame.size.height));
CGContextRef context = UIGraphicsGetCurrentContext();
[mapView.layer renderInContext:context];
UIImage *img_screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img_screenShot;
}

CGContextDrawImage memory is not freed

I'm using the PhotoScrollerNetwork project to provide a single high resolution image to a view in my project and automatically tile it, so memory is managed properly. It uses this block of code to draw the full high res image to memory, so that tiles can be calculated out of it.
-(void)drawImage:(CGImageRef)image {
madvise(ims[0].map.addr, ims[0].map.mappedSize - ims[0].map.emptyTileRowSize, MADV_SEQUENTIAL);
unsigned char *addr = ims[0].map.addr + ims[0].map.col0offset + ims[0].map.row0offset * ims[0].map.bytesPerRow;
CGContextRef context = CGBitmapContextCreate(addr, ims[0].map.width, ims[0].map.height, bitsPerComponent, ims[0].map.bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little);
assert(context);
CGContextSetBlendMode(context, kCGBlendModeCopy); // Apple uses this in QA1708
CGRect rect = CGRectMake(0, 0, ims[0].map.width, ims[0].map.height);
CGContextDrawImage(context, rect, image);
CGContextRelease(context);
madvise(ims[0].map.addr, ims[0].map.mappedSize - ims[0].map.emptyTileRowSize, MADV_FREE);
}
In the dealloc method of the class, the ims is freed ( 'free(ims)'), so this should be handled properly. However, if I make a new view (and thus a call to drawImage) repeatedly, my memory is getting filled. I found that if I comment CGContextDrawImage(context, rect, image);, the memory is ok, so I think something is kept in memory, but I can't get what... The dealloc method is always called, so that's not the problem.
EDIT:
My image is also released properly, this is the complete flow:
- (void)myFunc {
CFDictionaryRef options = [self createOptions];
CGImageRef image = CGImageSourceCreateImageAtIndex(imageSourcRef, 0, options);
CFRelease(options);
CFRelease(imageSourcRef);
if (image) {
[self decodeImage:image];
CGImageRelease(image);
}
}
- (void)decodeImage:(CGImageRef)image {
assert(decoder == cgimageDecoder);
size_t width = CGImageGetWidth(image);
size_t height = CGImageGetHeight(image);
#if LEVELS_INIT == 0
zoomLevels = [self zoomLevelsForSize:CGSizeMake(width, height)];
ims = calloc(zoomLevels, sizeof(imageMemory));
#endif
[self mapMemoryForIndex:0 width:width height:height];
[self drawImage:image];
[self createLevelsAndTile];
}
Running both local from-bundle images, and network images, it appears any significant leak is gone. This with iOS7 and Xcode 5.

Fast screenshot ios

In my project I have to make a screenshot of the screen and apply blur to create the effect of frosted glass. Content can be moved under the glass and blured picture changed.
I'v used Accelerate.framework to speedup blurring, also i,v used OpenGL to draw CIImage directly to GLView.
Now I'm looking for a way to optimize getting screenshot of the screen.
I use this method to get screenshot of some area at the bottom of the screen:
CGSize size = CGSizeMake(rect.size.width, rect.size.height);
// get screenshot of self.view
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(nil, size.width, size.height, 8, 0, colorSpaceRef, kCGImageAlphaPremultipliedFirst);
CGContextClearRect(ctx, rect);
CGColorSpaceRelease(colorSpaceRef);
CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);
CGContextSetShouldAntialias(ctx, NO);
CGContextSetAllowsAntialiasing(ctx, NO);
CGContextTranslateCTM(ctx, 0.0, someView.frame.size.height);
CGContextScaleCTM(ctx, 1, -1);
//add mask
CGImageRef maskImage = [UIImage imageNamed:#"mask.png"].CGImage;
CGContextClipToMask(ctx, rect, maskImage);
[someView.layer renderInContext:ctx];
//get screenshot image
CGImageRef imageRef = CGBitmapContextCreateImage(ctx);
It works fine and fast if self.view has 1-2 subviews, but if there are several subviews (or it is tableview), then everything starts to slow down.
So i try to find a fast way to get pixels from some rect on screen. Maybe using a low-level API.
if just perform some animations , try this way , called -snapshotViewAfterScreenUpdates: or -resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets: method which are UIView provided , these method return UIView object without rendering into a bitmap image , so it is a more efficient .

How to get a CGImageRef from Context-Drawn Images?

Ok using coregraphics, I'm building up an image which will later be used in a CGContextClipToMask operation. It looks something like the following:
UIImage *eyes = [UIImage imageNamed:#"eyes"];
UIImage *mouth = [UIImage imageNamed:#"mouth"];
UIGraphicsBeginImageContext(CGSizeMake(150, 150));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 0, 0, 0, 1);
CGContextFillRect(context, bounds);
[eyes drawInRect:bounds blendMode:kCGBlendModeMultiply alpha:1];
[mouth drawInRect:bounds blendMode:kCGBlendModeMultiply alpha:1];
// how can i now get a CGImageRef here to use in a masking operation?
UIGraphicsEndImageContext();
Now, as you can see by the comment, I'm wondering how I'm actually going to USE the image I've built up. The reason why I'm using core graphics here and not just building up a UIImage is that the transparency I'm creating is very important. If I just grab a UIImage from the context, when it's used as a mask, it will just apply to everything... Further to the point, will I have any problems using a partially-transparent mask using this method?
CGImageRef result = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
You can call the UIGraphicsGetImageFromCurrentImageContext function, which will return a UIImage object. You can hold onto and use the UIImage, or ask it for its CGImage.

iOS: Applying a RGB filter to a greyscale PNG

I have a greyscale gem top view.
(PNG format, so has alpha component)
I would like to create 12 small size buttons, each in a different colour, from this image.
For the sake of tidiness, I would like to do this within the code rather than externally in some art package.
Can anyone provide a method (or even some code) for doing this?
PS I am aware of how to do it in GL using a ridiculous amount of code, I'm hoping there is a simpler way using core graphics / core animation
EDIT: Working solution, thanks to awesomeness from below answer
CGSize targetSize = (CGSize){100,100};
UIImage* image;
{
CGRect rect = (CGRect){ .size = targetSize };
UIGraphicsBeginImageContext( targetSize );
{
CGContextRef X = UIGraphicsGetCurrentContext();
UIImage* uiGem = [UIImage imageNamed: #"GemTop_Dull.png"];
// draw gem
[uiGem drawInRect: rect];
// overlay a red rectangle
CGContextSetBlendMode( X, kCGBlendModeColor ) ;
CGContextSetRGBFillColor ( X, 0.9, 0, 0, 1 );
CGContextFillRect ( X, rect );
// redraw gem
[uiGem drawInRect: rect
blendMode: kCGBlendModeDestinationIn
alpha: 1. ];
image = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
}
The easiest way to do it is to draw the image into an RGB-colorspaced CGBitmapContext, use CGContextSetBlendMode to set kCGBlendModeColor, and then draw over it with a solid color (e.g. with CGContextFillRect).
The best looking results are going to come from using the gray value to index into a gradient that goes from the darkest to the lightest colors of the desired result. Unfortunately I don't know the specifics of doing that with core graphics.
This is an improvement upon the answer in the question and an implementation of #Anomie
First, put this at the beginning of your UIButton class, or your view controller. It translates from UIColor to an RGBA value, which you will need later.
typedef enum { R, G, B, A } UIColorComponentIndices;
#implementation UIColor (EPPZKit)
- (CGFloat)redRGBAValue {
return CGColorGetComponents(self.CGColor)[R];
}
- (CGFloat)greenRGBAValue {
return CGColorGetComponents(self.CGColor)[G];
}
- (CGFloat)blueRGBAValue {
return CGColorGetComponents(self.CGColor)[B];
}
- (CGFloat)alphaRGBAValue {
return CGColorGetComponents(self.CGColor)[A];
}
#end
Now, make sure that you have your custom image button in IB, with a grayscale image and the right frame. This is considerably better and easier then creating the custom image button programmatically, because:
you can let IB load the image, instead of having to load it manually
you can adjust the button and see it visually in IB
your IB will look more like your app at runtime
you don't have to manually set frames
Assuming you are having the button be in IB (near the bottom will be support for having it programmatically created), add this method to your view controller or button cub class:
- (UIImage*)customImageColoringFromButton:(UIButton*)customImageButton fromColor:(UIColor*)color {
UIImage *customImage = [customImageButton.imageView.image copy];
UIGraphicsBeginImageContext(customImageButton.imageView.frame.size); {
CGContextRef X = UIGraphicsGetCurrentContext();
[customImage drawInRect: customImageButton.imageView.frame];
// Overlay a colored rectangle
CGContextSetBlendMode( X, kCGBlendModeColor) ;
CGContextSetRGBFillColor ( X, color.redRGBAValue, color.greenRGBAValue, color.blueRGBAValue, color.alphaRGBAValue);
CGContextFillRect ( X, customImageButton.imageView.frame);
// Redraw
[customImage drawInRect:customImageButton.imageView.frame blendMode: kCGBlendModeDestinationIn alpha: 1.0];
customImage = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
return customImage;
}
You then will need to call it in a setup method in your view controller or button subclass, and set the imageView of the button to it:
[myButton.imageView setImage:[self customImageColoringFromButton:myButton fromColor:desiredColor]];
If you are not using IB to create the button, use this method:
- (UIImage*)customImageColoringFromImage:(UIImage*)image fromColor:(UIColor*)color fromFrame:(CGRect)frame {
UIImage *customImage = [image copy];
UIGraphicsBeginImageContext(frame.size); {
CGContextRef X = UIGraphicsGetCurrentContext();
[customImage drawInRect: frame];
// Overlay a colored rectangle
CGContextSetBlendMode( X, kCGBlendModeColor) ;
CGContextSetRGBFillColor ( X, color.redRGBAValue, color.greenRGBAValue, color.blueRGBAValue, color.alphaRGBAValue);
CGContextFillRect ( X, frame);
// Redraw
[customImage drawInRect:frame blendMode: kCGBlendModeDestinationIn alpha: 1.0];
customImage = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
return customImage;
}
And call it with:
[self.disclosureButton.imageView setImage:[self customImageColoringFromImage:[UIImage imageNamed:#"GemTop_Dull.png"] fromColor:desiredColor fromFrame:desiredFrame]];

Resources