/* |
File: QuartzRendering.m |
Abstract: Demonstrates using Quartz for drawing gradients (QuartzGradientView) and patterns (QuartzPatternView). |
Version: 3.0 |
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
|
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
|
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
|
Copyright (C) 2013 Apple Inc. All Rights Reserved. |
|
*/ |
|
#import "QuartzRendering.h" |
|
#pragma mark - QuartzPatternView |
|
|
// Colored patterns specify colors as part of their drawing |
void ColoredPatternCallback(void *info, CGContextRef context) |
{ |
// Dark Blue |
CGContextSetRGBFillColor(context, 29.0 / 255.0, 156.0 / 255.0, 215.0 / 255.0, 1.00); |
CGContextFillRect(context, CGRectMake(0.0, 0.0, 8.0, 8.0)); |
CGContextFillRect(context, CGRectMake(8.0, 8.0, 8.0, 8.0)); |
|
// Light Blue |
CGContextSetRGBFillColor(context, 204.0 / 255.0, 224.0 / 255.0, 244.0 / 255.0, 1.00); |
CGContextFillRect(context, CGRectMake(8.0, 0.0, 8.0, 8.0)); |
CGContextFillRect(context, CGRectMake(0.0, 8.0, 8.0, 8.0)); |
} |
|
|
// Uncolored patterns take their color from the given context |
void UncoloredPatternCallback(void *info, CGContextRef context) |
{ |
CGContextFillRect(context, CGRectMake(0.0, 0.0, 8.0, 8.0)); |
CGContextFillRect(context, CGRectMake(8.0, 8.0, 8.0, 8.0)); |
} |
|
|
|
@interface QuartzPatternView () |
|
@property (nonatomic, readonly) CGColorRef coloredPatternColor; |
@property (nonatomic, readonly) CGColorSpaceRef uncoloredPatternColorSpace; |
|
@end |
|
|
@implementation QuartzPatternView |
{ |
CGColorRef _coloredPatternColor; |
CGPatternRef _uncoloredPattern; |
CGColorSpaceRef _uncoloredPatternColorSpace; |
} |
|
|
- (CGColorRef)coloredPatternColor |
{ |
if (_coloredPatternColor == NULL) |
{ |
// Colored Pattern setup |
CGPatternCallbacks coloredPatternCallbacks = {0, ColoredPatternCallback, NULL}; |
// First we need to create a CGPatternRef that specifies the qualities of our pattern. |
CGPatternRef coloredPattern = CGPatternCreate( |
NULL, // 'info' pointer for our callback |
CGRectMake(0.0, 0.0, 16.0, 16.0), // the pattern coordinate space, drawing is clipped to this rectangle |
CGAffineTransformIdentity, // a transform on the pattern coordinate space used before it is drawn. |
16.0, 16.0, // the spacing (horizontal, vertical) of the pattern - how far to move after drawing each cell |
kCGPatternTilingNoDistortion, |
true, // this is a colored pattern, which means that you only specify an alpha value when drawing it |
&coloredPatternCallbacks); // the callbacks for this pattern. |
|
// To draw a pattern, you need a pattern colorspace. |
// Since this is an colored pattern, the parent colorspace is NULL, indicating that it only has an alpha value. |
CGColorSpaceRef coloredPatternColorSpace = CGColorSpaceCreatePattern(NULL); |
CGFloat alpha = 1.0; |
// Since this pattern is colored, we'll create a CGColorRef for it to make drawing it easier and more efficient. |
// From here on, the colored pattern is referenced entirely via the associated CGColorRef rather than the |
// originally created CGPatternRef. |
_coloredPatternColor = CGColorCreateWithPattern(coloredPatternColorSpace, coloredPattern, &alpha); |
CGColorSpaceRelease(coloredPatternColorSpace); |
CGPatternRelease(coloredPattern); |
} |
|
return _coloredPatternColor; |
} |
|
|
- (CGPatternRef)uncoloredPattern |
{ |
if (_uncoloredPattern == NULL) |
{ |
CGPatternCallbacks uncoloredPatternCallbacks = {0, UncoloredPatternCallback, NULL}; |
// As above, we create a CGPatternRef that specifies the qualities of our pattern |
_uncoloredPattern = CGPatternCreate( |
NULL, // 'info' pointer |
CGRectMake(0.0, 0.0, 16.0, 16.0), // coordinate space |
CGAffineTransformIdentity, // transform |
16.0, 16.0, // spacing |
kCGPatternTilingNoDistortion, |
false, // this is an uncolored pattern, thus to draw it we need to specify both color and alpha |
&uncoloredPatternCallbacks); // callbacks for this pattern |
} |
|
return _uncoloredPattern; |
} |
|
|
|
-(CGColorSpaceRef)uncoloredPatternColorSpace; |
|
{ |
if (_uncoloredPatternColorSpace == NULL) { |
// With an uncolored pattern we still need to create a pattern colorspace, but now we need a parent colorspace |
// We'll use the DeviceRGB colorspace here. We'll need this colorspace along with the CGPatternRef to draw this pattern later. |
CGColorSpaceRef deviceRGB = CGColorSpaceCreateDeviceRGB(); |
_uncoloredPatternColorSpace = CGColorSpaceCreatePattern(deviceRGB); |
CGColorSpaceRelease(deviceRGB); |
} |
|
return _uncoloredPatternColorSpace; |
} |
|
|
-(void)drawInContext:(CGContextRef)context |
{ |
// Draw the colored pattern. Since we have a CGColorRef for this pattern, we just set |
// that color current and draw. |
CGContextSetFillColorWithColor(context, self.coloredPatternColor); |
CGContextFillRect(context, CGRectMake(10.0, 10.0, 90.0, 90.0)); |
|
// You can also stroke with a pattern. |
CGContextSetStrokeColorWithColor(context, self.coloredPatternColor); |
CGContextStrokeRectWithWidth(context, CGRectMake(120.0, 10.0, 90.0, 90.0), 8.0); |
|
// Since we aren't encapsulating our pattern in a CGColorRef for the uncolored pattern case, setup requires two steps. |
// First you have to set the context's current colorspace (fill or stroke) to a pattern colorspace, |
// indicating to Quartz that you want to draw a pattern. |
CGContextSetFillColorSpace(context, self.uncoloredPatternColorSpace); |
// Next you set the pattern and the color that you want the pattern to draw with. |
CGFloat color1[] = {1.0, 0.0, 0.0, 1.0}; |
CGContextSetFillPattern(context, self.uncoloredPattern, color1); |
// And finally you draw! |
CGContextFillRect(context, CGRectMake(10.0, 120.0, 90.0, 90.0)); |
// As long as the current colorspace is a pattern colorspace, you are free to change the pattern or pattern color |
CGFloat color2[] = {0.0, 1.0, 0.0, 1.0}; |
CGContextSetFillPattern(context, self.uncoloredPattern, color2); |
CGContextFillRect(context, CGRectMake(10.0, 230.0, 90.0, 90.0)); |
|
// And of course, just like the colored case, you can stroke with a pattern as well. |
CGContextSetStrokeColorSpace(context, self.uncoloredPatternColorSpace); |
CGContextSetStrokePattern(context, self.uncoloredPattern, color1); |
CGContextStrokeRectWithWidth(context, CGRectMake(120.0, 120.0, 90.0, 90.0), 8.0); |
// As long as the current colorspace is a pattern colorspace, you are free to change the pattern or pattern color |
CGContextSetStrokePattern(context, self.uncoloredPattern, color2); |
CGContextStrokeRectWithWidth(context, CGRectMake(120.0, 230.0, 90.0, 90.0), 8.0); |
} |
|
|
-(void)dealloc |
{ |
CGColorRelease(_coloredPatternColor); |
CGPatternRelease(_uncoloredPattern); |
CGColorSpaceRelease(_uncoloredPatternColorSpace); |
} |
|
|
@end |
|
|
|
#pragma mark - QuartzGradientView |
|
|
@interface QuartzGradientView () |
|
@property (nonatomic, readonly) CGGradientRef gradient; |
|
@end |
|
|
|
@implementation QuartzGradientView |
{ |
CGGradientRef _gradient; |
} |
|
|
-(CGGradientRef)gradient |
{ |
if(_gradient == NULL) |
{ |
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); |
CGFloat colors[] = |
{ |
204.0 / 255.0, 224.0 / 255.0, 244.0 / 255.0, 1.00, |
29.0 / 255.0, 156.0 / 255.0, 215.0 / 255.0, 1.00, |
0.0 / 255.0, 50.0 / 255.0, 126.0 / 255.0, 1.00, |
}; |
_gradient = CGGradientCreateWithColorComponents(rgb, colors, NULL, sizeof(colors)/(sizeof(colors[0])*4)); |
CGColorSpaceRelease(rgb); |
} |
return _gradient; |
} |
|
|
// Returns an appropriate starting point for the demonstration of a linear gradient |
CGPoint demoLGStart(CGRect bounds) |
{ |
return CGPointMake(bounds.origin.x, bounds.origin.y + bounds.size.height * 0.25); |
} |
|
|
// Returns an appropriate ending point for the demonstration of a linear gradient |
CGPoint demoLGEnd(CGRect bounds) |
{ |
return CGPointMake(bounds.origin.x, bounds.origin.y + bounds.size.height * 0.75); |
} |
|
|
// Returns the center point for for the demonstration of the radial gradient |
CGPoint demoRGCenter(CGRect bounds) |
{ |
return CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); |
} |
|
|
// Returns an appropriate inner radius for the demonstration of the radial gradient |
CGFloat demoRGInnerRadius(CGRect bounds) |
{ |
CGFloat r = bounds.size.width < bounds.size.height ? bounds.size.width : bounds.size.height; |
return r * 0.125; |
} |
|
|
// Returns an appropriate outer radius for the demonstration of the radial gradient |
CGFloat demoRGOuterRadius(CGRect bounds) |
{ |
CGFloat r = bounds.size.width < bounds.size.height ? bounds.size.width : bounds.size.height; |
return r * 0.5; |
} |
|
|
-(CGGradientDrawingOptions)drawingOptions |
{ |
CGGradientDrawingOptions options = 0; |
if (self.extendsPastStart) |
{ |
options |= kCGGradientDrawsBeforeStartLocation; |
} |
if (self.extendsPastEnd) |
{ |
options |= kCGGradientDrawsAfterEndLocation; |
} |
return options; |
} |
|
|
-(void)drawInContext:(CGContextRef)context |
{ |
// Use the clip bounding box, sans a generous border |
CGRect clip = CGRectInset(CGContextGetClipBoundingBox(context), 20.0, 20.0); |
|
CGPoint start, end; |
CGFloat startRadius, endRadius; |
|
// Clip to area to draw the gradient, and draw it. Since we are clipping, we save the graphics state |
// so that we can revert to the previous larger area. |
CGContextSaveGState(context); |
CGContextClipToRect(context, clip); |
|
CGGradientDrawingOptions options = [self drawingOptions]; |
switch(self.type) |
{ |
case kLinearGradient: |
// A linear gradient requires only a starting & ending point. |
// The colors of the gradient are linearly interpolated along the line segment connecting these two points |
// A gradient location of 0.0 means that color is expressed fully at the 'start' point |
// a location of 1.0 means that color is expressed fully at the 'end' point. |
// The gradient fills outwards perpendicular to the line segment connectiong start & end points |
// (which is why we need to clip the context, or the gradient would fill beyond where we want it to). |
// The gradient options (last) parameter determines what how to fill the clip area that is "before" and "after" |
// the line segment connecting start & end. |
start = demoLGStart(clip); |
end = demoLGEnd(clip); |
CGContextDrawLinearGradient(context, self.gradient, start, end, options); |
CGContextRestoreGState(context); |
break; |
|
case kRadialGradient: |
// A radial gradient requires a start & end point as well as a start & end radius. |
// Logically a radial gradient is created by linearly interpolating the center, radius and color of each |
// circle using the start and end point for the center, start and end radius for the radius, and the color ramp |
// inherant to the gradient to create a set of stroked circles that fill the area completely. |
// The gradient options specify if this interpolation continues past the start or end points as it does with |
// linear gradients. |
start = end = demoRGCenter(clip); |
startRadius = demoRGInnerRadius(clip); |
endRadius = demoRGOuterRadius(clip); |
CGContextDrawRadialGradient(context, self.gradient, start, startRadius, end, endRadius, options); |
CGContextRestoreGState(context); |
break; |
} |
|
// Show the clip rect |
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); |
CGContextStrokeRectWithWidth(context, clip, 2.0); |
} |
|
|
-(void)setType:(GradientType)newType |
{ |
if (newType != _type) |
{ |
_type = newType; |
[self setNeedsDisplay]; |
} |
} |
|
|
-(void)setExtendsPastStart:(BOOL)yn |
{ |
if (yn != _extendsPastStart) |
{ |
_extendsPastStart = yn; |
[self setNeedsDisplay]; |
} |
} |
|
|
-(void)setExtendsPastEnd:(BOOL)yn |
{ |
if (yn != _extendsPastEnd) |
{ |
_extendsPastEnd = yn; |
[self setNeedsDisplay]; |
} |
} |
|
|
-(void)dealloc |
{ |
CGGradientRelease(_gradient); |
} |
|
|
@end |