Reputation: 2113
Suppose I have an image that was rendered by drawing another image into it with a Bezier path used to clip the drawing. What I'd like is to be able to render the resulting image in such a way that the edges of the new image look like they're beveled. I'm thinking that something that draws the Bezier path with some line thickness that varies the color with the angle of the path is what I want. Is there a function that automates this process? I'm thinking there must be, or else there wouldn't be such an array of buttons and so forth that are rendered this way.
The app icons, for example, are an illustration of what I'm looking for. They have a sort of drop shadow on the lower right and a highlight at upper left.
Upvotes: 0
Views: 1292
Reputation: 8502
I've written a beveling algorithm for reasonably simple shapes. The algorithm will take a closed path, bevel it (45° inset) and then apply provided highlight/shadow colors given a specific light source angle. It's not perfect and on really complex shapes you may not get the effect you desire, but I've found it works on most "normal" shapes (squares, circles, triangles, round-rects, and even stars, gears and some slightly more complex shapes). The algorithm will attempt to apply the highlight/shadows by calculating where the other sides of the path intersect with the light rays of the light source. I've written a blog post about it here: http://aaronhayman.com/2012/beveling-shapes-in-core-graphics/, but the blog post is a little out of date from the actual code, which I've improved since I first wrote that post. You can find the code here: https://gist.github.com/ahayman/2830483. The code is written in straight "c", and is designed to be as fast and compact as possible.
A few things to note:
UIColor *highlight = [UIColor colorWithWhite:1 alpha:.25f];
UIColor *shadow = [UIColor colorWithWhite:0 alpha:.25f];
Alternative
You can also cheat a little and draw a gradient in a stroked path. I think a lot of people don't ever notice this little Core Graphics function but CGPathCreateCopyByStrokingPath
is a fantastic little function. In effect, it lets you create a new path by stroking another path. You can then take the returned path and apply a gradient to it to create a "bevelled look". As an example, let say I have a UIBezierPath *path
variable that represents the bezier path you want to bevel.
UIBezierPath *path = [self imageClippingPath];
CGPathRef stroke = CGPathCreateCopyByStrokingPath(path.CGPath, NULL, 2.0f, path.lineCapStyle, path.lineJoinStyle, path.miterLimit);
UIBezierPath *strokedPath = [UIBezierPath bezierPathWithCGPath:stroke];
[self drawGradientInPath:strokedPath inContext:context];
CGPathRelease(stroke);
Note that if you clip to the path, you'll get a stroke that is only 1.0f wide, since strokes are made around the path and the clip will cut the stroke in half.
You can create an even better "bevel" by creating two separate stroked paths of different widths: say an "inner" bevel with a width of 4.0f and an "outer" bevel with a width of 2.0f. You would first draw the "inner" bevel and then layer the outer bevel (which is half the width of the inner) over it. If you also clip your context to the path you're stroking, you would end up two separate bevels, each 1.0f in size. Remember that strokes are made around the path. So only half the stoke will be inside the clipped area. If you fill in the inner bevel with a different gradient than the out bevel, you can create an inset-button-look that mimics many of Apple's own. After looking closely at Apple's buttons, I'm pretty sure this is exactly what they do (or something very similar) to get a bevelled look.
Upvotes: 3
Reputation: 26345
It depends on exactly what you're looking for. You can use a 2D convolution kernel, similar to what's described in this article.
I'm not sure if it's available on iOS, but Core Image come with the CIHeightFieldFromMask filter, which produces something like what you want.
Upvotes: 0