Optimize drawing code + add examples how to round corners (#996)

* Use CoreGraphics for drawing and cropping of node content

* Smaller fixes
This commit is contained in:
Michael Schneider 2018-07-26 09:44:10 -07:00 committed by Huy Nguyen
parent 4880b54db0
commit eb4c21c545
16 changed files with 250 additions and 109 deletions

View File

@ -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.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */; };
509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */; };
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.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; };
8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */; };
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 = "<group>"; };
205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewLayoutController.m; sourceTree = "<group>"; };
205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CoreGraphics+ASConvenience.h"; sourceTree = "<group>"; };
205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CoreGraphics+ASConvenience.m"; sourceTree = "<group>"; };
205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "CoreGraphics+ASConvenience.mm"; sourceTree = "<group>"; };
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = "<group>"; };
2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTruncationTests.mm; sourceTree = "<group>"; };
@ -734,7 +734,7 @@
7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRelativeLayoutSpec.h; sourceTree = "<group>"; };
7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = "<group>"; };
8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ASConvenience.h"; sourceTree = "<group>"; };
8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ASConvenience.m"; sourceTree = "<group>"; };
8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIImage+ASConvenience.mm"; sourceTree = "<group>"; };
81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeSnapshotTests.m; sourceTree = "<group>"; };
81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = "<group>"; };
81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = "<group>"; };
@ -1234,7 +1234,7 @@
68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */,
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */,
8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */,
8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */,
8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */,
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.m */,
205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */,
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.m in Sources */,
8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.mm 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.m in Sources */,
509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.mm in Sources */,
254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */,
34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */,
254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */,

View File

@ -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)

View File

@ -53,6 +53,7 @@
#import <AsyncDisplayKit/ASWeakProxy.h>
#import <AsyncDisplayKit/ASResponderChainEnumerator.h>
#import <AsyncDisplayKit/ASTipsController.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
// Conditionally time these scopes to our debug ivars (only exist in debug/profile builds)
#if TIME_DISPLAYNODE_OPS
@ -1508,7 +1509,7 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS
__instanceLock__.lock();
if (_placeholderLayer.superlayer && !placeholderShouldPersist) {
void (^cleanupBlock)() = ^{
[_placeholderLayer removeFromSuperlayer];
[self->_placeholderLayer removeFromSuperlayer];
};
if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) {
@ -1666,24 +1667,29 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
CGSize size = CGSizeMake(radius + 1, radius + 1);
ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextRef context = UIGraphicsGetCurrentContext();
if (isRight == YES) {
CGContextTranslateCTM(ctx, -radius + 1, 0);
CGContextTranslateCTM(context, -radius + 1, 0);
}
if (isTop == YES) {
CGContextTranslateCTM(ctx, 0, -radius + 1);
CGContextTranslateCTM(context, 0, -radius + 1);
}
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];
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.
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);
_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);
CGPathRelease(addedPath);
CGPathRelease(roundedPath);
}
[self _layoutClipCornersIfNeeded];
});
@ -1868,7 +1874,10 @@ 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();
@ -2979,11 +2988,11 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
if (ASInterfaceStateIncludesVisible(self.pendingInterfaceState)) {
void(^exitVisibleInterfaceState)(void) = ^{
// This block intentionally retains self.
__instanceLock__.lock();
unsigned isStillInHierarchy = _flags.isInHierarchy;
BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState);
ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible);
__instanceLock__.unlock();
self->__instanceLock__.lock();
unsigned isStillInHierarchy = self->_flags.isInHierarchy;
BOOL isVisible = ASInterfaceStateIncludesVisible(self->_pendingInterfaceState);
ASInterfaceState newState = (self->_pendingInterfaceState & ~ASInterfaceStateVisible);
self->__instanceLock__.unlock();
if (!isStillInHierarchy && isVisible) {
#if ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR
if (![self supportsRangeManagedInterfaceState]) {
@ -3142,8 +3151,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(__instanceLock__);
if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) {
ASDN::MutexLocker l(self->__instanceLock__);
if (ASInterfaceStateIncludesDisplay(self->_interfaceState) == NO) {
[self clearContents];
}
});
@ -3160,8 +3169,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(__instanceLock__);
if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) {
ASDN::MutexLocker l(self->__instanceLock__);
if (ASInterfaceStateIncludesDisplay(self->_interfaceState) == NO) {
[self clearContents];
}
});

