mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-03 03:10:47 +00:00
Add an experimental "no-copy" renderer (#741)
* Add "ASGraphicsContext" to skip copying our rendered images * Zero the buffer before making a context * Update license header * Update dangerfile * Make it a runtime flag * Restore GState for good measure * Free buffer if end without image * Enable the experiment, and cut out the middle-man * Fix typo
This commit is contained in:
parent
3708f2e448
commit
1d105c2056
@ -402,6 +402,8 @@
|
||||
CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */; };
|
||||
CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */; };
|
||||
CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */; };
|
||||
CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */ = {isa = PBXBuildFile; fileRef = CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */; };
|
||||
CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */; };
|
||||
CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */; };
|
||||
CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */; };
|
||||
@ -895,6 +897,8 @@
|
||||
CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSParagraphStyle+ASText.m"; sourceTree = "<group>"; };
|
||||
CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+ASText.h"; sourceTree = "<group>"; };
|
||||
CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+ASText.m"; sourceTree = "<group>"; };
|
||||
CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASGraphicsContext.h; sourceTree = "<group>"; };
|
||||
CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASGraphicsContext.m; sourceTree = "<group>"; };
|
||||
CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionModernDataSourceTests.m; sourceTree = "<group>"; };
|
||||
CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSectionController.h; sourceTree = "<group>"; };
|
||||
CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IGListAdapter+AsyncDisplayKit.h"; sourceTree = "<group>"; };
|
||||
@ -1270,6 +1274,8 @@
|
||||
058D09E1195D050800B7D73C /* Details */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */,
|
||||
CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */,
|
||||
CC5601391F06E9A700DC4FBE /* ASIntegerMap.h */,
|
||||
CC56013A1F06E9A700DC4FBE /* ASIntegerMap.mm */,
|
||||
CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */,
|
||||
@ -1836,6 +1842,7 @@
|
||||
68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */,
|
||||
CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */,
|
||||
B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */,
|
||||
CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */,
|
||||
E5C347B11ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h in Headers */,
|
||||
CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */,
|
||||
B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */,
|
||||
@ -2268,6 +2275,7 @@
|
||||
E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */,
|
||||
9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
|
||||
690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */,
|
||||
CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.m in Sources */,
|
||||
CCCCCCD81EC3EF060087FE10 /* ASTextInput.m in Sources */,
|
||||
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
|
||||
DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */,
|
||||
|
@ -11,6 +11,7 @@
|
||||
- Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424)
|
||||
- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706)
|
||||
- [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler)
|
||||
- Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler)
|
||||
|
||||
## 2.6
|
||||
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)
|
||||
|
@ -67,7 +67,7 @@ end
|
||||
|
||||
# Ensure new files have proper header
|
||||
new_source_license_header = <<-HEREDOC
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
|
||||
// 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
|
||||
|
@ -36,6 +36,7 @@
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
@ -1507,7 +1508,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
BOOL isRight = (idx == 1 || idx == 2);
|
||||
|
||||
CGSize size = CGSizeMake(radius + 1, radius + 1);
|
||||
UIGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay);
|
||||
ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay);
|
||||
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
if (isRight == YES) {
|
||||
@ -1524,11 +1525,9 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
|
||||
// No lock needed, as _clipCornerLayers is only modified on the main thread.
|
||||
CALayer *clipCornerLayer = _clipCornerLayers[idx];
|
||||
clipCornerLayer.contents = (id)(UIGraphicsGetImageFromCurrentImageContext().CGImage);
|
||||
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);
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
}
|
||||
[self _layoutClipCornersIfNeeded];
|
||||
});
|
||||
|
@ -25,6 +25,7 @@
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
|
||||
@ -213,11 +214,10 @@ typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry);
|
||||
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
UIGraphicsBeginImageContext(size);
|
||||
ASGraphicsBeginImageContextWithOptions(size, NO, 1);
|
||||
[self.placeholderColor setFill];
|
||||
UIRectFill(CGRectMake(0, 0, size.width, size.height));
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
|
||||
return image;
|
||||
}
|
||||
@ -472,7 +472,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex;
|
||||
|
||||
+ (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
{
|
||||
// The following `UIGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an
|
||||
// The following `ASGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an
|
||||
// A5 processor for a 400x800 backingSize.
|
||||
// Check for cancellation before we call it.
|
||||
if (isCancelled()) {
|
||||
@ -481,7 +481,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex;
|
||||
|
||||
// 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(key.backingSize, key.isOpaque, 1.0);
|
||||
ASGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0);
|
||||
|
||||
BOOL contextIsClean = YES;
|
||||
|
||||
@ -529,9 +529,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex;
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
UIImage *result = ASGraphicsGetImageAndEndCurrentContext();
|
||||
|
||||
if (key.imageModificationBlock) {
|
||||
result = key.imageModificationBlock(result);
|
||||
@ -742,7 +740,7 @@ static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex;
|
||||
extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor)
|
||||
{
|
||||
return ^(UIImage *originalImage) {
|
||||
UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
|
||||
ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
|
||||
UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}];
|
||||
|
||||
// Make the image round
|
||||
@ -758,24 +756,21 @@ extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(
|
||||
[roundOutline stroke];
|
||||
}
|
||||
|
||||
UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return modifiedImage;
|
||||
return ASGraphicsGetImageAndEndCurrentContext();
|
||||
};
|
||||
}
|
||||
|
||||
extern asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color)
|
||||
{
|
||||
return ^(UIImage *originalImage) {
|
||||
UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
|
||||
ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
|
||||
|
||||
// Set color and render template
|
||||
[color setFill];
|
||||
UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
[templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];
|
||||
|
||||
UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
UIImage *modifiedImage = ASGraphicsGetImageAndEndCurrentContext();
|
||||
|
||||
// if the original image was stretchy, keep it stretchy
|
||||
if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) {
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
@ -222,7 +223,7 @@
|
||||
|
||||
CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height);
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
|
||||
ASGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
|
||||
[image drawAtPoint:CGPointZero];
|
||||
|
||||
UIImage *pinImage;
|
||||
@ -254,8 +255,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
}
|
||||
|
||||
strongSelf.image = image;
|
||||
|
@ -28,6 +28,7 @@
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
|
||||
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASTextKitCoreTextAdditions.h>
|
||||
#import <AsyncDisplayKit/ASTextKitRenderer+Positioning.h>
|
||||
@ -907,7 +908,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
|
||||
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
UIGraphicsBeginImageContext(size);
|
||||
ASGraphicsBeginImageContextWithOptions(size, NO, 1.0);
|
||||
[self.placeholderColor setFill];
|
||||
|
||||
ASTextKitRenderer *renderer = [self _locked_renderer];
|
||||
@ -926,8 +927,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
|
||||
}
|
||||
}
|
||||
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
return image;
|
||||
}
|
||||
|
||||
|
@ -119,6 +119,7 @@
|
||||
#import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/UIView+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/UIImage+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import <AsyncDisplayKit/NSArray+Diffing.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/UIResponder+AsyncDisplayKit.h>
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
|
||||
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASWeakSet.h>
|
||||
#import <AsyncDisplayKit/UIImage+ASConvenience.h>
|
||||
@ -148,7 +149,7 @@ static BOOL __enableHitTestDebug = NO;
|
||||
UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7];
|
||||
CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0);
|
||||
|
||||
UIGraphicsBeginImageContext(imgRect.size);
|
||||
ASGraphicsBeginImageContextWithOptions(imgRect.size, NO, 1);
|
||||
|
||||
[fillColor setFill];
|
||||
UIRectFill(imgRect);
|
||||
@ -156,8 +157,7 @@ static BOOL __enableHitTestDebug = NO;
|
||||
[self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect];
|
||||
[self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect];
|
||||
|
||||
UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
UIImage *debugHighlightImage = ASGraphicsGetImageAndEndCurrentContext();
|
||||
|
||||
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth);
|
||||
debugOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets resizingMode:UIImageResizingModeStretch];
|
||||
|
62
Source/Details/ASGraphicsContext.h
Normal file
62
Source/Details/ASGraphicsContext.h
Normal file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// ASGraphicsContext.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
|
||||
// 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 <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
@class UIImage;
|
||||
|
||||
/**
|
||||
* Functions for creating one-shot graphics contexts that do not have to copy
|
||||
* their contents when an image is generated from them. This is efficient
|
||||
* for our use, since we do not reuse graphics contexts.
|
||||
*
|
||||
* The API mirrors the UIGraphics API, with the exception that forming an image
|
||||
* ends the context as well.
|
||||
*/
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
ASDISPLAYNODE_EXTERN_C_BEGIN
|
||||
|
||||
/**
|
||||
* Call this to enable the experimental no-copy rendering.
|
||||
*
|
||||
* Returns YES if it was enabled, or NO + assert if it's too late because
|
||||
* rendering has already started. In practice it's fine to call this
|
||||
* during -didFinishLaunchingWithOptions:.
|
||||
*/
|
||||
extern BOOL ASEnableNoCopyRendering(void);
|
||||
|
||||
/**
|
||||
* Creates a one-shot context.
|
||||
*
|
||||
* Behavior is the same as UIGraphicsBeginImageContextWithOptions.
|
||||
*/
|
||||
extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);
|
||||
|
||||
/**
|
||||
* Generates and image and ends the current one-shot context.
|
||||
*
|
||||
* Behavior is the same as UIGraphicsGetImageFromCurrentImageContext followed by UIGraphicsEndImageContext.
|
||||
*/
|
||||
extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext(void);
|
||||
|
||||
/**
|
||||
* Call this if you want to end the current context without making an image.
|
||||
*
|
||||
* Behavior is the same as UIGraphicsEndImageContext.
|
||||
*/
|
||||
extern void ASGraphicsEndImageContext(void);
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_END
|
||||
NS_ASSUME_NONNULL_END
|
160
Source/Details/ASGraphicsContext.m
Normal file
160
Source/Details/ASGraphicsContext.m
Normal file
@ -0,0 +1,160 @@
|
||||
//
|
||||
// ASGraphicsContext.m
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
|
||||
// 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 "ASGraphicsContext.h"
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <UIKit/UIGraphics.h>
|
||||
#import <UIKit/UIImage.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
#pragma mark - Feature Gating
|
||||
|
||||
// Two flags that we atomically manipulate to control the feature.
|
||||
typedef NS_OPTIONS(uint, ASNoCopyFlags) {
|
||||
ASNoCopyEnabled = 1 << 0,
|
||||
ASNoCopyBlocked = 1 << 1
|
||||
};
|
||||
static atomic_uint __noCopyFlags;
|
||||
|
||||
// Check if it's blocked, and set the enabled flag if not.
|
||||
extern BOOL ASEnableNoCopyRendering()
|
||||
{
|
||||
ASNoCopyFlags expectedFlags = 0;
|
||||
BOOL enabled = atomic_compare_exchange_strong(&__noCopyFlags, &expectedFlags, ASNoCopyEnabled);
|
||||
ASDisplayNodeCAssert(enabled, @"Can't enable no-copy rendering after first render started.");
|
||||
return enabled;
|
||||
}
|
||||
|
||||
// Check if it's enabled and set the "blocked" flag either way.
|
||||
static BOOL ASNoCopyRenderingBlockAndCheckEnabled() {
|
||||
ASNoCopyFlags oldFlags = atomic_fetch_or(&__noCopyFlags, ASNoCopyBlocked);
|
||||
return (oldFlags & ASNoCopyEnabled) != 0;
|
||||
}
|
||||
|
||||
#pragma mark - Callbacks
|
||||
|
||||
void _ASReleaseCGDataProviderData(__unused void *info, const void *data, __unused size_t size)
|
||||
{
|
||||
free((void *)data);
|
||||
}
|
||||
|
||||
#pragma mark - Graphics Contexts
|
||||
|
||||
extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
|
||||
{
|
||||
if (!ASNoCopyRenderingBlockAndCheckEnabled()) {
|
||||
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only create device RGB color space once. UIGraphics actually doesn't do this but it's safe.
|
||||
static dispatch_once_t onceToken;
|
||||
static CGFloat defaultScale;
|
||||
static CGColorSpaceRef deviceRGB;
|
||||
dispatch_once(&onceToken, ^{
|
||||
deviceRGB = CGColorSpaceCreateDeviceRGB();
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 0);
|
||||
CGContextRef uikitContext = UIGraphicsGetCurrentContext();
|
||||
defaultScale = CGContextGetCTM(uikitContext).a;
|
||||
UIGraphicsEndImageContext();
|
||||
});
|
||||
|
||||
// These options are taken from UIGraphicsBeginImageContext.
|
||||
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst);
|
||||
|
||||
if (scale == 0) {
|
||||
scale = defaultScale;
|
||||
}
|
||||
size_t intWidth = (size_t)ceil(size.width * scale);
|
||||
size_t intHeight = (size_t)ceil(size.height * scale);
|
||||
size_t bytesPerPixel = 4;
|
||||
size_t bytesPerRow = bytesPerPixel * intWidth;
|
||||
size_t bufferSize = bytesPerRow * intHeight;
|
||||
|
||||
// We create our own buffer, and wrap the context around that. This way we can prevent
|
||||
// the copy that usually gets made when you form a CGImage from the context.
|
||||
void *buf = calloc(bufferSize, 1);
|
||||
CGContextRef context = CGBitmapContextCreate(buf, intWidth, intHeight, 8, bytesPerRow, deviceRGB, bitmapInfo);
|
||||
|
||||
// Set the CTM to account for iOS orientation & specified scale.
|
||||
// If only we could use CGContextSetBaseCTM. It doesn't
|
||||
// seem like there are any consequences for our use case
|
||||
// but we'll be on the look out. The internet hinted that it
|
||||
// affects shadowing but I tested and shadowing works.
|
||||
CGContextTranslateCTM(context, 0, intHeight);
|
||||
CGContextScaleCTM(context, scale, -scale);
|
||||
|
||||
// Save the state so we can restore it and recover our scale in GetImageAndEnd
|
||||
CGContextSaveGState(context);
|
||||
|
||||
UIGraphicsPushContext(context);
|
||||
}
|
||||
|
||||
extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext()
|
||||
{
|
||||
if (!ASNoCopyRenderingBlockAndCheckEnabled()) {
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return image;
|
||||
}
|
||||
|
||||
// Pop the context and make sure we have one.
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (context == NULL) {
|
||||
ASDisplayNodeCFailAssert(@"Can't end image context without having begun one.");
|
||||
return nil;
|
||||
}
|
||||
UIGraphicsPopContext();
|
||||
|
||||
// Do some math to get the image properties.
|
||||
size_t width = CGBitmapContextGetWidth(context);
|
||||
size_t height = CGBitmapContextGetHeight(context);
|
||||
size_t bitsPerPixel = CGBitmapContextGetBitsPerPixel(context);
|
||||
size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context);
|
||||
size_t bufferSize = bytesPerRow * height;
|
||||
|
||||
// This is the buf that we malloc'd above.
|
||||
void *buf = CGBitmapContextGetData(context);
|
||||
|
||||
// Wrap it in a CGDataProvider, passing along our release callback for when the CGImage dies.
|
||||
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buf, bufferSize, _ASReleaseCGDataProviderData);
|
||||
|
||||
// Create the CGImage. Options taken from CGBitmapContextCreateImage.
|
||||
CGImageRef cgImg = CGImageCreate(width, height, CGBitmapContextGetBitsPerComponent(context), bitsPerPixel, bytesPerRow, CGBitmapContextGetColorSpace(context), CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault);
|
||||
CGDataProviderRelease(provider);
|
||||
|
||||
// We saved our GState right after setting the CTM so that we could restore it
|
||||
// here and get the original scale back.
|
||||
CGContextRestoreGState(context);
|
||||
CGFloat scale = CGContextGetCTM(context).a;
|
||||
CGContextRelease(context);
|
||||
|
||||
UIImage *result = [[UIImage alloc] initWithCGImage:cgImg scale:scale orientation:UIImageOrientationUp];
|
||||
CGImageRelease(cgImg);
|
||||
return result;
|
||||
}
|
||||
|
||||
extern void ASGraphicsEndImageContext()
|
||||
{
|
||||
if (!ASNoCopyRenderingBlockAndCheckEnabled()) {
|
||||
UIGraphicsEndImageContext();
|
||||
return;
|
||||
}
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (context) {
|
||||
// We manually allocated this buffer so we need to free it.
|
||||
free(CGBitmapContextGetData(context));
|
||||
CGContextRelease(context);
|
||||
UIGraphicsPopContext();
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASSignpost.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
@ -218,15 +219,14 @@
|
||||
displayBlock = ^id{
|
||||
CHECK_CANCELLED_AND_RETURN_NIL();
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
|
||||
ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
|
||||
|
||||
for (dispatch_block_t block in displayBlocks) {
|
||||
CHECK_CANCELLED_AND_RETURN_NIL(UIGraphicsEndImageContext());
|
||||
CHECK_CANCELLED_AND_RETURN_NIL(ASGraphicsEndImageContext());
|
||||
block();
|
||||
}
|
||||
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
|
||||
ASDN_DELAY_FOR_DISPLAY();
|
||||
return image;
|
||||
@ -236,8 +236,8 @@
|
||||
CHECK_CANCELLED_AND_RETURN_NIL();
|
||||
|
||||
if (shouldCreateGraphicsContext) {
|
||||
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
|
||||
CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); );
|
||||
ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
|
||||
CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); );
|
||||
}
|
||||
|
||||
CGContextRef currentContext = UIGraphicsGetCurrentContext();
|
||||
@ -256,9 +256,8 @@
|
||||
[self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor];
|
||||
|
||||
if (shouldCreateGraphicsContext) {
|
||||
CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); );
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); );
|
||||
image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
}
|
||||
|
||||
ASDN_DELAY_FOR_DISPLAY();
|
||||
@ -332,7 +331,7 @@
|
||||
bounds.size.height *= contentsScale;
|
||||
CGFloat white = 0.0f, alpha = 0.0f;
|
||||
[backgroundColor getWhite:&white alpha:&alpha];
|
||||
UIGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale);
|
||||
ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale);
|
||||
[*image drawInRect:bounds];
|
||||
} else {
|
||||
bounds = CGContextGetClipBoundingBox(context);
|
||||
@ -362,8 +361,7 @@
|
||||
[roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil.
|
||||
|
||||
if (*image) {
|
||||
*image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
*image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/UIImage+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
|
||||
@ -138,7 +139,7 @@ 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.
|
||||
UIGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale);
|
||||
ASGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale);
|
||||
|
||||
BOOL contextIsClean = YES;
|
||||
if (cornerColor) {
|
||||
@ -168,8 +169,7 @@ UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollectio
|
||||
[strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1];
|
||||
}
|
||||
|
||||
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
UIImage *result = ASGraphicsGetImageAndEndCurrentContext();
|
||||
|
||||
UIEdgeInsets capInsets = UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius);
|
||||
result = [result resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch];
|
||||
|
@ -22,6 +22,8 @@
|
||||
#import "WindowWithStatusBarUnderlay.h"
|
||||
#import "Utilities.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
|
||||
#define WEAVER 0
|
||||
|
||||
#if WEAVER
|
||||
@ -38,6 +40,8 @@
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
|
||||
ASEnableNoCopyRendering();
|
||||
|
||||
// this UIWindow subclass is neccessary to make the status bar opaque
|
||||
_window = [[WindowWithStatusBarUnderlay alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
_window.backgroundColor = [UIColor whiteColor];
|
||||
|
Loading…
x
Reference in New Issue
Block a user