Horizontal Gradient with a rounded rect path? - cocoa-touch

I have a custom UIView I'm drawing using CoreGraphics. Using CoreGraphics, how would I set a horizontal gradient and clip it to my rounded rect path while still using a shadow? I wrote as much as the path and I can fill it with a color using setFill, but the gradient (a horizontal one, too) isn't abiding by the path...
EDIT: Since the time of posting (not so long ago), I figured out how to do a complex horizontal gradient, so now my only problem is drawing it within my CGContext's path.

Well it seems I've found my own answer:
CGContextAddPath(context, path);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPt, endPt, 0);
Those lines of code perfectly clip it to the path of a rounded rect, or any path desired.

Related

NSBezierPath drawing

I want to do a rounded rectangle outline on an NSImage and I figured that using NSBezierPath would be the best way. However, I ran into a problem: instead of drawing a nice curve, I get this:
For reasons I can't understand, NSBezierPath is drawing the rounded part with a darker color than the rest.
Here's the code I'm using (inside a drawRect: call on a custom view):
NSBezierPath* bp = [NSBezierPath bezierPathWithRoundedRect: self.bounds xRadius: 5 yRadius: 5];
[[[NSColor blackColor] colorWithAlphaComponent: 0.5] setStroke];
[bp stroke];
Any ideas?
Edit:
If I inset the path by 0.5 everything draws just fine. But why is it that I get this when I offset the path by 10 pixels (for example)?
If I understand correctly, it should draw a thin line as well...
Many rendering systems are derived from the PostScript drawing model. Core Graphics is one of these derivative systems. (Here are some others: PDF, SVG, the HTML Canvas 2D Context, Cairo.)
All of these systems have the idea of stroking a path with a line of some fixed width. When you stroke the path, the line straddles the path: half of the line's width is on one side of the path, and half of the line's width is on the other side. Here's a diagram that may make this clearer:
Now, what happens when you stroke a path that lies along the boundary of your view? Half of the stroke falls outside of your view's bounds and is clipped away - not drawn. You only see the half of the stroke that falls inside the view's bounds.
When you use a rounded corner, that corner pulls away from the view's boundary, toward its center, so more of the stroke around the corner falls inside the view's boundary. So the stroke appears to get thicker around the rounded corner, like this:
To fix this, you need to inset your path by half the line width, so that the entire stroke falls inside your view's bounds along the entire path. The default line width is 1.0, so:
NSBezierPath* bp = [NSBezierPath bezierPathWithRoundedRect:
NSRectInset(self.bounds, 0.5, 0.5) xRadius:5 yRadius:5];
In iOS field, just minus the radius of the circle to prevent from being clipped.
UIBezierPath *roundPath = [UIBezierPath bezierPath];
[roundPath addArcWithCenter:
CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)
radius:(self.frame.size.width / 2 - 0.5)
startAngle:M_PI_2 endAngle:M_PI * 3 / 2.f clockwise:YES];

Smoothing looks different than system's, when using NSString drawAtPoint:

I have a custom status item view where I draw a string using NSString's drawAtPoint:withAttributes:. When comparing to the system clock with the same text, it seems that my text is missing subpixel smoothing and looks sort of grainy. I found an advice to shift drawing point from (x,y) to (x+0.5,y+0.5), but it did not help. Default view and setTitle: produce the same result.
That's how it looks:
Seems that system clock has some light gray border below, but I could not imitate it by drawing a string the second time with light gray color.
I don't see any "font smoothing" in the system's rendering, or in any menu titles on my machine. If it was turned on, you'd see red and blue tinted pixels at the edges of the characters, instead of just gray. The difference is quite obvious when zoomed in.
You may want to experiment with turning subpixel positioning and quantization on or off, using
CGContextSetShouldSubpixelPositionFonts, CGContextSetShouldSubpixelQuantizeFonts, etc.
Otherwise, the main difference really is that faint white shadow in the system's rendering. Try setting the context's shadow to an offset of {0,1} (or maybe {0,-1} if your context is flipped?), blur of 0 or 1, and a color of 100% white at 30% alpha -- that looks pretty close to me.
Try:
CGContextRef ctx = [NSGraphicsContext currentContext].graphicsPort;
CGContextSetShouldSmoothFonts(ctx, true);
I was searching for that a long time, too. Got it from a WWDC video (I think it was "Best Practices for Cocoa Animation")
I guess you use
UIGraphicsBeginImageContext(<#CGSize size#>);
for context initialization. On retina displays it leads to blured renderings of fonts. Use
UIGraphicsBeginImageContextWithOptions(myFrameSize, NO, 0.0f);
instead to fix it.
Also, it seems if you use a Layer, set yourself as the layer delegate and do your drawing in:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
NSGraphicsContext *nsGraphicsContext;
nsGraphicsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx
flipped:NO];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:nsGraphicsContext];
// drawing here...
[NSGraphicsContext restoreGraphicsState];
}
It will also work.

