Adds support for using UIGraphicsImageRenderer in ASTextNode. (#1384)

* Adds support for using UIGraphicsImageRenderer in ASTextNode.

In many cases this reduces the backing store of text nodes by 1/2.

* Guard for UIGraphicsRenderer availability.

* Comma
This commit is contained in:
Garrett Moon 2019-03-09 07:55:46 -08:00 committed by Adlai Holler
parent 872e89b772
commit 9b80eabd8f
6 changed files with 80 additions and 23 deletions

View File

@ -26,7 +26,8 @@
"exp_skip_a11y_wait",
"exp_new_default_cell_layout_mode",
"exp_dispatch_apply",
"exp_image_downloader_priority"
"exp_image_downloader_priority",
"exp_text_drawing"
]
}
}

View File

@ -32,6 +32,7 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) {
ASExperimentalNewDefaultCellLayoutMode = 1 << 11, // exp_new_default_cell_layout_mode
ASExperimentalDispatchApply = 1 << 12, // exp_dispatch_apply
ASExperimentalImageDownloaderPriority = 1 << 13, // exp_image_downloader_priority
ASExperimentalTextDrawing = 1 << 14, // exp_text_drawing
ASExperimentalFeatureAll = 0xFFFFFFFF
};

View File

@ -25,8 +25,8 @@ NSArray<NSString *> *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags
@"exp_skip_a11y_wait",
@"exp_new_default_cell_layout_mode",
@"exp_dispatch_apply",
@"exp_image_downloader_priority"]));
@"exp_image_downloader_priority",
@"exp_text_drawing"]));
if (flags == ASExperimentalFeatureAll) {
return allNames;
}

View File

@ -18,6 +18,7 @@
#import <mutex>
#import <tgmath.h>
#import <AsyncDisplayKit/ASAvailability.h>
#import <AsyncDisplayKit/_ASDisplayLayer.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
@ -140,6 +141,9 @@ static ASTextKitRenderer *rendererForAttributes(ASTextKitAttributes attributes,
ASTextKitAttributes _rendererAttributes;
UIColor *_backgroundColor;
UIEdgeInsets _textContainerInsets;
CGFloat _contentScale;
BOOL _opaque;
CGRect _bounds;
}
@end
@ -148,12 +152,18 @@ static ASTextKitRenderer *rendererForAttributes(ASTextKitAttributes attributes,
- (instancetype)initWithRendererAttributes:(ASTextKitAttributes)rendererAttributes
backgroundColor:(/*nullable*/ UIColor *)backgroundColor
textContainerInsets:(UIEdgeInsets)textContainerInsets
contentScale:(CGFloat)contentScale
opaque:(BOOL)opaque
bounds:(CGRect)bounds
{
self = [super init];
if (self != nil) {
_rendererAttributes = rendererAttributes;
_backgroundColor = backgroundColor;
_textContainerInsets = textContainerInsets;
_contentScale = contentScale;
_opaque = opaque;
_bounds = bounds;
}
return self;
}
@ -526,31 +536,74 @@ static NSArray *DefaultLinkAttributeNames() {
return [[ASTextNodeDrawParameter alloc] initWithRendererAttributes:[self _locked_rendererAttributes]
backgroundColor:self.backgroundColor
textContainerInsets:_textContainerInset];
textContainerInsets:_textContainerInset
contentScale:_contentsScaleForDisplay
opaque:self.isOpaque
bounds:[self threadSafeBounds]];
}
+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
+ (UIImage *)displayWithParameters:(id<NSObject>)parameters isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled
{
ASTextNodeDrawParameter *drawParameter = (ASTextNodeDrawParameter *)parameters;
UIColor *backgroundColor = (isRasterizing || drawParameter == nil) ? nil : drawParameter->_backgroundColor;
if (drawParameter->_bounds.size.width <= 0 || drawParameter->_bounds.size.height <= 0) {
return nil;
}
UIImage *result = nil;
UIColor *backgroundColor = drawParameter->_backgroundColor;
UIEdgeInsets textContainerInsets = drawParameter ? drawParameter->_textContainerInsets : UIEdgeInsetsZero;
ASTextKitRenderer *renderer = [drawParameter rendererForBounds:bounds];
ASTextKitRenderer *renderer = [drawParameter rendererForBounds:drawParameter->_bounds];
BOOL renderedWithGraphicsRenderer = NO;
CGContextRef context = UIGraphicsGetCurrentContext();
ASDisplayNodeAssert(context, @"This is no good without a context.");
CGContextSaveGState(context);
CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top);
// Fill background
if (backgroundColor != nil) {
[backgroundColor setFill];
UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy);
if (AS_AVAILABLE_IOS_TVOS(10, 10)) {
if (ASActivateExperimentalFeature(ASExperimentalTextDrawing)) {
renderedWithGraphicsRenderer = YES;
UIGraphicsImageRenderer *graphicsRenderer = [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(drawParameter->_bounds.size.width, drawParameter->_bounds.size.height)];
result = [graphicsRenderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
CGContextRef context = rendererContext.CGContext;
ASDisplayNodeAssert(context, @"This is no good without a context.");
CGContextSaveGState(context);
CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top);
// Fill background
if (backgroundColor != nil) {
[backgroundColor setFill];
UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy);
}
// Draw text
[renderer drawInContext:context bounds:drawParameter->_bounds];
CGContextRestoreGState(context);
}];
}
}
// Draw text
[renderer drawInContext:context bounds:bounds];
CGContextRestoreGState(context);
if (!renderedWithGraphicsRenderer) {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(drawParameter->_bounds.size.width, drawParameter->_bounds.size.height), drawParameter->_opaque, drawParameter->_contentScale);
CGContextRef context = UIGraphicsGetCurrentContext();
ASDisplayNodeAssert(context, @"This is no good without a context.");
CGContextSaveGState(context);
CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top);
// Fill background
if (backgroundColor != nil) {
[backgroundColor setFill];
UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy);
}
// Draw text
[renderer drawInContext:context bounds:drawParameter->_bounds];
CGContextRestoreGState(context);
result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
return result;
}
#pragma mark - Attributes

View File

@ -112,7 +112,7 @@ NS_ASSUME_NONNULL_BEGIN
@summary Delegate override to provide new layer contents as a UIImage.
@param parameters An object describing all of the properties you need to draw. Return this from -drawParametersForAsyncLayer:
@param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return.
@return A UIImage with contents that are ready to display on the main thread. Make sure that the image is already decoded before returning it here.
@return A UIImage (backed by a CGImage) with contents that are ready to display on the main thread. Make sure that the image is already decoded before returning it here.
*/
+ (UIImage *)displayWithParameters:(nullable id<NSObject>)parameters
isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock;

View File

@ -31,7 +31,8 @@ static ASExperimentalFeatures features[] = {
ASExperimentalSkipAccessibilityWait,
ASExperimentalNewDefaultCellLayoutMode,
ASExperimentalDispatchApply,
ASExperimentalImageDownloaderPriority
ASExperimentalImageDownloaderPriority,
ASExperimentalTextDrawing
};
@interface ASConfigurationTests : ASTestCase <ASConfigurationDelegate>
@ -57,7 +58,8 @@ static ASExperimentalFeatures features[] = {
@"exp_skip_a11y_wait",
@"exp_new_default_cell_layout_mode",
@"exp_dispatch_apply",
@"exp_image_downloader_priority"
@"exp_image_downloader_priority",
@"exp_text_drawing"
];
}