What is the purpose of `contextWithCGContext:options:` method? - objective-c

I treated CIContext as a kind of abstract state collector, which purpose is to allow CIFilters to function.
But there is +[CIContext contextWithCGContext:options:] function which accepts CGContextRef as its input.
What does it mean? If this CGContextRef already contains some graphics, then how it will be used in newly created CIContext?
Will the contents of CGContextRef affect CIFilters in any way?
Is it possible to output CIImages, produced by CIFilters, into this initial CGContextRef?

What does it mean? If this CGContextRef already contains some graphics, then how it will be used in newly created CIContext?
CIContext can be backed by the CGContext, and used to draw the filtered data on top of the context's content.
Is it possible to output CIImages, produced by CIFilters, into this initial CGContextRef?
(Almost) any UIView class sets a CGContext shortly before calling drawRect: method, and if you are not going to produce an UIImage/CIImage object, but instead just want to override this method and draw the result of your filters, you can get use of -[CIContext drawImage:inRect:fromRect:] in order to render your (e.g. filtered) data to the context of this view:
- (void)drawRect:(CGRect)rect {
CIContext *context = [CIContext contextWithCGContext:UIGraphicsGetCurrentContext() options:nil];
CIImage *input = self.image.CIImage;
CIFilter *filter = [[self class] makeFilter];
[filter setValue:input forKey:kCIInputImageKey];
[context drawImage:filter.outputImage
inRect:rect
fromRect:filter.outputImage.extent];
}
Will the contents of CGContextRef affect CIFilters in any way?
Not exactly, CIFilters objects in isolation don't get affected by any side effects of CGContext state currently active. However if the CGContext itself has any transformation in place, it won't be discarded of course:
- (void)drawRect:(CGRect)rect {
CGContextRef cgContext = UIGraphicsGetCurrentContext();
CGContextRotateCTM(cgContext, radians(-45.));
CIContext *context = [CIContext contextWithCGContext:cgContext options:nil];
... Applies CIFilter sequence ...
// The result will be both filtered and rotated by 45 degrees counterclockwise
[context drawImage:filter.outputImage
inRect:rect
fromRect:filter.outputImage.extent];
}

Related

CIFilter guassianBlur and boxBlur are shrinking the image - how to avoid the resizing?