How does CGContextFillPath in Objective C work?

I have the following code which is supposed to draw a stroked and filled rectangle but the fill won't show up.
[[UIColor greenColor] setStroke];
[[UIColor brownColor] setFill];
CGContextBeginPath(context);
CGContextMoveToPoint(context, right, bottom);
CGContextAddLineToPoint(context, right,top);
CGContextAddLineToPoint(context,left, top);
CGContextAddLineToPoint(context, left, bottom);
CGContextAddLineToPoint(context, right, bottom);
CGContextStrokePath(context);
CGContextFillPath(context);
The stroke works and I get a nice green rectangle with no fill (or a white fill). This is within a UIView for iOS. Seems very simple and it's driving me nuts!
The right way to do this is to set the drawing mode to include both a fill and a path.
CGPathDrawingMode mode = kCGPathFillStroke;
CGContextClosePath( context ); //ensure path is closed, not necessary if you know it is
CGContextDrawPath( context, mode );
You can use CGContextFillPath() to simply do a fill, but it's basically just CGContextDrawPath() that automatically calls CGContextClosePath() and uses kCGPathFill as the CGPathDrawingMode. You might as well always use CGContextDrawPath() and put in your own parameters to get the type of fill/stroke that you want. You can also put holes in convex paths by using one of the Even-Odd drawing modes.
From the docs on CGContextStrokePath:
Quartz uses the line width and stroke color of the graphics state to
paint the path. As a side effect when you call this function, Quartz
clears the current path.
Probably you should call CGContextClosePath before you call CGContextFillPath

Getting a CALayer's rounded corner content area outline as a clipping path

I can create a CALayer using [CALayer layer] and then have rounded corners using layer.cornerRadius = x.
After I do this, I have a rounded rectangle layer. Is it possible for me to extract this rounded rectangle outline as a path without re-creating the path myself?
If you just want the path then surely it's easy enough to just make one?
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:layer.bounds
cornerRadius:layer.cornerRadius];
If you need to use this in CoreGraphics then just ask for it's CGPath
roundedRect.CGPath;

How do I clip or change alpha of an image (pixels) in Quartz?

I'm working on making an iPhone App where there are two ImageViews and when you touch the top one, wherever you tapped, the bottom one shows instead.
Basically what I want to do is cut an ellipse/roundedrect out of an image. To do this I was thinking on either clipping the image, or changing the alpha pixels in the rect to zero. I am new to Quartz 2D Programming so I am not sure how to do this.
Assuming I have:
UIImageView *topImage;
UIImageView *bottomImage;
How do I delete a CGRect/Ellipse/RoundedRect from these images.
This is kind of like those lottery tickets that you have to scratch off to reveal if you won.
I would generally try to make a mask from a path (here containing a rounded rectangle), then masking the image with it, as demonstrated in the apple docs. The one of the benefits of this is that for hit testing all you need to do is CGPathContainsPoint with the point that was touched (as in it will test whether it was in the visible area of the image).
I tried this code:
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect frame = CGRectMake(100, 100, 100, 100);
CGPathRef roundedRectPath = [self newPathForRoundedRect:frame radius:5];
CGContextAddPath(ctx, roundedRectPath);
CGContextClip (ctx);
CGPathRelease(roundedRectPath);
(Together with the rounded rect path function you sent)
This is on a white view and beneath the view there is a gray Window, so I thought this would just show gray instead of white in CGRect frame but it didn't do anything...