mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Switch to instance methods of draw and display
This patch switches to instance methods of draw and display for ASTextNode and ASImageNode to attempt to increase their performance. It also fixes some thread safety issues in ASImageNode which appear to have been regressions (though probably not hit very often). And it sets up work for allowing modification of CGContexts before and after a node's contents are drawn.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
#import "ASImageNode+CGExtras.h"
|
||||
|
||||
@@ -22,42 +23,34 @@
|
||||
|
||||
@interface _ASImageNodeDrawParameters : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) BOOL cropEnabled;
|
||||
@property (nonatomic, assign) BOOL opaque;
|
||||
@property (nonatomic, retain) UIImage *image;
|
||||
@property (nonatomic, assign) CGRect bounds;
|
||||
@property (nonatomic, assign) CGFloat contentsScale;
|
||||
@property (nonatomic, retain) UIColor *backgroundColor;
|
||||
@property (nonatomic, assign) UIViewContentMode contentMode;
|
||||
@property (nonatomic, assign) CGRect cropRect;
|
||||
@property (nonatomic, copy) asimagenode_modification_block_t imageModificationBlock;
|
||||
|
||||
@end
|
||||
|
||||
// TODO: eliminate explicit parameters with a set of keys copied from the node
|
||||
@implementation _ASImageNodeDrawParameters
|
||||
|
||||
- (id)initWithCrop:(BOOL)cropEnabled opaque:(BOOL)opaque image:(UIImage *)image bounds:(CGRect)bounds contentsScale:(CGFloat)contentsScale backgroundColor:(UIColor *)backgroundColor contentMode:(UIViewContentMode)contentMode cropRect:(CGRect)cropRect imageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock
|
||||
- (id)initWithBounds:(CGRect)bounds opaque:(BOOL)opaque contentsScale:(CGFloat)contentsScale backgroundColor:(UIColor *)backgroundColor contentMode:(UIViewContentMode)contentMode
|
||||
{
|
||||
self = [self init];
|
||||
if (!self) return nil;
|
||||
|
||||
_cropEnabled = cropEnabled;
|
||||
_opaque = opaque;
|
||||
_image = image;
|
||||
_bounds = bounds;
|
||||
_contentsScale = contentsScale;
|
||||
_backgroundColor = backgroundColor;
|
||||
_contentMode = contentMode;
|
||||
_cropRect = cropRect;
|
||||
_imageModificationBlock = [imageModificationBlock copy];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@ : %p image:%@ cropEnabled:%@ opaque:%@ bounds:%@ contentsScale:%.2f backgroundColor:%@ contentMode:%@ cropRect:%@>", [self class], self, self.image, @(self.cropEnabled), @(self.opaque), NSStringFromCGRect(self.bounds), self.contentsScale, self.backgroundColor, ASDisplayNodeNSStringFromUIContentMode(self.contentMode), NSStringFromCGRect(self.cropRect)];
|
||||
return [NSString stringWithFormat:@"<%@ : %p opaque:%@ bounds:%@ contentsScale:%.2f backgroundColor:%@ contentMode:%@>", [self class], self, @(self.opaque), NSStringFromCGRect(self.bounds), self.contentsScale, self.backgroundColor, ASDisplayNodeNSStringFromUIContentMode(self.contentMode)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -78,6 +71,7 @@
|
||||
}
|
||||
|
||||
@synthesize image = _image;
|
||||
@synthesize imageModificationBlock = _imageModificationBlock;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
@@ -153,88 +147,104 @@
|
||||
{
|
||||
BOOL hasValidCropBounds = _cropEnabled && !CGRectIsNull(_cropDisplayBounds) && !CGRectIsEmpty(_cropDisplayBounds);
|
||||
|
||||
return [[_ASImageNodeDrawParameters alloc] initWithCrop:_cropEnabled
|
||||
opaque:self.opaque
|
||||
image:self.image
|
||||
bounds:(hasValidCropBounds ? _cropDisplayBounds : self.bounds)
|
||||
contentsScale:self.contentsScaleForDisplay
|
||||
backgroundColor:self.backgroundColor
|
||||
contentMode:self.contentMode
|
||||
cropRect:self.cropRect
|
||||
imageModificationBlock:self.imageModificationBlock];
|
||||
return [[_ASImageNodeDrawParameters alloc] initWithBounds:self.bounds
|
||||
opaque:self.opaque
|
||||
contentsScale:self.contentsScaleForDisplay
|
||||
backgroundColor:self.backgroundColor
|
||||
contentMode:self.contentMode];
|
||||
}
|
||||
|
||||
+ (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
- (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
{
|
||||
UIImage *image = parameters.image;
|
||||
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
UIImage *image = _image;
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(parameters.contentsScale > 0, @"invalid contentsScale at display time");
|
||||
|
||||
|
||||
BOOL cropEnabled = _cropEnabled;
|
||||
CGFloat contentsScale = _contentsScaleForDisplay;
|
||||
CGRect cropDisplayBounds = _cropDisplayBounds;
|
||||
CGRect cropRect = _cropRect;
|
||||
asimagenode_modification_block_t imageModificationBlock = _imageModificationBlock;
|
||||
|
||||
ASDN::MutexUnlocker u(_imageLock);
|
||||
|
||||
ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentBlock;
|
||||
ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentBlock;
|
||||
|
||||
BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds);
|
||||
|
||||
CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds);
|
||||
BOOL isOpaque = parameters.opaque;
|
||||
CGFloat contentsScaleForDisplay = parameters.contentsScale;
|
||||
UIColor *backgroundColor = parameters.backgroundColor;
|
||||
UIViewContentMode contentMode = parameters.contentMode;
|
||||
|
||||
ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time");
|
||||
|
||||
// if the image is resizable, bail early since the image has likely already been configured
|
||||
BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
|
||||
if (stretchable) {
|
||||
if (parameters.imageModificationBlock != NULL) {
|
||||
image = parameters.imageModificationBlock(image);
|
||||
if (imageModificationBlock != NULL) {
|
||||
image = imageModificationBlock(image);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
CGRect bounds = parameters.bounds;
|
||||
|
||||
CGFloat contentsScale = parameters.contentsScale;
|
||||
UIViewContentMode contentMode = parameters.contentMode;
|
||||
|
||||
CGSize imageSize = image.size;
|
||||
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
|
||||
CGSize boundsSizeInPixels = CGSizeMake(floorf(bounds.size.width * contentsScale), floorf(bounds.size.height * contentsScale));
|
||||
|
||||
|
||||
BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill
|
||||
|| contentMode == UIViewContentModeScaleAspectFit
|
||||
|| contentMode == UIViewContentModeCenter;
|
||||
|
||||
|| contentMode == UIViewContentModeScaleAspectFit
|
||||
|| contentMode == UIViewContentModeCenter;
|
||||
|
||||
CGSize backingSize;
|
||||
CGRect imageDrawRect;
|
||||
|
||||
|
||||
if (boundsSizeInPixels.width * contentsScale < 1.0f ||
|
||||
boundsSizeInPixels.height * contentsScale < 1.0f ||
|
||||
imageSizeInPixels.width < 1.0f ||
|
||||
imageSizeInPixels.height < 1.0f) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
// If we're not supposed to do any cropping, just decode image at original size
|
||||
if (!parameters.cropEnabled || !contentModeSupported || stretchable) {
|
||||
if (!cropEnabled || !contentModeSupported || stretchable) {
|
||||
backingSize = imageSizeInPixels;
|
||||
imageDrawRect = (CGRect){.size = backingSize};
|
||||
} else {
|
||||
ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels,
|
||||
boundsSizeInPixels,
|
||||
contentMode,
|
||||
parameters.cropRect,
|
||||
cropRect,
|
||||
&backingSize,
|
||||
&imageDrawRect);
|
||||
}
|
||||
|
||||
|
||||
if (backingSize.width <= 0.0f ||
|
||||
backingSize.height <= 0.0f ||
|
||||
imageDrawRect.size.width <= 0.0f ||
|
||||
imageDrawRect.size.height <= 0.0f) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
// Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds
|
||||
// will do its rounding on pixel instead of point boundaries
|
||||
UIGraphicsBeginImageContextWithOptions(backingSize, parameters.opaque, 1.0);
|
||||
UIGraphicsBeginImageContextWithOptions(backingSize, isOpaque, 1.0);
|
||||
|
||||
// if view is opaque, fill the context with background color
|
||||
if (parameters.opaque && parameters.backgroundColor) {
|
||||
[parameters.backgroundColor setFill];
|
||||
if (isOpaque && backgroundColor) {
|
||||
[backgroundColor setFill];
|
||||
UIRectFill({ .size = backingSize });
|
||||
}
|
||||
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (preContextBlock) {
|
||||
preContextBlock(context);
|
||||
}
|
||||
|
||||
// iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on
|
||||
// multiple threads concurrently. In fact, instead of crashing, it appears to deadlock.
|
||||
// The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premier,
|
||||
@@ -250,20 +260,24 @@
|
||||
@synchronized(image) {
|
||||
[image drawInRect:imageDrawRect];
|
||||
}
|
||||
|
||||
|
||||
if (postContextBlock) {
|
||||
postContextBlock(context);
|
||||
}
|
||||
|
||||
if (isCancelled()) {
|
||||
UIGraphicsEndImageContext();
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
if (parameters.imageModificationBlock != NULL) {
|
||||
result = parameters.imageModificationBlock(result);
|
||||
|
||||
if (imageModificationBlock != NULL) {
|
||||
result = imageModificationBlock(result);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -271,8 +285,9 @@
|
||||
{
|
||||
[super displayDidFinish];
|
||||
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
// If we've got a block to perform after displaying, do it.
|
||||
if (self.image && _displayCompletionBlock) {
|
||||
if (_image && _displayCompletionBlock) {
|
||||
|
||||
// FIXME: _displayCompletionBlock is not protected by lock
|
||||
_displayCompletionBlock(NO);
|
||||
@@ -291,6 +306,7 @@
|
||||
|
||||
// Stash the block and call-site queue. We'll invoke it in -displayDidFinish.
|
||||
// FIXME: _displayCompletionBlock not protected by lock
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
if (_displayCompletionBlock != displayCompletionBlock) {
|
||||
_displayCompletionBlock = [displayCompletionBlock copy];
|
||||
}
|
||||
@@ -301,6 +317,7 @@
|
||||
#pragma mark - Cropping
|
||||
- (BOOL)isCropEnabled
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
return _cropEnabled;
|
||||
}
|
||||
|
||||
@@ -311,6 +328,7 @@
|
||||
|
||||
- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
if (_cropEnabled == cropEnabled)
|
||||
return;
|
||||
|
||||
@@ -331,11 +349,13 @@
|
||||
|
||||
- (CGRect)cropRect
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
return _cropRect;
|
||||
}
|
||||
|
||||
- (void)setCropRect:(CGRect)cropRect
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
if (CGRectEqualToRect(_cropRect, cropRect))
|
||||
return;
|
||||
|
||||
@@ -354,6 +374,18 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (asimagenode_modification_block_t)imageModificationBlock
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
return _imageModificationBlock;
|
||||
}
|
||||
|
||||
- (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
_imageModificationBlock = imageModificationBlock;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user