« Drop Shadows

// Introduction

Now that we have Rotate using controls and mouse events to handle all of our transformations, let's jazz up the drawing. We'll do it by adding a drop shadow to the path that we're drawing. Sounds difficult, right? Thanks to CoreGraphics and ShadowView, a NSView subclass by Andrew Zamler-Carhart, it's a fairly simple item to add.

For this tutorial, you'll need to download ShadowView.h and ShadowView.m. ShadowView adds the shadow functionality that's built into CoreGraphics to NSView. It works in a straightforward manner that I'll leave you to investigate on your own. This is after all Cocoa and we don't need to reinvent the wheel every time we reuse a feature.

WARNING: The API that is being accessed in this tutorial is a private, undocumented API. Use with caution. This functionality may change in a future system update.

Figure 1: The path shadowed.

// Preliminaries

Before we get started on implementing the shadows, let's get the interface tweaks out of the way. Drop shadows have four input parameters that define their appearance. We'll add a slider with a text field for each one. In addition, we will add a check box control to draw the bezier path as a filled shape. Lay out the controls as shown in Figure 2.

Figure 2: Control Layout

The following outlets and actions need to be added to the Controller class and connected to the appropriate control.

// Mutating Access

Now that we have the Controller completed, we have to add all the mutator and accessor methods that we called and their instance variables to RotateView. Add the following variables and create their methods with the given format.

// Where the Shadows Lie

Now that the boring stuff is out of the way, let's start the fun stuff. The first thing we have to do is change RotateView to be a subclass of ShadowView instead of NSView. To do this we'll change @interface RotateView : NSView in RotateView.h to span class="code">@interface RotateView : ShadowView

Finally, all we have to do is state what we'll draw shadows of. We do this in the drawRect method. At the top of the method we call showShadowHeight:radius:azimuth:ka: and pass to it the four values we have stored. This will turn the shadows on and anything that is drawn after it will have a shadow. We then draw everything out the way we previously had been. At the end of drawRect we call the hideShadow method to turn the shadows off.

// Shadow Anatomy 101

So, now that we're passing the variables all over the place, what do they mean? Let's look at what each one is and how it affects the shadow.

The first parameter is height. It controls the amount of separation between the object and its shadow. As the height value is increased, the shadow moves away from the object as if the object were being raised off the surface it is resting on.

The third parameter is the azimuth. It controls the direction from which the light originates. The shadow will therefore fall in the opposite direction. For example, if the azimuth is set to 45 degrees, the shadow will fall at 225 degrees.

The final parameter is Ka. It is the ambient light coefficient which controls how much ambient light exists. In a nutshell, it will control how dark the shadow is. At a value of zero, there is no ambient light. Only the main light source illuminates the surface, which creates a shadow with maximum darkness. When the parameter is one, the ambient light is at the same intensity as the main light source. The result is no visible shadow since the ambient light will illuminate the shadowed area to the same level that the main light source illuminates the area outside the shadow.

// Conclusion

That's all it takes to shadow your drawings. If you get some really strange and unexpected results, make sure that hideShadow was called after your drawing routine. It will probably be the main point of error when using ShadowView.

You can also download a copy of my project from here. Tom Waters also has the program TestShadow available that gives a view to experiment with the four parameters.

Comments

Many of the links in this article are broken. For example, those to ShadowView.[hm] & Rotate.sit