From 56609847ca6b8f398c7a02815886ee1368c04163 Mon Sep 17 00:00:00 2001 From: Sean Heber Date: Tue, 12 Jun 2012 10:54:48 -0500 Subject: [PATCH] Much like UIImage, UIColor needed multiple internal representations in order to work correctly when switching between retina and non-retina displays. Obviously normally color would not need this, but the pattern images proved difficult to get right and this seemed to be the best way. If a color is created using just a normal solid color, only one representation is used so there's not a significant increase in complexity or memory use in the normal case. --- UIKit/Classes/UIColor+UIPrivate.h | 38 ++++++++ UIKit/Classes/UIColor.h | 6 +- UIKit/Classes/UIColor.m | 123 +++++++++++++------------- UIKit/Classes/UIColorRep.h | 47 ++++++++++ UIKit/Classes/UIColorRep.m | 142 ++++++++++++++++++++++++++++++ 5 files changed, 289 insertions(+), 67 deletions(-) create mode 100644 UIKit/Classes/UIColor+UIPrivate.h create mode 100644 UIKit/Classes/UIColorRep.h create mode 100644 UIKit/Classes/UIColorRep.m diff --git a/UIKit/Classes/UIColor+UIPrivate.h b/UIKit/Classes/UIColor+UIPrivate.h new file mode 100644 index 00000000..8e1a59ea --- /dev/null +++ b/UIKit/Classes/UIColor+UIPrivate.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2012, The Iconfactory. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of The Iconfactory nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE ICONFACTORY BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "UIColor.h" + +@class UIColorRep; + +@interface UIColor (UIPrivate) +- (id)_initWithRepresentations:(NSArray *)reps; +- (UIColorRep *)_bestRepresentationForProposedScale:(CGFloat)scale; +- (BOOL)_isOpaque; +@end diff --git a/UIKit/Classes/UIColor.h b/UIKit/Classes/UIColor.h index 59b7c5ac..a3a9d51f 100644 --- a/UIKit/Classes/UIColor.h +++ b/UIKit/Classes/UIColor.h @@ -29,15 +29,13 @@ #import -@class UIImage, NSColor; +@class UIImage; @interface UIColor : NSObject { @private - CGColorRef _color; + id _representations; } -- (id)initWithNSColor:(NSColor *)c; - + (UIColor *)colorWithWhite:(CGFloat)white alpha:(CGFloat)alpha; + (UIColor *)colorWithHue:(CGFloat)hue saturation:(CGFloat)saturation brightness:(CGFloat)brightness alpha:(CGFloat)alpha; + (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; diff --git a/UIKit/Classes/UIColor.m b/UIKit/Classes/UIColor.m index 8c75fd32..ef3f1a11 100644 --- a/UIKit/Classes/UIColor.m +++ b/UIKit/Classes/UIColor.m @@ -27,52 +27,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import "UIColor.h" -#import "UIImage.h" +#import "UIColor+UIPrivate.h" +#import "UIColorRep.h" +#import "UIImage+UIPrivate.h" #import "UIGraphics.h" #import #import -// callback for CreateImagePattern. -static void drawPatternImage(void *info, CGContextRef ctx) -{ - CGImageRef image = (CGImageRef)info; - CGContextDrawImage(ctx, CGRectMake(0,0, CGImageGetWidth(image),CGImageGetHeight(image)), image); -} - -// callback for CreateImagePattern. -static void releasePatternImage(void *info) -{ - CGImageRelease((CGImageRef)info); -} - -static CGPatternRef CreateImagePattern(CGImageRef image) -{ - NSCParameterAssert(image); - int width = CGImageGetWidth(image); - int height = CGImageGetHeight(image); - static const CGPatternCallbacks callbacks = {0, &drawPatternImage, &releasePatternImage}; - return CGPatternCreate (CGImageRetain(image), - CGRectMake (0, 0, width, height), - ((floorf(NSAppKitVersionNumber) == NSAppKitVersionNumber10_7)? CGAffineTransformMake (1, 0, 0, -1, 0, height) : CGAffineTransformIdentity), - width, - height, - kCGPatternTilingConstantSpacing, - true, - &callbacks); -} - -static CGColorRef CreatePatternColor(CGImageRef image) -{ - CGPatternRef pattern = CreateImagePattern(image); - CGColorSpaceRef space = CGColorSpaceCreatePattern(NULL); - CGFloat components[1] = {1.0}; - CGColorRef color = CGColorCreateWithPattern(space, pattern, components); - CGColorSpaceRelease(space); - CGPatternRelease(pattern); - return color; -} - static UIColor *BlackColor = nil; static UIColor *DarkGrayColor = nil; static UIColor *LightGrayColor = nil; @@ -95,19 +56,21 @@ - (id)initWithNSColor:(NSColor *)aColor { if (!aColor) { [self release]; - return nil; - } else if ((self=[super init])) { + self = nil; + } else { NSColor *c = [aColor colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; CGFloat components[[c numberOfComponents]]; [c getComponents:components]; - _color = CGColorCreate([[c colorSpace] CGColorSpace], components); + CGColorRef color = CGColorCreate([[c colorSpace] CGColorSpace], components); + self = [self initWithCGColor:color]; + CGColorRelease(color); } return self; } - (void)dealloc { - if (_color) CGColorRelease(_color); + [_representations release]; [super dealloc]; } @@ -172,27 +135,58 @@ - (id)initWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CG return [self initWithNSColor:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha]]; } -- (id)initWithCGColor:(CGColorRef)ref +- (id)_initWithRepresentations:(NSArray *)reps { - if (!ref) { + if ([reps count] == 0) { [self release]; - return nil; + self = nil; } else if ((self=[super init])) { - _color = CGColorRetain(ref); + _representations = [reps copy]; } return self; } +- (id)initWithCGColor:(CGColorRef)ref +{ + return [self _initWithRepresentations:[NSArray arrayWithObjects:[[[UIColorRep alloc] initWithCGColor:ref] autorelease], nil]]; +} + - (id)initWithPatternImage:(UIImage *)patternImage { - if (!patternImage) { - [self release]; - self = nil; - } else if ((self=[super init])) { - _color = CreatePatternColor(patternImage.CGImage); + NSArray *imageReps = [patternImage _representations]; + NSMutableArray *colorReps = [NSMutableArray arrayWithCapacity:[imageReps count]]; + + for (UIImageRep *imageRep in imageReps) { + [colorReps addObject:[[[UIColorRep alloc] initWithPatternImageRepresentation:imageRep] autorelease]]; } + + return [self _initWithRepresentations:colorReps]; +} - return self; +- (UIColorRep *)_bestRepresentationForProposedScale:(CGFloat)scale +{ + UIColorRep *bestRep = nil; + + for (UIColorRep *rep in _representations) { + if (rep.scale > scale) { + break; + } else { + bestRep = rep; + } + } + + return bestRep ?: [_representations lastObject]; +} + +- (BOOL)_isOpaque +{ + for (UIColorRep *rep in _representations) { + if (!rep.opaque) { + return NO; + } + } + + return YES; } - (void)set @@ -203,22 +197,24 @@ - (void)set - (void)setFill { - CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), _color); + CGContextRef ctx = UIGraphicsGetCurrentContext(); + CGContextSetFillColorWithColor(ctx, [self _bestRepresentationForProposedScale:_UIGraphicsGetContextScaleFactor(ctx)].CGColor); } - (void)setStroke { - CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), _color); + CGContextRef ctx = UIGraphicsGetCurrentContext(); + CGContextSetStrokeColorWithColor(ctx, [self _bestRepresentationForProposedScale:_UIGraphicsGetContextScaleFactor(ctx)].CGColor); } - (CGColorRef)CGColor { - return _color; + return [self _bestRepresentationForProposedScale:1].CGColor; } - (UIColor *)colorWithAlphaComponent:(CGFloat)alpha { - CGColorRef newColor = CGColorCreateCopyWithAlpha(_color, alpha); + CGColorRef newColor = CGColorCreateCopyWithAlpha(self.CGColor, alpha); UIColor *resultingUIColor = [UIColor colorWithCGColor:newColor]; CGColorRelease(newColor); return resultingUIColor; @@ -226,9 +222,10 @@ - (UIColor *)colorWithAlphaComponent:(CGFloat)alpha - (NSColor *)NSColor { - NSColorSpace *colorSpace = [[NSColorSpace alloc] initWithCGColorSpace:CGColorGetColorSpace(_color)]; - const NSInteger numberOfComponents = CGColorGetNumberOfComponents(_color); - const CGFloat *components = CGColorGetComponents(_color); + CGColorRef color = self.CGColor; + NSColorSpace *colorSpace = [[NSColorSpace alloc] initWithCGColorSpace:CGColorGetColorSpace(color)]; + const NSInteger numberOfComponents = CGColorGetNumberOfComponents(color); + const CGFloat *components = CGColorGetComponents(color); NSColor *theColor = [NSColor colorWithColorSpace:colorSpace components:components count:numberOfComponents]; [colorSpace release]; return theColor; diff --git a/UIKit/Classes/UIColorRep.h b/UIKit/Classes/UIColorRep.h new file mode 100644 index 00000000..9251c70c --- /dev/null +++ b/UIKit/Classes/UIColorRep.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2012, The Iconfactory. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of The Iconfactory nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE ICONFACTORY BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +@class UIImageRep; + +@interface UIColorRep : NSObject { + CGColorRef _CGColor; + UIImageRep *_patternImageRep; +} + +- (id)initWithPatternImageRepresentation:(UIImageRep *)patternImageRep; +- (id)initWithCGColor:(CGColorRef)color; + +@property (nonatomic, readonly) CGColorRef CGColor; +@property (nonatomic, readonly) CGFloat scale; +@property (nonatomic, readonly) UIImageRep *patternImageRep; +@property (nonatomic, readonly, getter=isOpaque) BOOL opaque; + +@end diff --git a/UIKit/Classes/UIColorRep.m b/UIKit/Classes/UIColorRep.m new file mode 100644 index 00000000..6d0d4b9b --- /dev/null +++ b/UIKit/Classes/UIColorRep.m @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2012, The Iconfactory. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of The Iconfactory nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE ICONFACTORY BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "UIColorRep.h" +#import "UIImageRep.h" +#import "UIGraphics.h" +#import +#import + +static void drawPatternImage(void *info, CGContextRef ctx) +{ + UIImageRep *rep = [(UIColorRep *)info patternImageRep]; + + UIGraphicsPushContext(ctx); + CGContextSaveGState(ctx); + + const CGRect patternRect = {CGPointZero, rep.imageSize}; + const CGRect deviceRect = CGContextConvertRectToDeviceSpace(ctx, patternRect); + + // this attempts to detect a flipped context and then counter-flips it. + // I don't like this because it seems like it shouldn't be necessary and that I'm missing something more fundamental. + // If a pattern color is used as a backgroundColor on a UIView with no drawRect:, it will set the backgrounColor of + // the view's layer directly with the CGColor (which is made from this pattern image). In that case, the pattern + // appears flipped for reasons I don't fully understand unless I apply this counter-flip transform. If the UIView does + // have a drawRect:, then the different way that the background color is set (UIView draws it directly into the + // CGContext that Core Animation gives it before calling drawRect:), causes the pattern to appear right-side-up. + if (floorf(NSAppKitVersionNumber) != NSAppKitVersionNumber10_7) { + if (CGPointEqualToPoint(patternRect.origin, deviceRect.origin)) { + CGContextTranslateCTM(ctx, 0, patternRect.size.height); + CGContextScaleCTM(ctx, 1, -1); + } + } + + [rep drawInRect:patternRect fromRect:CGRectNull]; + + CGContextRestoreGState(ctx); + UIGraphicsPopContext(); +} + +@implementation UIColorRep +@synthesize patternImageRep = _patternImageRep; + +- (id)initWithPatternImageRepresentation:(UIImageRep *)patternImageRep +{ + if (!patternImageRep) { + [self release]; + self = nil; + } else if ((self=[super init])) { + _patternImageRep = [patternImageRep retain]; + } + return self; +} + +- (id)initWithCGColor:(CGColorRef)color +{ + if (!color) { + [self release]; + self = nil; + } else if ((self=[super init])) { + _CGColor = CGColorRetain(color); + } + return self; +} + +- (void)dealloc +{ + [_patternImageRep release]; + CGColorRelease(_CGColor); + [super dealloc]; +} + +- (CGColorRef)CGColor +{ + if (!_CGColor && _patternImageRep) { + const CGSize imageSize = _patternImageRep.imageSize; + const CGFloat scaler = 1/_patternImageRep.scale; + //const CGAffineTransform t = CGAffineTransformScale(CGAffineTransformMake(1, 0, 0, -1, 0, imageSize.height), scaler, scaler); + const CGAffineTransform t = CGAffineTransformMakeScale(scaler, scaler); + static const CGPatternCallbacks callbacks = {0, &drawPatternImage, NULL}; + + CGPatternRef pattern = CGPatternCreate ((void *)self, + CGRectMake (0, 0, imageSize.width, imageSize.height), + t, + imageSize.width, + imageSize.height, + kCGPatternTilingConstantSpacing, + true, + &callbacks); + + CGColorSpaceRef space = CGColorSpaceCreatePattern(NULL); + CGFloat components[1] = {1.0}; + + _CGColor = CGColorCreateWithPattern(space, pattern, components); + + CGColorSpaceRelease(space); + CGPatternRelease(pattern); + } + + return _CGColor; +} + +- (CGFloat)scale +{ + return _patternImageRep? _patternImageRep.scale : 1; +} + +- (BOOL)isOpaque +{ + if (!_patternImageRep && _CGColor) { + return CGColorGetAlpha(_CGColor) == 1; + } else { + return _patternImageRep.opaque; + } +} + +@end