Working with UIGraphicsPushContext(aContext) and UIGraphicsPopContext() - objective-c

I'm having problems getting the subject calls to work. The below test should draw two orange and one green rectangle.
Here is my understanding of the below code...
I draw a orange rectangle at 50,50
I call the draw greenRect at 200,200, sending the current context
I push the current context on the stack, change the stroke color and draw a green rect at 100,100
I pop the current context which should restore the original context (orange stroke color)
I then draw the last rectangle which should be stroking orange
The last rectangle should stroke orange, but is stroking green, telling me that I modified the original context
Thoughts?
- (void)drawRect:(CGRect)rect{
CGRect aRectangle=CGRectMake(50., 50., 40., 40.);
UIBezierPath *path=[UIBezierPath bezierPathWithRect:aRectangle];
UIColor *strokeColor=[UIColor orangeColor];
[strokeColor setStroke];
[path stroke];
CGContextRef context=UIGraphicsGetCurrentContext();
[self drawGreenRect:context];
CGRect anotherRectangle=CGRectMake(100., 100., 40., 40.);
UIBezierPath *anotherPath=[UIBezierPath bezierPathWithRect:anotherRectangle];
[anotherPath stroke];
}
- (void)drawGreenRect:(CGContextRef)ctxt{
UIGraphicsPushContext(UIGraphicsGetCurrentContext());
CGRect aRectangle=CGRectMake(200., 200., 40., 40.);
UIBezierPath *path=[UIBezierPath bezierPathWithRect:aRectangle];
UIColor *strokeColor=[UIColor greenColor];
[strokeColor setStroke];
[path stroke];
UIGraphicsPopContext();
}

UIGraphicsPushContext() doesn't create a new context for you, it just pushes the context you pass onto a stack. So after you do UIGraphicsPushContext(UIGraphicsGetCurrentContext());, you've got a graphics context stack two deep, but both of the items on it are the same context, the one that was set up by your view for you.
You'll need to actually create a context to push, most likely using UIGraphicsBeginImageContext(). You can then get the image from that context and put it into your view.

Related

CoreGraphics Quartz drawing shadow on transparent alpha path

