Skip to content

Commit

Permalink
Adds support for using UIGraphicsImageRenderer in ASTextNode. (#1384)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
garrettmoon authored and Adlai-Holler committed Mar 9, 2019
1 parent 872e89b commit 9b80eab
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 23 deletions.
3 changes: 2 additions & 1 deletion Schemas/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
}
Expand Down
1 change: 1 addition & 0 deletions Source/ASExperimentalFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
4 changes: 2 additions & 2 deletions Source/ASExperimentalFeatures.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,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"]));
if (flags == ASExperimentalFeatureAll) {
return allNames;
}
Expand Down
87 changes: 70 additions & 17 deletions Source/ASTextNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -140,6 +141,9 @@ @interface ASTextNodeDrawParameter : NSObject {
ASTextKitAttributes _rendererAttributes;
UIColor *_backgroundColor;
UIEdgeInsets _textContainerInsets;
CGFloat _contentScale;
BOOL _opaque;
CGRect _bounds;
}
@end

Expand All @@ -148,12 +152,18 @@ @implementation ASTextNodeDrawParameter
- (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;
}
Expand Down Expand Up @@ -526,31 +536,74 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer

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;
UIEdgeInsets textContainerInsets = drawParameter ? drawParameter->_textContainerInsets : UIEdgeInsetsZero;
ASTextKitRenderer *renderer = [drawParameter rendererForBounds:bounds];

CGContextRef context = UIGraphicsGetCurrentContext();
ASDisplayNodeAssert(context, @"This is no good without a context.");

CGContextSaveGState(context);
CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top);
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:drawParameter->_bounds];
BOOL renderedWithGraphicsRenderer = NO;

// 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
Expand Down
2 changes: 1 addition & 1 deletion Source/Details/_ASDisplayLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions Tests/ASConfigurationTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
ASExperimentalSkipAccessibilityWait,
ASExperimentalNewDefaultCellLayoutMode,
ASExperimentalDispatchApply,
ASExperimentalImageDownloaderPriority
ASExperimentalImageDownloaderPriority,
ASExperimentalTextDrawing
};

@interface ASConfigurationTests : ASTestCase <ASConfigurationDelegate>
Expand All @@ -57,7 +58,8 @@ + (NSArray *)names {
@"exp_skip_a11y_wait",
@"exp_new_default_cell_layout_mode",
@"exp_dispatch_apply",
@"exp_image_downloader_priority"
@"exp_image_downloader_priority",
@"exp_text_drawing"
];
}

Expand Down

0 comments on commit 9b80eab

Please sign in to comment.