diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 47111722cc..16d2cb2efd 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -314,6 +314,10 @@ 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */; }; 7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */; }; + 8021EC1C1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8021EC1E1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; + 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; 8B0768B31CE752EC002E1453 /* ASDefaultPlaybackButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */; }; @@ -840,6 +844,8 @@ 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRelativeLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm; sourceTree = ""; }; 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRelativeLayoutSpec.h; path = AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h; sourceTree = ""; }; 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; + 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ASConvenience.h"; sourceTree = ""; }; + 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ASConvenience.m"; sourceTree = ""; }; 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = ""; }; 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = ""; }; 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDefaultPlaybackButton.h; sourceTree = ""; }; @@ -1124,6 +1130,8 @@ 058D09DD195D050800B7D73C /* ASImageNode.h */, 058D09DE195D050800B7D73C /* ASImageNode.mm */, 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */, + 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */, + 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */, 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */, 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */, 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */, @@ -1594,6 +1602,7 @@ AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, 058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */, 92074A671CC8BADA00918F75 /* ASControlNode+tvOS.h in Headers */, + 8021EC1C1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */, 697C0DE31CF38F28001DE0D4 /* ASLayoutValidation.h in Headers */, 68355B3B1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h in Headers */, 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */, @@ -1753,6 +1762,7 @@ A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */, DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */, 9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */, + 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */, 254C6B801BF94DF4003EC431 /* ASEqualityHashHelpers.h in Headers */, B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */, B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */, @@ -2093,6 +2103,7 @@ B30BF6531C5964B0004FCD53 /* ASLayoutManager.m in Sources */, 05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */, ACF6ED211B17843500DA7C62 /* ASDimension.mm in Sources */, + 8021EC1E1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, 058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */, 058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */, 764D83D61C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m in Sources */, @@ -2260,6 +2271,7 @@ B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, + 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, B350621A1B010EFD0018CF92 /* ASDealloc2MainObject.m in Sources */, 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */, diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 0064993276..88547b0694 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -75,15 +75,17 @@ #import #import #import -#import -#import -#import -#import #import #import #import #import +#import +#import +#import +#import +#import + #import #import diff --git a/AsyncDisplayKit/UIImage+ASConvenience.h b/AsyncDisplayKit/UIImage+ASConvenience.h new file mode 100644 index 0000000000..092bdbcd55 --- /dev/null +++ b/AsyncDisplayKit/UIImage+ASConvenience.h @@ -0,0 +1,71 @@ +// +// UIImage+ASConvenience.h +// AsyncDisplayKit +// +// Created by Hannah Troisi on 6/24/16. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import +#import + +// High-performance flat-colored, rounded-corner resizable images +// +// For "Baked-in Opaque" corners, set cornerColor equal to the color behind the rounded image object, e.g. the background color. +// For "Baked-in Alpha" corners, set cornerColor = [UIColor clearColor] +// +// See http://asyncdisplaykit.org/docs/corner-rounding.html for an explanation. + +@interface UIImage (ASDKAdditions) + +/** + * This generates a flat-color, rounded-corner resizeable image + * + * @param cornerRadius The radius of the rounded-corner + * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) + * @param fillColor The fill color of the rounded-corner image + */ ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor; + +/** + * This generates a flat-color, rounded-corner resizeable image with a border + * + * @param cornerRadius The radius of the rounded-corner + * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) + * @param fillColor The fill color of the rounded-corner image + * @param borderColor The border color. Set to nil for no border. + * @param borderWidth The border width. Dummy value if borderColor = nil. + */ ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor + borderColor:(UIColor *)borderColor + borderWidth:(CGFloat)borderWidth; + +/** + * This generates a flat-color, rounded-corner resizeable image with a border + * + * @param cornerRadius The radius of the rounded-corner + * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) + * @param fillColor The fill color of the rounded-corner image + * @param borderColor The border color. Set to nil for no border. + * @param borderWidth The border width. Dummy value if borderColor = nil. + * @param roundedCorners Select individual or multiple corners to round. Set to UIRectCornerAllCorners to round all 4 corners. + * @param scale The number of pixels per point. Provide 0.0 to use the screen scale. + */ ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor + borderColor:(UIColor *)borderColor + borderWidth:(CGFloat)borderWidth + roundedCorners:(UIRectCorner)roundedCorners + scale:(CGFloat)scale; + +@end + diff --git a/AsyncDisplayKit/UIImage+ASConvenience.m b/AsyncDisplayKit/UIImage+ASConvenience.m new file mode 100644 index 0000000000..4b3ce606ec --- /dev/null +++ b/AsyncDisplayKit/UIImage+ASConvenience.m @@ -0,0 +1,131 @@ +// +// UIImage+ASConvenience.m +// AsyncDisplayKit +// +// Created by Hannah Troisi on 6/24/16. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "UIImage+ASConvenience.h" +#import + +@implementation UIImage (ASDKAdditions) + + + + ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor +{ + return [self as_resizableRoundedImageWithCornerRadius:cornerRadius + cornerColor:cornerColor + fillColor:fillColor + borderColor:nil + borderWidth:1.0 + roundedCorners:UIRectCornerAllCorners + scale:0.0]; +} + ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor + borderColor:(UIColor *)borderColor + borderWidth:(CGFloat)borderWidth +{ + return [self as_resizableRoundedImageWithCornerRadius:cornerRadius + cornerColor:cornerColor + fillColor:fillColor + borderColor:borderColor + borderWidth:borderWidth + roundedCorners:UIRectCornerAllCorners + scale:0.0]; +} + ++ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius + cornerColor:(UIColor *)cornerColor + fillColor:(UIColor *)fillColor + borderColor:(UIColor *)borderColor + borderWidth:(CGFloat)borderWidth + roundedCorners:(UIRectCorner)roundedCorners + scale:(CGFloat)scale +{ + static NSCache *__pathCache = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + __pathCache = [[NSCache alloc] init]; + // UIBezierPath objects are fairly small and these are equally sized. 20 should be plenty for many different parameters. + __pathCache.countLimit = 20; + }); + + // Treat clear background color as no background color + if ([cornerColor isEqual:[UIColor clearColor]]) { + cornerColor = nil; + } + + CGFloat dimension = (cornerRadius * 2) + 1; + CGRect bounds = CGRectMake(0, 0, dimension, dimension); + + // This is a hack to make one NSNumber key out of the corners and cornerRadius + if (roundedCorners == UIRectCornerAllCorners) { + // UIRectCornerAllCorners is ~0, but below is equivalent and we can pack it into half an NSUInteger + roundedCorners = UIRectCornerTopLeft | UIRectCornerTopRight | UIRectCornerBottomLeft | UIRectCornerBottomRight; + } + // Left half of NSUInteger is roundedCorners, right half is cornerRadius + UInt64 pathKeyNSUInteger = (UInt64)roundedCorners << sizeof(Float32) * 8; + Float32 floatCornerRadius = cornerRadius; + pathKeyNSUInteger |= (NSUInteger)floatCornerRadius; + + NSNumber *pathKey = [NSNumber numberWithUnsignedLongLong:pathKeyNSUInteger]; + + UIBezierPath *path = nil; + CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius); + + @synchronized(__pathCache) { + path = [__pathCache objectForKey:pathKey]; + if (!path) { + path = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:roundedCorners cornerRadii:cornerRadii]; + [__pathCache setObject:path forKey:pathKey]; + } + } + + // We should probably check if the background color has any alpha component but that + // might be expensive due to needing to check mulitple color spaces. + UIGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); + + if (cornerColor) { + [cornerColor setFill]; + // Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overwrites directly. + UIRectFillUsingBlendMode(bounds, kCGBlendModeCopy); + } + + [fillColor setFill]; + [path fill]; + + if (borderColor) { + [borderColor setStroke]; + + // Inset border fully inside filled path (not halfway on each side of path) + CGRect strokeRect = CGRectInset(bounds, borderWidth / 2.0, borderWidth / 2.0); + + // It is rarer to have a stroke path, and our cache key only handles rounded rects for the exact-stretchable + // size calculated by cornerRadius, so we won't bother caching this path. Profiling validates this decision. + UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect cornerRadius:cornerRadius]; + [strokePath setLineWidth:borderWidth]; + [strokePath stroke]; + } + + UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + UIEdgeInsets capInsets = UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius); + result = [result resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch]; + + return result; +} + +@end \ No newline at end of file