Searching the web for about 4 hours not getting an answer so:
How to draw a shadow on a path which has transparency?
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(c, 2);
CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
CGContextSetFillColorWithColor(c, [[UIColor colorWithWhite:1.0 alpha:0.8] CGColor]);
// Sample Path
CGContextMoveToPoint(c, 20.0, 10.0);
CGContextAddLineToPoint(c, 100.0, 40.0);
CGContextAddLineToPoint(c, 40.0, 70.0);
CGContextClosePath(c);
CGContextDrawPath(c, kCGPathFillStroke);
}
The first thing I notice, the shadow is only around the stroke. But that isn't the problem so far. The shadow behind the path/rect is still visible, which means: the shadow color is effecting the fill color of my path. The fill color should be white but instead its grey. How to solve this issue?
You will have to clip the context and draw twice.
First you create a reference to your path since you will have to use it a few times and save your graphics context so you can come back to it.
Then you clip the graphics context to a only draw outside of your path. This is done by adding your path to the path that covers the entire view. Once you have clipped you draw your path with the shadow so that it's draw on the outside.
Next you restore the graphics context to how it was before you clipped and draw your path again without the shadow.
It's going to look like this on an orange background (white background wasn't very visible)
The code to do the above drawing is this:
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(c, 2);
CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
CGContextSetFillColorWithColor(c, [[UIColor colorWithWhite:1.0 alpha:0.5] CGColor]);
// Sample Path
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 20.0, 10.0);
CGPathAddLineToPoint(path, NULL, 40.0, 70.0);
CGPathAddLineToPoint(path, NULL, 100.0, 40.0);
CGPathCloseSubpath(path);
// Save the state so we can undo the shadow and clipping later
CGContextSaveGState(c);
{ // Only for readability (so we know what are inside the save/restore scope
CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
CGFloat width = CGRectGetWidth(self.frame);
CGFloat height = CGRectGetHeight(self.frame);
// Create a mask that covers the entire frame
CGContextMoveToPoint(c, 0, 0);
CGContextAddLineToPoint(c, width, 0);
CGContextAddLineToPoint(c, width, height);
CGContextAddLineToPoint(c, 0, height);
CGContextClosePath(c);
// Add the path (which by even-odd rule will remove it)
CGContextAddPath(c, path);
// Clip to that path (drawing will only happen outside our path)
CGContextClip(c);
// Now draw the path in the clipped context
CGContextAddPath(c, path);
CGContextDrawPath(c, kCGPathFillStroke);
}
CGContextRestoreGState(c); // Go back to before the clipping and before the shadow
// Draw the path without the shadow to get the transparent fill
CGContextAddPath(c, path);
CGContextDrawPath(c, kCGPathFillStroke);
}
If you want the entire shadow to be as strong and don't want the transparency of the fill color to make the shadow weaker then you can use a fully opaque color when filling the first time. It's going to get clipped so it won't be visible inside the path anyway. It will only affect the shadow.
Per your request in comments, here's a more in-depth exploration. Consider the following screenshot (StackOverflow shrinks it for me -- it helps to look at it full size.):
What you're seeing here is 5 different drawing approaches (top to bottom) over three different backgrounds (left to right). (I've also dropped the fill alpha from 0.8 to 0.5 to make the effects easier to see.) The three different drawing approaches are (top to bottom):
Just the stroke, not the fill, with a shadow
The way you posted in the code in your original question
The stroke and fill, with no shadow applied
Just the shadow, by itself
The way #DavidRönnqvist proposed in his answer.
The three different backgrounds should be self explanatory.
You said in your original question:
The first thing I notice, the shadow is only around the stroke.
This is why I included the first drawing approach. That's what it really looks like when there is just the stroke, with no fill, and (therefore) only the stroke is being shadowed.
Then, you said:
But that isn't the problem so far. The shadow behind the path/rect is
still visible, which means: the shadow color is effecting the fill
color of my path. The fill color should be white but instead its grey.
Your original code is the next version (#2). What you're seeing there is that the shadow for the stroke is darker than the shadow for the fill. This is because the stroke color's alpha is 1.0 and the fill's alpha is less than 1.0. This might be easier to see in version #4 which is just the shadow -- it's darker around the edge where the stroke is. Version #3 shows the drawing without a shadow. See you you can see the red and the image semi-obsurced in the fill of the shape? So in your original drawing you're seeing the object's own shadow through the object itself.
If that's not making sense, try thinking of a piece of glass that's got a tint to it (if you're into photography, think of a Neutral Density Filter). If you hold that glass between a light source and another surface, and then peek from the side and look just at the lower surface, you know that the semi-transparent glass is going to cast some shadow, but not as dark a shadow as something completely opaque (like a piece of cardboard). This is what you're seeing -- you're looking through the object at it's shadow.
Version #5 is #DavidRönnqvist's approach. The eye-fooling effect I was talking about in my comment is easiest to appreciate (for me, anyway) by looking at the shapes drawn over the image background. What it ends up looking like (in version #5) is that the shape is a bordered, copied, portion of the image that's been overlaid with a semi-transparent white mask of some sort. If you look back at version #3, it's clear, in the absence of the shadow, what's going on: you're looking through the semi-transparent shape at the image beneath. Then if you look at version #4, it's also clear that you have a shadow being cast by an object that's behind your eye/camera. From there, I would argue that that's also clear when looking at version #2 over the image what's going on (even if it's less clear over a solid color). At first glance, my eye/brain doesn't know what it's looking at in version #5 -- there's a moment of "visual dissonance" before I establish the mental model of "copied, masked, portion of the image floating above the original image."
So if that effect (#5) was what you were going for, then David's solution will work great. I just wanted to point out that it's sort of a non-intuitive effect.
Hope this is helpful. I've put the complete sample project I used to generate this screenshot on GitHub.
CGFloat lineWidth = 2.0f;
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSaveGState(c);
CGContextSetLineWidth(c, lineWidth);
CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
CGContextAddRect(c, someRect);
CGContextDrawPath(c, kCGPathStroke);
CGContextRestoreGState(c);
someRect.origin.x += lineWidth/2;
someRect.origin.y += lineWidth/2;
someRect.size.width -= lineWidth;
someRect.size.height -= lineWidth;
CGContextClearRect(c, someRect);
CGContextSetFillColorWithColor(c, [[[UIColor whiteColor] colorWithAlphaComponent:0.8] CGColor]);
CGContextAddRect(c, someRect);
CGContextDrawPath(c, kCGPathFill);
NSShadow* shadow = [[NSShadow alloc] init];
[shadow setShadowColor: [NSColor blackColor]];
[shadow setShadowOffset: NSMakeSize(2.1, -3.1)];
[shadow setShadowBlurRadius: 5];
NSBezierPath* bezierPath = [NSBezierPath bezierPath];
[bezierPath moveToPoint: NSMakePoint(12.5, 6.5)];
[bezierPath curveToPoint: NSMakePoint(52.5, 8.5) controlPoint1: NSMakePoint(40.5, 13.5) controlPoint2: NSMakePoint(52.5, 8.5)];
[bezierPath lineToPoint: NSMakePoint(115.5, 13.5)];
[bezierPath lineToPoint: NSMakePoint(150.5, 6.5)];
[bezierPath lineToPoint: NSMakePoint(201.5, 13.5)];
[bezierPath lineToPoint: NSMakePoint(222.5, 8.5)];
[NSGraphicsContext saveGraphicsState];
[shadow set];
[[NSColor blackColor] setStroke];
[bezierPath setLineWidth: 1];
[bezierPath stroke];
[NSGraphicsContext restoreGraphicsState];

NSView drawRect interfering with subviews?

I have an nsview and i use draw rect to draw an image for background. It also has 3 subviews nsbuttons. The problem is, whenever the mouse is down on a button, the other buttons disappear. But when I remove the draw rect method, this doesn't happen. So I am guessing this has to do with the draw rect method for drawing images.
How can I avoid this?
Thanks.
EDIT:
Ok, i figured out where the problem is. Basically, I have an NSMenuItem, and I am putting a view inside it with 3 buttons. But in NSMenu, at the top, there's a padding of 4 pixels. So, basically, to remove that padding I used the solution provided here:
Gap above NSMenuItem custom view
From the solution there's a line in the drawRect method:
[[NSBezierPath bezierPathWithRect:fullBounds] setClip];
The moment, i remove this line, and the button behave properly. But then, the padding on top doesn't go away.
Here's my drawRect:
- (void) drawRect:(NSRect)dirtyRect {
[[NSGraphicsContext currentContext] saveGraphicsState];
NSRect fullBounds = [self bounds];
fullBounds.size.height += 4;
[[NSBezierPath bezierPathWithRect:fullBounds] setClip];
NSImage *background = [NSImage imageNamed:#"bg.png"];
[background drawInRect:fullBounds fromRect:NSZeroRect operation:NSCompositeCopy fraction:100.0];
[[NSGraphicsContext currentContext] restoreGraphicsState];
}
The solution to the linked question doesn't include saving and restoring the graphics state, which is a good idea when you are modifying one that you didn't create. Give this a try:
- (void)drawRect:(NSRect)dirtyRect {
// Save the current clip rect that has been set up for you
[NSGraphicsContext saveGraphicsState];
// Calculate your fullBounds rect
// ...
// Set the clip rect
// ...
// Do your drawing
// ...
// Restore the correct clip rect
[NSGraphicsContext restoreGraphicsState]
Are you sure that those buttons are actually subviews, and not just placed over the view you're drawing?

How to draw over a subview of NSView

I'm trying to draw at the top of my NSView which has some subviews.
In fact I'm trying to reproduce the connection line style of Interface Builder. Here is the code I'm using for the moment:
- (void)drawRect:(CGRect)dirtyRect
{
// Background color
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
// Draw line
if(_connecting)
{
CGContextRef c = [[NSGraphicsContext currentContext] graphicsPort];
[[NSColor redColor] setStroke];
CGContextMoveToPoint(c, _start.x, _start.y);
CGContextAddLineToPoint(c, _end.x, _end.y);
CGContextSetLineWidth(c, LINE_WIDTH);
CGContextClosePath(c);
CGContextStrokePath(c);
}
}
The first part is to color my NSView (if you know an other way, tell me please 'cause I come from iPhone development and I miss the backgroundColor property of UIView)
Then if a connection if detected, I draw it with 2 NSPoints. This code works but I didn't get it to draw over subviews, only on the first NSView.
A parent view cannot draw over its subviews. You would have to place another view over the subviews and draw the line there.

Using NSImage operation to make a crop effect

I have an NSView that display an image, and i'd like to make this view acts like a cropping image effect. Then i make 3 rectangles (imageRect, secRect and IntersectRect), the imageRect is the rect which show an image, secRect is rect which just act to darken whole imageRect, and the intersectRect is a rect which like an observe rect, what i want to do is like make a "hole" on secRect to see directly into imageRect (without the darken). here's my drawRect method :
- (void)drawRect:(NSRect)rect {
// Drawing code here.
NSImage *image = [NSImage imageNamed:#"Lonely_Tree_by_sican.jpg"];
NSRect imageRect = [self bounds];
[image compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver ];
if (NSIntersectsRect([myDrawRect currentRect], [self bounds])) {
//get the intersectionRect
intersectionRect = NSIntersectionRect([myDrawRect currentRect], imageRect);
//draw the imageRect
[image compositeToPoint:imageRect.origin operation:NSCompositeSourceOver];
//draw the secRect and fill it with black and alpha 0.5
NSRect secRect = NSMakeRect(imageRect.origin.x, imageRect.origin.y, imageRect.size.width, imageRect.size.height);
[[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:0.5] set];
[NSBezierPath fillRect:secRect];
//have no idea for the intersectRect
/*[image compositeToPoint:intersectionRect.origin
fromRect:secLayer
operation:NSCompositeXOR
fraction:1.0];*/
}
//draw the rectangle
[myDrawRect beginDrawing];
}
I have my own class (myDrawRect) to draw a rectangle based on mouse click on [self bounds], so just ignore the beginDrawing command.
Any help would be fine, thanks. Hebbian.
You're doing far more work than you need to, and you're using deprecated methods (the compositeToPoint:operation: and compositeToPoint:fromRect:operation:fraction: methods) to do it.
All you need to do is send the image a single drawInRect:fromRect:operation:fraction: message. The fromRect: parameter is the rectangle you want to crop to; if you don't want to scale the cropped section, then the destination rect (the drawInRect: parameter) should have the same size.
About the only extra work you may need to do is if the image may be bigger than the view and you want to only draw the section that's within the view's bounds: When that happens, you'll need to inset the crop rectangle by the difference in size between the crop rectangle and the view bounds.

Create a colored bubble/circle programmatically in ObjectiveC and Cocoa

Can anyone guide me in the correct way to build a colored bubble/circle programmatically?
I can't use images as I need it to be able to be any color depending on user interaction.
My thought was maybe to make a white circle image and then overlay a color on top of it.
However I am not sure if this would work, or how to really go about it.
If someone could point me the right direction I would appreciate it.
There are a couple steps to drawing something in Cocoa.
First you need a path that will be used to define the object that you are going to be drawing. Take a look here Drawing Fundamental Shapes for a guide on creating paths in Cocoa. You will be most interested in sending the "appendBezierPathWithOvalInRect" message to an "NSBezierPath" object, this takes a rectangle that bounds the circle you want to draw.
This code will create a 10x10 circle at coordinates 10,10:
NSRect rect = NSMakeRect(10, 10, 10, 10);
NSBezierPath* circlePath = [NSBezierPath bezierPath];
[circlePath appendBezierPathWithOvalInRect: rect];
Once you have your path you want to set the color for the current drawing context. There are two colors, stroke and fill; stroke is the outline of the path and the fill is the interior color. To set a color you send "set" to an "NSColor" object.
This sets the stroke to black and the fill to red:
[[NSColor blackColor] setStroke];
[[NSColor redColor] setFill];
Now that you have your path and you have your colors set just fill the path and then draw it:
[path stroke];
[path fill];
All of this will need to be done in a graphics context like in drawRect of a view perhaps. All of this together with a graphics context would look like this:
- (void)drawRect:(NSRect)rect
{
// Get the graphics context that we are currently executing under
NSGraphicsContext* gc = [NSGraphicsContext currentContext];
// Save the current graphics context settings
[gc saveGraphicsState];
// Set the color in the current graphics context for future draw operations
[[NSColor blackColor] setStroke];
[[NSColor redColor] setFill];
// Create our circle path
NSRect rect = NSMakeRect(10, 10, 10, 10);
NSBezierPath* circlePath = [NSBezierPath bezierPath];
[circlePath appendBezierPathWithOvalInRect: rect];
// Outline and fill the path
[circlePath stroke];
[circlePath fill];
// Restore the context to what it was before we messed with it
[gc restoreGraphicsState];
}
You may use simple UIView to create perfect circle with only parameter radius:
// Add framework CoreGraphics.framework
#import <QuartzCore/QuartzCore.h>
-(UIView *)circleWithColor:(UIColor *)color radius:(int)radius {
UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 2 * radius, 2 * radius)];
circle.backgroundColor = color;
circle.layer.cornerRadius = radius;
circle.layer.masksToBounds = YES;
return circle;
}
Create an NSView subclass that holds an NSColor as an ivar. In the drawRect method, create an NSBezierPath of the appropriate size, using the view's bounds. Then set the color [myColor set] and fill the path [myPath fill]. There's a lot more you can do, such as set transparency, a border, and so on and so on, but I'll leave that to the docs unless you have a specific question.
To use the NSView subclass, just drag a view object onto your nib, and choose the name of your subclass in custom class in IB's inspector. You'll need to also set an outlet to it in your controller, so you can change the color as needed.
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(c, 40, 0, 255, 0.1);
CGContextSetRGBStrokeColor(c, 0, 40, 255, 0.5);
// Draw a green solid circle
CGContextSetRGBFillColor(c, 0, 255, 0, 1);
CGContextFillEllipseInRect(c, CGRectMake(100, 100, 25, 25));
Download sketch from apple. http://developer.apple.com/library/mac/#samplecode/Sketch
It can do a lot more, but one of the things is draw circles.