View File

@ -737,19 +737,43 @@ asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat
{
return ^(UIImage *originalImage) {
ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}];
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = (CGRect){CGPointZero, originalImage.size};
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddEllipseInRect(path, NULL, rect);
CGContextAddPath(context, path);
// Make the image round
[roundOutline addClip];
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);
// Draw the original image
[originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];
CGPathRelease(path);
// Draw a border on top.
if (borderWidth > 0.0) {
[borderColor setStroke];
[roundOutline setLineWidth:borderWidth];
[roundOutline stroke];
// 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);
}
return ASGraphicsGetImageAndEndCurrentContext();

View File

@ -130,7 +130,7 @@
#endif
#endif
#define ASOVERLOADABLE __attribute__((overloadable))
#define AS_OVERLOADABLE __attribute__((overloadable))
#if __has_attribute(noescape)

View File

@ -17,8 +17,8 @@
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <tgmath.h>
#import <UIKit/UIBezierPath.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
@ -56,4 +56,11 @@ 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

View File

@ -1,19 +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 <AsyncDisplayKit/CoreGraphics+ASConvenience.h>

View File

@ -0,0 +1,64 @@
//
// 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 <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
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;
}

View File

@ -94,7 +94,7 @@ AS_EXTERN ASDimension const ASDimensionAuto;
/**
* Returns a dimension with the specified type and value.
*/
ASOVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit unit, CGFloat value)
AS_OVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit unit, CGFloat value)
{
if (unit == ASDimensionUnitAuto ) {
ASDisplayNodeCAssert(value == 0, @"ASDimension auto value must be 0.");
@ -112,7 +112,7 @@ ASOVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit
/**
* Returns a dimension with the specified points value.
*/
ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensionMake(CGFloat points)
AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensionMake(CGFloat points)
{
return ASDimensionMake(ASDimensionUnitPoints, points);
}
@ -122,7 +122,7 @@ ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensio
* Examples: ASDimensionMake(@"50%") = ASDimensionMake(ASDimensionUnitFraction, 0.5)
* ASDimensionMake(@"0.5pt") = ASDimensionMake(ASDimensionUnitPoints, 0.5)
*/
ASOVERLOADABLE AS_WARN_UNUSED_RESULT AS_EXTERN ASDimension ASDimensionMake(NSString *dimension);
AS_OVERLOADABLE 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.
*/
ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize min, CGSize max)
AS_OVERLOADABLE 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 @@ ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRang
/**
* Creates an ASSizeRange with provided size as both min and max.
*/
ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize exactSize)
AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize exactSize)
{
return ASSizeRangeMake(exactSize, exactSize);
}

View File