I am taking a snapshot of the contents of an NSView, applying a CIFilter, and placing the result back into the view. If the CIFilter is a form of blur, such as CIBoxBlur or CIGuassianBlur, the filtered result is slightly smaller than the original. As I am doing this iteratively the result becomes increasingly small, which I want to avoid.
The issue is alluded to here albeit in a slightly different context (Quartz Composer). Apple FunHouse demo app applies a Guassian blur without the image shrinking, but I haven't yet worked out how this app does it (it seems to be using OpenGL which I am not familiar with).
Here is the relevant part of the code (inside an NSView subclass)
NSImage* background = [[NSImage alloc] initWithData:[self dataWithPDFInsideRect:[self bounds]]];
CIContext* context = [[NSGraphicsContext currentContext] CIContext];
CIImage* ciImage = [background ciImage];
CIFilter *filter = [CIFilter filterWithName:#"CIGaussianBlur"
keysAndValues: kCIInputImageKey, ciImage,
#"inputRadius", [NSNumber numberWithFloat:10.0], nil];
CIImage *result = [filter valueForKey:kCIOutputImageKey];
CGImageRef cgImage = [context createCGImage:result
fromRect:[result extent]];
NSImage* newBackground = [[NSImage alloc] initWithCGImage:cgImage size:background.size];
If I try a color-changing filter such as CISepiaTone, which is not shifting pixels around, the shrinking does not occur.
I am wondering if there is a quick fix that doesn't involve diving into openGL?
They're actually not shrinking the image, they're expanding it (I think by 7 pixels around all edges) and the default UIView 'scale To View' makes it looks like it's been shrunk.
Crop your CIImage with:
CIImage *cropped=[output imageByCroppingToRect:CGRectMake(0, 0, view.bounds.size.width*scale, view.bounds.size.height*scale)];
where view is the original bounds of your NSView that you drew into and 'scale' is your [UIScreen mainScreen] scale].
You probably want to clamp your image before using the blur:
- (CIImage*)imageByClampingToExtent {
CIFilter *clamp = [CIFilter filterWithName:#"CIAffineClamp"];
[clamp setValue:[NSAffineTransform transform] forKey:#"inputTransform"];
[clamp setValue:self forKey:#"inputImage"];
return [clamp valueForKey:#"outputImage"];
}
Then blur, and then crop to the original extent. You'll get non-transparent edges this way.
#BBC_Z's solution is correct.
Although I find it more elegant to crop not according to the view, but to the image.
And you can cut away the useless blurred edges:
// Crop transparent edges from blur
resultImage = [resultImage imageByCroppingToRect:(CGRect){
.origin.x = blurRadius,
.origin.y = blurRadius,
.size.width = originalCIImage.extent.size.width - blurRadius*2,
.size.height = originalCIImage.extent.size.height - blurRadius*2
}];

Applying core image filters - app crashes

I use the following code for applying a few types of image filters. (there are three more 'editImage' functions for brightness, saturation and contrast, with a common completeImageUsingOutput method). I use a slider to vary their values.
If I work with any of them individually, it works fine. As soon as I make two function calls on two different filters, the app crashed.
EDIT: didReceiveMemoryWarning is called. I see the memory allocations using memory leaks instrument, and after each edit memory allocation increases by around 15mb
The crash happens during
CGImageRef cgimg = [context createCGImage:outputImage fromRect:outputImage.extent];
Moreover, if the instructions completeImageUsingOutputImage method are put into the individual functions, I am able to work with two types of filters without crashing. As soon as I call the third one, the app crashes.
(filters and context have been declared as instance variables and initialized in the init method)
- (UIImage *)editImage:(UIImage *)imageToBeEdited tintValue:(float)tint
{
CIImage *image = [[CIImage alloc] initWithImage:imageToBeEdited];
NSLog(#"in edit Image:\ncheck image: %#\ncheck value:%f", image, tint);
[tintFilter setValue:image forKey:kCIInputImageKey];
[tintFilter setValue:[NSNumber numberWithFloat:tint] forKey:#"inputAngle"];
CIImage *outputImage = [tintFilter outputImage];
NSLog(#"check output image: %#", outputImage);
return [self completeEditingUsingOutputImage:outputImage];
}
- (UIImage *)completeEditingUsingOutputImage:(CIImage *)outputImage
{
CGImageRef cgimg = [context createCGImage:outputImage fromRect:outputImage.extent];
NSLog(#"check cgimg: %#", cgimg);
UIImage *newImage = [UIImage imageWithCGImage:cgimg];
NSLog(#"check newImge: %#", newImage);
CGImageRelease(cgimg);
return newImage;
}
EDIT : using these filters on a reduced sized image is working now, but still, it would be good if I why was some memory not being released before.
Add this line in at top most of completeEditingUsingOutputImage: method
CIContext *context = [CIContext contextWithOptions:nil];
Also this is how get CIImage:
CIImage *outputImage = [tintFilter valueForKey:#"outputImage"];

Is it possible to draw Graphics with CGContext and save them into a NSMutableArray?

At first I draw graphics:
-(void)drawRect: (CGRect)rect{
//Circle
circle = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(circle, [UIColor darkGrayColor].CGColor);
CGContextFillRect(circle,CGRectMake(10,10, 50, 50));
//rectangle
rectangle = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(rectangle, [UIColor darkGrayColor].CGColor);
CGContextFillRect(rectangle, CGRectMake(10, 10, 50, 50));
//square
square = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(square, [UIColor darkGrayColor].CGColor);
CGContextFillRect(square, CGRectMake(100, 100, 25, 25));
//triangle
triangle = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(triangle, [UIColor darkGrayColor].CGColor);
CGPoint points[6] = { CGPointMake(100, 200), CGPointMake(150, 250),
CGPointMake(150, 250), CGPointMake(50, 250),
CGPointMake(50, 250), CGPointMake(100, 200) };
CGContextStrokeLineSegments(triangle, points, 6);
}
And now I want to put the graphics into an Array:
-(void)viewDidLoad {
grafics = [[NSMutableArray alloc]initWithObjects: circle,rectangle,square,triangle];
[self.navigationItem.title = #"Shape"];
[super viewDidLoad];
}
The problem is, that I have to make a cast thereby the objects fit into this array. Another problem is that the UITableViewController does not load the array into the rows. There is nothing wrong with the tableview, but a failure with handling CGContext. What I am doing wrong?
drawRect: is a method on UIView used to draw the view itself, not to pre-create graphic objects.
Since it seems that you want to create shapes to store them and draw later, it appears reasonable to create the shapes as UIImage and draw them using UIImageView. UIImage can be stored directly in an NSArray.
To create the images, do the following (on the main queue; not in drawRect:):
1) create a bitmap context
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
2) get the context
CGContextRef context = UIGraphicsGetCurrentContext();
3) draw whatever you need
4) export the context into an image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
5) destroy the context
UIGraphicsEndImageContext();
6) store the reference to the image
[yourArray addObject:image];
Repeat for each shape you want to create.
For details see the documentation for the above mentioned functions. To get a better understanding of the difference between drawing in drawRect: and in arbitrary place in your program and of working with contexts in general, I would recommend you read the Quartz2D Programming Guide, especially the section on Graphics Contexts.
First, UIGraphicsGetCurrentContext returns CGContextRef, which is not an object and cannot be stored directly in a collection (edit: this is not really true, see the comments). Technically speaking you could wrap the context in an NSValue or turn it into an UIImage:
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage *img = [UIImage imageWithCGImage:imageRef];
The resulting UIImage is an object and can be stored in a collection just fine. But the code sample does not make sense in the big picture. The -drawRect: method is meant to draw your component, not create some graphics to be used later. And -viewDidLoad is a UIViewController method, not a UIView one. Could you edit the question and describe what you are trying to do?

issue in setting UIViews, view.bounds.origin.y while using in layer renderInContext:

I am trying to crop and then transform a UIView to a pdf file. The UIView is cropping correctly for x component, width and height. But it is taking the same y component,ie 0 for rendering.I want to crop the image 110 points from top. This is my code
UIView *tempV;
tempV=self.view;
CGRect fram= tempV.bounds;
fram.origin.x=537;
fram.origin.y=110;
fram.size.width=404;
fram.size.height=772;
tempV.bounds=fram;
NSLog(#"Mail");
NSLog(#"%f,%f,%f,%f",tempV.bounds.origin.x,tempV.bounds.origin.y,tempV.bounds.size.width,tempV.bounds.size.height);
NSMutableData *pdfData=[NSMutableData data];
UIGraphicsBeginPDFContextToData(pdfData, tempV.bounds, nil);
UIGraphicsBeginPDFPage();
CGContextRef pdfContext= UIGraphicsGetCurrentContext();
[tempV.layer renderInContext:pdfContext];
UIGraphicsEndPDFContext();
MFMailComposeViewController *mailComposer=[[[MFMailComposeViewController alloc]init] autorelease];
mailComposer.mailComposeDelegate=self;
[mailComposer addAttachmentData: pdfData mimeType: #"application/pdf" fileName: #"Dudel creation.pdf"];
[pdfData writeToFile:#"Dudel creation.pdf" atomically:YES];
[self presentModalViewController: mailComposer animated: YES];
You are doing something very wrong in your initial part of your code... I don't even want to go there, but let me break down a few things:
1) UIGraphicsBeginPDFContextToData second parameter is a CGRect.
2) From what I've understood, you want a very specific rectangle of what's showing on your screen and although its center is completely different from your view controller's view (you're trying to change both origin and size). So, why create a dependency on your view controller's view's bounds? (remember bounds and center always go hand in hand).
3) So why not just get rid of the initial part of your code and do this:
CGRect fram = CGRectMake (537, 110, 404, 772); // A rectangle with no other dependency, since you want one very specific.
NSMutableData *pdfData=[NSMutableData data];
UIGraphicsBeginPDFContextToData(pdfData, fram, nil); // Passing the newly created rectangle as the second parameter to the function.
UIGraphicsBeginPDFPage();
CGContextRef pdfContext= UIGraphicsGetCurrentContext();
[tempV.layer renderInContext:pdfContext];
UIGraphicsEndPDFContext();
MFMailComposeViewController *mailComposer=[[[MFMailComposeViewController alloc]init] autorelease];
mailComposer.mailComposeDelegate=self;
[mailComposer addAttachmentData: pdfData mimeType: #"application/pdf" fileName: #"Dudel creation.pdf"];
[pdfData writeToFile:#"Dudel creation.pdf" atomically:YES];
[self presentModalViewController: mailComposer animated: YES];

Drawing a view hierarchy into a specific context in Cocoa

For part of my application I have a need to create an image of a certain view and all of its subviews.
To do this I'm creating a context that wraps a bitmap with the same-size as the view, but I'm unsure how to draw the view hierarchy into it. I can draw a single view just be setting the context and explicitly calling drawRect, but this does not deal with all of the subviews.
I can't see anything in the NSView interface that could help with this so I suspect the solution may lie at a higher level.
I found that writing the drawing code myself was the best way to:
deal with potential transparency issues (some of the other options do add a white background to the whole image)
performance was much better
The code below is not perfect, because it does not deal with scaling issues when going from bounds to frames, but it does take into account the isFlipped state, and works very well for what I used it for. Note that it only draws the subviews (and the subsubviews,... recursively), but getting it to also draw itself is very easy, just add a [self drawRect:[self bounds]] in the implementation of imageWithSubviews.
- (void)drawSubviews
{
BOOL flipped = [self isFlipped];
for ( NSView *subview in [self subviews] ) {
// changes the coordinate system so that the local coordinates of the subview (bounds) become the coordinates of the superview (frame)
// the transform assumes bounds and frame have the same size, and bounds origin is (0,0)
// handling of 'isFlipped' also probably unreliable
NSAffineTransform *transform = [NSAffineTransform transform];
if ( flipped ) {
[transform translateXBy:subview.frame.origin.x yBy:NSMaxY(subview.frame)];
[transform scaleXBy:+1.0 yBy:-1.0];
} else
[transform translateXBy:subview.frame.origin.x yBy:subview.frame.origin.y];
[transform concat];
// recursively draw the subview and sub-subviews
[subview drawRect:[subview bounds]];
[subview drawSubviews];
// reset the transform to get back a clean graphic contexts for the rest of the drawing
[transform invert];
[transform concat];
}
}
- (NSImage *)imageWithSubviews
{
NSImage *image = [[[NSImage alloc] initWithSize:[self bounds].size] autorelease];
[image lockFocus];
// it seems NSImage cannot use flipped coordinates the way NSView does (the method 'setFlipped:' does not seem to help)
// Use instead an NSAffineTransform
if ( [self isFlipped] ) {
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:0 yBy:NSMaxY(self.bounds)];
[transform scaleXBy:+1.0 yBy:-1.0];
[transform concat];
}
[self drawSubviews];
[image unlockFocus];
return image;
}
You can use -[NSView dataWithPDFInsideRect:] to render the entire hierarchy of the view you send it to into a PDF, returned as an NSData object. You can then do whatever you wish with that, including render it into a bitmap.
Are you sure you want a bitmap representation though? After all, that PDF could be (at least in theory) resolution-independent.
You can use -[NSBitmapImageRep initWithFocusedViewRect:] after locking focus on a view to have the view render itself (and its subviews) into the given rectangle.
What you want to do is available explicitly already. See the section "NSView Drawing Redirection API" in the 10.4 AppKit release notes.
Make an NSBitmapImageRep for caching and clear it:
NSGraphicsContext *bitmapGraphicsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:cacheBitmapImageRep];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:bitmapGraphicsContext];
[[NSColor clearColor] set];
NSRectFill(NSMakeRect(0, 0, [cacheBitmapImageRep size].width, [cacheBitmapImageRep size].height));
[NSGraphicsContext restoreGraphicsState];
Cache to it:
-[NSView cacheDisplayInRect:toBitmapImageRep:]
If you want to more generally draw into a specified context handling view recursion and transparency correctly,
-[NSView displayRectIgnoringOpacity:inContext:]