diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index d3e26159d1..3c66687060 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -109,7 +109,7 @@ 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; }; 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */; }; 509E68651B3AEDC5009B9150 /* CoreGraphics+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */; }; + 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */; }; 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.mm in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.mm */; }; 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; 680346941CE4052A0009FEB4 /* ASNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -176,7 +176,7 @@ 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 */; }; 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */; }; + 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */; }; 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A7D9581D44542100BF333E /* ASWeakMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -621,7 +621,7 @@ 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewLayoutController.h; sourceTree = ""; }; 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewLayoutController.m; sourceTree = ""; }; 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CoreGraphics+ASConvenience.h"; sourceTree = ""; }; - 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "CoreGraphics+ASConvenience.mm"; sourceTree = ""; }; + 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CoreGraphics+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTruncationTests.mm; sourceTree = ""; }; @@ -734,7 +734,7 @@ 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 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.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIImage+ASConvenience.mm"; sourceTree = ""; }; + 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ASConvenience.m"; sourceTree = ""; }; 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeSnapshotTests.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 = ""; }; @@ -1234,7 +1234,7 @@ 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */, 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */, - 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */, + 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */, CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */, CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.m */, ); @@ -1422,7 +1422,7 @@ CC3B20871C3F7A5400798563 /* ASWeakSet.h */, CC3B20881C3F7A5400798563 /* ASWeakSet.m */, 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */, - 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */, + 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, DBC452DA1C5BF64600B16017 /* NSArray+Diffing.mm */, CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */, @@ -2405,7 +2405,7 @@ CCA282C51E9EAE630037E8B7 /* ASLayerBackingTipProvider.m in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, - 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.mm in Sources */, + 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, CCAA0B80206ADBF30057B336 /* ASRecursiveUnfairLock.m in Sources */, CCBDDD0620C62A2D00CBA922 /* ASMainThreadDeallocation.mm in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, @@ -2504,7 +2504,7 @@ 6959433F1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */, 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.m in Sources */, CC034A141E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m in Sources */, - 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.mm in Sources */, + 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */, 254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */, 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 5be5334b2d..90f87a2899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ - Reduced binary size by disabling exception support (which we don't use.) [Adlai Holler](https://github.com/Adlai-Holler) - Create and set delegate for clip corner layers within ASDisplayNode [Michael Schneider](https://github.com/maicki) [#1029](https://github.com/TextureGroup/Texture/pull/1029) - Improve locking situation in ASVideoPlayerNode [Michael Schneider](https://github.com/maicki) [#1042](https://github.com/TextureGroup/Texture/pull/1042) -- Optimize drawing code + add examples how to round corners. [Michael Schneider](https://github.com/maicki) + ## 2.7 - Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 0fa7a9eb60..347d41c42c 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -53,7 +53,6 @@ #import #import #import -#import // Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) #if TIME_DISPLAYNODE_OPS @@ -1509,7 +1508,7 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS __instanceLock__.lock(); if (_placeholderLayer.superlayer && !placeholderShouldPersist) { void (^cleanupBlock)() = ^{ - [self->_placeholderLayer removeFromSuperlayer]; + [_placeholderLayer removeFromSuperlayer]; }; if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { @@ -1667,29 +1666,24 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) CGSize size = CGSizeMake(radius + 1, radius + 1); ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay); - CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextRef ctx = UIGraphicsGetCurrentContext(); if (isRight == YES) { - CGContextTranslateCTM(context, -radius + 1, 0); + CGContextTranslateCTM(ctx, -radius + 1, 0); } if (isTop == YES) { - CGContextTranslateCTM(context, 0, -radius + 1); + CGContextTranslateCTM(ctx, 0, -radius + 1); } - - CGMutablePathRef roundedPath = CGPathCreateMutable(); - CGPathRef addedPath = ASCGRoundedPathCreate(CGRectMake(0, 0, radius * 2, radius * 2), radius); - CGPathAddPath(roundedPath, NULL, addedPath); - CGPathAddRect(roundedPath, NULL, CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)); - CGContextAddPath(context, roundedPath); - CGContextSetFillColorWithColor(context, backgroundColor.CGColor); - CGContextEOFillPath(context); - - // No lock needed, as _clipCornerLayers is only modified on the main thread. - _clipCornerLayers[idx].contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); - _clipCornerLayers[idx].bounds = CGRectMake(0.0, 0.0, size.width, size.height); - _clipCornerLayers[idx].anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); + UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius]; + [roundedRect setUsesEvenOddFillRule:YES]; + [roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]]; + [backgroundColor setFill]; + [roundedRect fill]; - CGPathRelease(addedPath); - CGPathRelease(roundedPath); + // No lock needed, as _clipCornerLayers is only modified on the main thread. + CALayer *clipCornerLayer = _clipCornerLayers[idx]; + clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); + clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height); + clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); } [self _layoutClipCornersIfNeeded]; }); @@ -1874,10 +1868,7 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, [self displayDidFinish]; } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - (void)displayWillStart {} -#pragma clang diagnostic pop - (void)displayWillStartAsynchronously:(BOOL)asynchronously { ASDisplayNodeAssertMainThread(); @@ -2988,11 +2979,11 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { if (ASInterfaceStateIncludesVisible(self.pendingInterfaceState)) { void(^exitVisibleInterfaceState)(void) = ^{ // This block intentionally retains self. - self->__instanceLock__.lock(); - unsigned isStillInHierarchy = self->_flags.isInHierarchy; - BOOL isVisible = ASInterfaceStateIncludesVisible(self->_pendingInterfaceState); - ASInterfaceState newState = (self->_pendingInterfaceState & ~ASInterfaceStateVisible); - self->__instanceLock__.unlock(); + __instanceLock__.lock(); + unsigned isStillInHierarchy = _flags.isInHierarchy; + BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState); + ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible); + __instanceLock__.unlock(); if (!isStillInHierarchy && isVisible) { #if ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR if (![self supportsRangeManagedInterfaceState]) { @@ -3151,8 +3142,8 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { [self setDisplaySuspended:YES]; //schedule clear contents on next runloop dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(self->__instanceLock__); - if (ASInterfaceStateIncludesDisplay(self->_interfaceState) == NO) { + ASDN::MutexLocker l(__instanceLock__); + if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { [self clearContents]; } }); @@ -3169,8 +3160,8 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { [[self asyncLayer] cancelAsyncDisplay]; //schedule clear contents on next runloop dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(self->__instanceLock__); - if (ASInterfaceStateIncludesDisplay(self->_interfaceState) == NO) { + ASDN::MutexLocker l(__instanceLock__); + if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { [self clearContents]; } }); diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index 3740991360..d060d8993e 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -737,43 +737,19 @@ asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat { return ^(UIImage *originalImage) { ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); - - CGContextRef context = UIGraphicsGetCurrentContext(); + UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}]; - CGRect rect = (CGRect){CGPointZero, originalImage.size}; - CGMutablePathRef path = CGPathCreateMutable(); - - CGPathAddEllipseInRect(path, NULL, rect); - CGContextAddPath(context, path); - // Make the image round - CGContextClip(context); - - // Although drawAtPoint:blendMode: would consider the CTM already, we are using CGContext* functions for drawing - // the image instead calling drawAtPoint:blendMode. This will save use 50% of retain calls for the image - CGContextSetBlendMode(context, kCGBlendModeCopy); - CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect)); - CGContextScaleCTM(context, originalImage.scale, -originalImage.scale); - CGContextSetAlpha(context, 1.0); - CGContextDrawImage(context, rect, originalImage.CGImage); + [roundOutline addClip]; - CGPathRelease(path); + // Draw the original image + [originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; // Draw a border on top. if (borderWidth > 0.0) { - // Begin a new path for the border - CGContextBeginPath(context); - - CGFloat strokeThickness = borderWidth; - CGFloat strokeInset = floor((strokeThickness + 1.0f) / 2.0f) - 1.0f; - CGPathRef path = CGPathCreateWithEllipseInRect(CGRectInset(rect, strokeInset, strokeInset), NULL); - CGContextAddPath(context, path); - - CGContextSetStrokeColorWithColor(context, borderColor.CGColor); - CGContextSetLineWidth(context, borderWidth); - CGContextStrokePath(context); - - CGPathRelease(path); + [borderColor setStroke]; + [roundOutline setLineWidth:borderWidth]; + [roundOutline stroke]; } return ASGraphicsGetImageAndEndCurrentContext(); diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index b3543ced68..2fabc0dde5 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -130,7 +130,7 @@ #endif #endif -#define AS_OVERLOADABLE __attribute__((overloadable)) +#define ASOVERLOADABLE __attribute__((overloadable)) #if __has_attribute(noescape) diff --git a/Source/Details/CoreGraphics+ASConvenience.h b/Source/Details/CoreGraphics+ASConvenience.h index 6229d4ad2d..9eeef3192f 100644 --- a/Source/Details/CoreGraphics+ASConvenience.h +++ b/Source/Details/CoreGraphics+ASConvenience.h @@ -17,8 +17,8 @@ #import +#import #import -#import #import @@ -56,11 +56,4 @@ ASDISPLAYNODE_INLINE BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CG return fabs(size1.width - size2.width) < delta && fabs(size1.height - size2.height) < delta; }; -AS_OVERLOADABLE AS_WARN_UNUSED_RESULT AS_EXTERN CGPathRef ASCGRoundedPathCreate(CGRect rect, UIRectCorner corners, CGSize cornerRadii); - -AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT CGPathRef ASCGRoundedPathCreate(CGRect rect, CGFloat cornerRadius) { - return ASCGRoundedPathCreate(rect, UIRectCornerAllCorners, CGSizeMake(cornerRadius, cornerRadius)); -} - - NS_ASSUME_NONNULL_END diff --git a/Source/Details/CoreGraphics+ASConvenience.m b/Source/Details/CoreGraphics+ASConvenience.m new file mode 100644 index 0000000000..92169ffe4e --- /dev/null +++ b/Source/Details/CoreGraphics+ASConvenience.m @@ -0,0 +1,19 @@ +// +// CoreGraphics+ASConvenience.m +// Texture +// +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + diff --git a/Source/Details/CoreGraphics+ASConvenience.mm b/Source/Details/CoreGraphics+ASConvenience.mm deleted file mode 100644 index 2a3b6b84ec..0000000000 --- a/Source/Details/CoreGraphics+ASConvenience.mm +++ /dev/null @@ -1,64 +0,0 @@ -// -// CoreGraphics+ASConvenience.m -// Texture -// -// 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 /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -AS_OVERLOADABLE CGPathRef ASCGRoundedPathCreate(CGRect rect, UIRectCorner corners, CGSize cornerRadii) { - CGMutablePathRef path = CGPathCreateMutable(); - - const CGPoint topLeft = rect.origin; - const CGPoint topRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect)); - const CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)); - const CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect)); - - if (corners & UIRectCornerTopLeft) { - CGPathMoveToPoint(path, NULL, topLeft.x+cornerRadii.width, topLeft.y); - } else { - CGPathMoveToPoint(path, NULL, topLeft.x, topLeft.y); - } - - if (corners & UIRectCornerTopRight) { - CGPathAddLineToPoint(path, NULL, topRight.x-cornerRadii.width, topRight.y); - CGPathAddCurveToPoint(path, NULL, topRight.x, topRight.y, topRight.x, topRight.y+cornerRadii.height, topRight.x, topRight.y+cornerRadii.height); - } else { - CGPathAddLineToPoint(path, NULL, topRight.x, topRight.y); - } - - if (corners & UIRectCornerBottomRight) { - CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y-cornerRadii.height); - CGPathAddCurveToPoint(path, NULL, bottomRight.x, bottomRight.y, bottomRight.x-cornerRadii.width, bottomRight.y, bottomRight.x-cornerRadii.width, bottomRight.y); - } else { - CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y); - } - - if (corners & UIRectCornerBottomLeft) { - CGPathAddLineToPoint(path, NULL, bottomLeft.x+cornerRadii.width, bottomLeft.y); - CGPathAddCurveToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomLeft.x, bottomLeft.y-cornerRadii.height, bottomLeft.x, bottomLeft.y-cornerRadii.height); - } else { - CGPathAddLineToPoint(path, NULL, bottomLeft.x, bottomLeft.y); - } - - if (corners & UIRectCornerTopLeft) { - CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y+cornerRadii.height); - CGPathAddCurveToPoint(path, NULL, topLeft.x, topLeft.y, topLeft.x+cornerRadii.width, topLeft.y, topLeft.x+cornerRadii.width, topLeft.y); - } else { - CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y); - } - - CGPathCloseSubpath(path); - return path; -} diff --git a/Source/Layout/ASDimension.h b/Source/Layout/ASDimension.h index ffe6138b5f..462406eedb 100644 --- a/Source/Layout/ASDimension.h +++ b/Source/Layout/ASDimension.h @@ -94,7 +94,7 @@ AS_EXTERN ASDimension const ASDimensionAuto; /** * Returns a dimension with the specified type and value. */ -AS_OVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit unit, CGFloat value) +ASOVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit unit, CGFloat value) { if (unit == ASDimensionUnitAuto ) { ASDisplayNodeCAssert(value == 0, @"ASDimension auto value must be 0."); @@ -112,7 +112,7 @@ AS_OVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit /** * Returns a dimension with the specified points value. */ -AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensionMake(CGFloat points) +ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensionMake(CGFloat points) { return ASDimensionMake(ASDimensionUnitPoints, points); } @@ -122,7 +122,7 @@ AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensi * Examples: ASDimensionMake(@"50%") = ASDimensionMake(ASDimensionUnitFraction, 0.5) * ASDimensionMake(@"0.5pt") = ASDimensionMake(ASDimensionUnitPoints, 0.5) */ -AS_OVERLOADABLE AS_WARN_UNUSED_RESULT AS_EXTERN ASDimension ASDimensionMake(NSString *dimension); +ASOVERLOADABLE AS_WARN_UNUSED_RESULT AS_EXTERN ASDimension ASDimensionMake(NSString *dimension); /** * Returns a dimension with the specified points value. @@ -244,7 +244,7 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASSizeRangeHasSignificantArea(AS /** * Creates an ASSizeRange with provided min and max size. */ -AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize min, CGSize max) +ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize min, CGSize max) { ASDisplayNodeCAssertPositiveReal(@"Range min width", min.width); ASDisplayNodeCAssertPositiveReal(@"Range min height", min.height); @@ -263,7 +263,7 @@ AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRan /** * Creates an ASSizeRange with provided size as both min and max. */ -AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize exactSize) +ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize exactSize) { return ASSizeRangeMake(exactSize, exactSize); } diff --git a/Source/Layout/ASDimension.mm b/Source/Layout/ASDimension.mm index a27226dda1..4fd0e18001 100644 --- a/Source/Layout/ASDimension.mm +++ b/Source/Layout/ASDimension.mm @@ -25,7 +25,7 @@ ASDimension const ASDimensionAuto = {ASDimensionUnitAuto, 0}; -AS_OVERLOADABLE ASDimension ASDimensionMake(NSString *dimension) +ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension) { if (dimension.length > 0) { diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 16cf9b39e6..2cf772aecf 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -25,7 +25,6 @@ #import #import #import -#import @interface ASDisplayNode () <_ASDisplayLayerDelegate> @@ -112,13 +111,10 @@ CGContextTranslateCTM(context, frame.origin.x, frame.origin.y); - // Support cornerRadius + //support cornerRadius if (rasterizingFromAscendent && clipsToBounds) { if (cornerRadius) { - CGPathRef cornerRadiusPath = ASCGRoundedPathCreate(bounds, cornerRadius); - CGContextAddPath(context, cornerRadiusPath); - CGContextClip(context); - CGPathRelease(cornerRadiusPath); + [[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius] addClip]; } else { CGContextClipToRect(context, bounds); } @@ -131,18 +127,13 @@ CGContextFillRect(context, bounds); } - // If there is a display block, call it to get the image, then copy the image into the current context (which - // is the rasterized container's backing store). + // If there is a display block, call it to get the image, then copy the image into the current context (which is the rasterized container's backing store). if (displayBlock) { UIImage *image = (UIImage *)displayBlock(); if (image) { BOOL opaque = ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)); CGBlendMode blendMode = opaque ? kCGBlendModeCopy : kCGBlendModeNormal; - CGContextSetBlendMode(context, blendMode); - CGContextTranslateCTM(context, 0, CGRectGetMaxY(bounds) + CGRectGetMinY(bounds)); - CGContextScaleCTM(context, 1, -1); - CGContextSetAlpha(context, 1.0); - CGContextDrawImage(context, bounds, image.CGImage); + [image drawInRect:bounds blendMode:blendMode alpha:1]; } } }; @@ -304,10 +295,7 @@ ASDisplayNodeAssert(context == UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); // TODO: This clip path should be removed if we are rasterizing. CGRect boundingBox = CGContextGetClipBoundingBox(context); - CGPathRef cornerRadiusPath = ASCGRoundedPathCreate(boundingBox, cornerRadius); - CGContextAddPath(context, cornerRadiusPath); - CGContextClip(context); - CGPathRelease(cornerRadiusPath); + [[UIBezierPath bezierPathWithRoundedRect:boundingBox cornerRadius:cornerRadius] addClip]; } if (willDisplayNodeContentWithRenderingContext) { @@ -344,58 +332,34 @@ CGFloat white = 0.0f, alpha = 0.0f; [backgroundColor getWhite:&white alpha:&alpha]; ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale); - context = UIGraphicsGetCurrentContext(); [*image drawInRect:bounds]; } else { bounds = CGContextGetClipBoundingBox(context); } ASDisplayNodeAssert(UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); - - CGContextSaveGState(context); - CGMutablePathRef roundedHole = CGPathCreateMutable(); - CGPathAddRect(roundedHole, NULL, bounds); - - CGPathRef additionalPath = ASCGRoundedPathCreate(bounds, cornerRadius * contentsScale); - CGPathAddPath(roundedHole, NULL, additionalPath); - - CGContextAddPath(context, roundedHole); + UIBezierPath *roundedHole = [UIBezierPath bezierPathWithRect:bounds]; + [roundedHole appendPath:[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius * contentsScale]]; + roundedHole.usesEvenOddFillRule = YES; + + UIBezierPath *roundedPath = nil; + if (borderWidth > 0.0f) { // Don't create roundedPath and stroke if borderWidth is 0.0 + CGFloat strokeThickness = borderWidth * contentsScale; + CGFloat strokeInset = ((strokeThickness + 1.0f) / 2.0f) - 1.0f; + roundedPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(bounds, strokeInset, strokeInset) + cornerRadius:_cornerRadius * contentsScale]; + roundedPath.lineWidth = strokeThickness; + [[UIColor colorWithCGColor:borderColor] setStroke]; + } // Punch out the corners by copying the backgroundColor over them. // This works for everything from clearColor to opaque colors. - CGContextSetFillColorWithColor(context, backgroundColor.CGColor); + [backgroundColor setFill]; + [roundedHole fillWithBlendMode:kCGBlendModeCopy alpha:1.0f]; - CGContextSetAlpha(context, 1.0); - CGContextSetBlendMode(context, kCGBlendModeCopy); - CGContextEOFillPath(context); - - CGPathRelease(additionalPath); - CGPathRelease(roundedHole); + [roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil. - CGContextRestoreGState(context); - - // Drawing borders with ASCornerRoundingTypePrecomposited set has some problems at the moment. If the borderWidth is - // set, besides we are drawing the border with the given corner radius, the CALayer also picks up the borderWidth - // value and draws the border without the cornerRadius. - if (borderWidth > 0.0f) { // Don't create roundedPath and stroke if borderWidth is 0.0 - CGContextSaveGState(context); - - CGFloat strokeThickness = borderWidth * contentsScale; - CGFloat strokeInset = ((strokeThickness + 1.0f) / 2.0f) - 1.0f; - CGPathRef roundedPath = ASCGRoundedPathCreate(CGRectInset(bounds, strokeInset, strokeInset), _cornerRadius * contentsScale); - CGContextAddPath(context, roundedPath); - - CGContextSetLineWidth(context, strokeThickness); - CGContextSetStrokeColorWithColor(context, borderColor); - - CGContextStrokePath(context); - - CGPathRelease(roundedPath); - - CGContextRestoreGState(context); - } - if (*image) { *image = ASGraphicsGetImageAndEndCurrentContext(); } diff --git a/Source/UIImage+ASConvenience.mm b/Source/UIImage+ASConvenience.m similarity index 86% rename from Source/UIImage+ASConvenience.mm rename to Source/UIImage+ASConvenience.m index 84e87b0fc0..c1d0751e39 100644 --- a/Source/UIImage+ASConvenience.mm +++ b/Source/UIImage+ASConvenience.m @@ -19,7 +19,6 @@ #import #import #import -#import #pragma mark - ASDKFastImageNamed @@ -115,9 +114,9 @@ UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollectio // 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 (CGColorGetAlpha(cornerColor.CGColor) == 0) { + if ([cornerColor isEqual:[UIColor clearColor]]) { cornerColor = nil; } @@ -141,46 +140,33 @@ UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollectio // 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. ASGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); - - CGContextRef context = UIGraphicsGetCurrentContext(); - - // Draw Corners + BOOL contextIsClean = YES; if (cornerColor) { contextIsClean = NO; - - CGContextSetFillColorWithColor(context, cornerColor.CGColor); + [cornerColor setFill]; // Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overrides directly. - CGContextSetBlendMode(context, kCGBlendModeCopy); - CGContextFillRect(context, bounds); + UIRectFillUsingBlendMode(bounds, kCGBlendModeCopy); } - - // Draw fill + BOOL canUseCopy = contextIsClean || (CGColorGetAlpha(fillColor.CGColor) == 1); - CGContextSetFillColorWithColor(context, fillColor.CGColor); - CGContextSetBlendMode(context, canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal); - CGContextSetAlpha(context, 1.0); - CGContextAddPath(context, path.CGPath); - CGContextFillPath(context); - - // Add a border + [fillColor setFill]; + [path fillWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; + 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. - CGPathRef strokePath = ASCGRoundedPathCreate(strokeRect, roundedCorners, cornerRadii); - - CGContextSetStrokeColorWithColor(context, borderColor.CGColor); - CGContextSetLineWidth(context, borderWidth); - CGContextSetAlpha(context, 1.0); + UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect + byRoundingCorners:roundedCorners + cornerRadii:cornerRadii]; + [strokePath setLineWidth:borderWidth]; BOOL canUseCopy = (CGColorGetAlpha(borderColor.CGColor) == 1); - CGContextSetBlendMode(context, (canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal)); - CGContextAddPath(context, strokePath); - CGContextStrokePath(context); - - CGPathRelease(strokePath); + [strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; } UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); diff --git a/Source/tvOS/ASControlNode+tvOS.m b/Source/tvOS/ASControlNode+tvOS.m index c18b67eec8..77e5418629 100644 --- a/Source/tvOS/ASControlNode+tvOS.m +++ b/Source/tvOS/ASControlNode+tvOS.m @@ -83,9 +83,7 @@ layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowRadius = 12.0; layer.shadowOpacity = 0.45; - CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL); - layer.shadowPath = shadowPath; - CGPathRelease(shadowPath); + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; } - (void)setDefaultFocusAppearance @@ -95,9 +93,7 @@ layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowRadius = 0; layer.shadowOpacity = 0; - CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL); - layer.shadowPath = shadowPath; - CGPathRelease(shadowPath); + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); } @end diff --git a/Source/tvOS/ASImageNode+tvOS.m b/Source/tvOS/ASImageNode+tvOS.m index 8f8486b941..9482fdc8c1 100644 --- a/Source/tvOS/ASImageNode+tvOS.m +++ b/Source/tvOS/ASImageNode+tvOS.m @@ -169,9 +169,7 @@ layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowRadius = 12.0; layer.shadowOpacity = 0.45; - CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL); - layer.shadowPath = shadowPath; - CGPathRelease(shadowPath); + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); } diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png deleted file mode 100644 index c4686232a2..0000000000 Binary files a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png and /dev/null differ diff --git a/examples/ASDKgram/Sample/PhotoCellNode.m b/examples/ASDKgram/Sample/PhotoCellNode.m index 057992bf24..a7bb214d21 100644 --- a/examples/ASDKgram/Sample/PhotoCellNode.m +++ b/examples/ASDKgram/Sample/PhotoCellNode.m @@ -79,8 +79,8 @@ _photoImageNode.delegate = self; _photoImageNode.URL = photo.URL; _photoImageNode.layerBacked = YES; - - _userNameLabel = [[ASTextNode alloc] init]; + + _userNameLabel = [[ASTextNode alloc] init]; _userNameLabel.attributedText = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE]; _photoLocationLabel = [[ASTextNode alloc] init];