@ -25,7 +25,7 @@
ASDimension const ASDimensionAuto = {ASDimensionUnitAuto, 0};
ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension)
AS_OVERLOADABLE ASDimension ASDimensionMake(NSString *dimension)
{
if (dimension.length > 0) {

View File

@ -25,6 +25,7 @@
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASSignpost.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
@interface ASDisplayNode () <_ASDisplayLayerDelegate>
@ -111,10 +112,13 @@
CGContextTranslateCTM(context, frame.origin.x, frame.origin.y);
//support cornerRadius
// Support cornerRadius
if (rasterizingFromAscendent && clipsToBounds) {
if (cornerRadius) {
[[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius] addClip];
CGPathRef cornerRadiusPath = ASCGRoundedPathCreate(bounds, cornerRadius);
CGContextAddPath(context, cornerRadiusPath);
CGContextClip(context);
CGPathRelease(cornerRadiusPath);
} else {
CGContextClipToRect(context, bounds);
}
@ -127,13 +131,18 @@
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;
[image drawInRect:bounds blendMode:blendMode alpha:1];
CGContextSetBlendMode(context, blendMode);
CGContextTranslateCTM(context, 0, CGRectGetMaxY(bounds) + CGRectGetMinY(bounds));
CGContextScaleCTM(context, 1, -1);
CGContextSetAlpha(context, 1.0);
CGContextDrawImage(context, bounds, image.CGImage);
}
}
};
@ -295,7 +304,10 @@
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);
[[UIBezierPath bezierPathWithRoundedRect:boundingBox cornerRadius:cornerRadius] addClip];
CGPathRef cornerRadiusPath = ASCGRoundedPathCreate(boundingBox, cornerRadius);
CGContextAddPath(context, cornerRadiusPath);
CGContextClip(context);
CGPathRelease(cornerRadiusPath);
}
if (willDisplayNodeContentWithRenderingContext) {
@ -332,34 +344,58 @@
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);
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];
}
CGMutablePathRef roundedHole = CGPathCreateMutable();
CGPathAddRect(roundedHole, NULL, bounds);
CGPathRef additionalPath = ASCGRoundedPathCreate(bounds, cornerRadius * contentsScale);
CGPathAddPath(roundedHole, NULL, additionalPath);
CGContextAddPath(context, roundedHole);
// Punch out the corners by copying the backgroundColor over them.
// This works for everything from clearColor to opaque colors.
[backgroundColor setFill];
[roundedHole fillWithBlendMode:kCGBlendModeCopy alpha:1.0f];
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
[roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil.
CGContextSetAlpha(context, 1.0);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextEOFillPath(context);
CGPathRelease(additionalPath);
CGPathRelease(roundedHole);
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();
}

View File

@ -19,6 +19,7 @@
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
#pragma mark - ASDKFastImageNamed
@ -114,9 +115,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 ([cornerColor isEqual:[UIColor clearColor]]) {
if (CGColorGetAlpha(cornerColor.CGColor) == 0) {
cornerColor = nil;
}
@ -140,33 +141,46 @@ 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;
[cornerColor setFill];
CGContextSetFillColorWithColor(context, cornerColor.CGColor);
// Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overrides directly.
UIRectFillUsingBlendMode(bounds, kCGBlendModeCopy);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextFillRect(context, bounds);
}
// Draw fill
BOOL canUseCopy = contextIsClean || (CGColorGetAlpha(fillColor.CGColor) == 1);
[fillColor setFill];
[path fillWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1];
CGContextSetFillColorWithColor(context, fillColor.CGColor);
CGContextSetBlendMode(context, canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal);
CGContextSetAlpha(context, 1.0);
CGContextAddPath(context, path.CGPath);
CGContextFillPath(context);
// Add a border
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
byRoundingCorners:roundedCorners
cornerRadii:cornerRadii];
[strokePath setLineWidth:borderWidth];
CGPathRef strokePath = ASCGRoundedPathCreate(strokeRect, roundedCorners, cornerRadii);
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
CGContextSetLineWidth(context, borderWidth);
CGContextSetAlpha(context, 1.0);
BOOL canUseCopy = (CGColorGetAlpha(borderColor.CGColor) == 1);
[strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1];
CGContextSetBlendMode(context, (canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal));
CGContextAddPath(context, strokePath);
CGContextStrokePath(context);
CGPathRelease(strokePath);
}
UIImage *result = ASGraphicsGetImageAndEndCurrentContext();

View File

@ -83,7 +83,9 @@
layer.shadowColor = [UIColor blackColor].CGColor;
layer.shadowRadius = 12.0;
layer.shadowOpacity = 0.45;
layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath;
CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL);
layer.shadowPath = shadowPath;
CGPathRelease(shadowPath);
}
- (void)setDefaultFocusAppearance
@ -93,7 +95,9 @@
layer.shadowColor = [UIColor blackColor].CGColor;
layer.shadowRadius = 0;
layer.shadowOpacity = 0;
layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath;
CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL);
layer.shadowPath = shadowPath;
CGPathRelease(shadowPath);
self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1);
}
@end

View File

@ -169,7 +169,9 @@
layer.shadowColor = [UIColor blackColor].CGColor;
layer.shadowRadius = 12.0;
layer.shadowOpacity = 0.45;
layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath;
CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL);
layer.shadowPath = shadowPath;
CGPathRelease(shadowPath);
view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -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];