Achieving Inner Shadow
Written by Caylan Larson
Achieving an inner shadow on text elements using Cocoa demands an understanding of masks and clipping. It is not a one-step process. It is complicated. As Aaron Hillegass famed, "you are not stupid, this is difficult."
Use fonts with hard corners for dramatic effects.
Follow along using the example application, Text Effect, to better understand how to correctly clip text with masks to achieve an embossed, inset or bezeled look and feel.
Without PaintCode support, the OSX community needs a way to prototype the inner shadow of text elements.
I'm fascinated with inner shadows. I'm working on a new app that relies heavily on a very small amount of text. I'm also a font zealot, so the display needs to be perfect. I bought Sketch (best screencast here) and PaintCode to help with the process. With Sketch, I mocked up my text interface within minutes. Viola! Now... how do I code it?
I fired up PaintCode and found inner shadows on text was almost impossible. PaintCode can convert the font to static bezier, which will then allow inner shadow. However, I need to dynamically display inner shadowed text - i.e. always changing. That means static bezier curves would not work. I had to "do it myself" which wouldn't be a problem, except for the intimidation that is Core Graphics.
In order to DIY, there are two main resources for learning inner-shadow. As with most things in Cocoa, the iOS community publicly solved the problem that Apple only describes in its most general form. All is well, except UIImage doesn't exist on Mac, and iOS doesn't support NSImage's lockFocus. A leap of faith, and a port. Lucky for us, there's an even better iOS example at Stackoverflow. But even this example, by "Mr. Steven XM", assumed you knew exactly what you were doing.
Here's how it works. First, create a mask, which is nothing more than whiteColor text on blackColor background.
White text on a black background. Yawn.
This is the first in a series of thinking games. Take the time to understand the trick. Think with abstraction. Take what we have drawn (the white text on black) - and use that with an all-black "masking" image. When masked together, the result looks the same, but the white text becomes clear, translucent, or otherwise nil. Some image editors show the null state as checkerboard.
When white text on black is clipped with an all-black mask, the text loses it's image data. The checkerboard is purely for illustrative purposes.
Before we continue, I must confess. I didn't go to school for this and I don't regularly talk to graphic designers on the merits of clipping and masking. My noun and verbs may be wrong. My target audience is Cocoa developers that say, "oh yeah, I know how a mask works" - but they need a more concrete foundation.
Once the text lacks data, we're able to use the "scene" as an image. With Cocoa's shadow drawing, we use the black area as the "thing" to cast a shadow. When you stop and think, this isn't far off from how our brains will perceive the inner shadow. This fact makes inner shadow design appealing. The technique effectively "pushes" the text inside the wall of the computer screen. An inner-shadow adds perceived permanence.
Casting a shadow using the only data in the image: the background.
The next step is the biggest leap of faith. If you've made it here, just one more step - the "grok" is worth it. If you get lost, watch an episode of Arrested Development. It's not a trick, it's an ILLUSION. When this last image (inner shadow on black) is clipped with the mask of the first image (white text on black background) something special happens. POOF. The shadow is masked. Or... unmasked?
Behold, a properly masked shadow! The gray background? May I have your forgiveness?
Keep in mind that the above masked shadow was drawn on the standard window background (this website's background is white). As far as Cocoa is concerned, the only data in the image is shadow, nothing more, nothing less. From our perspective, that's not entirely true. There is something both more and less.
What makes this masking special is that the shadow is generated with the original text's mask.
What do we do with this fancy shadow-only image? First, draw your text plain in a color of your choosing (I choose gray). Then, draw the inner shadow image on top of the plain gray text. It will look like this.
When the shadow is "underneath" the object, the object protrudes from the canvas.
Flip the shadow direction and the innie becomes an outie. The more I stare at this, the more the image plays tricks on me. What do you see?
When the shadow is "on top" of the object, the object sinks into the canvas.
Find my first public offering Text Effect on GitHub.