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:
Adlai Holler 2018-01-13 19:19:08 -08:00 committed by GitHub
parent 3708f2e448
commit 1d105c2056
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 272 additions and 44 deletions

View File

@ -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 */,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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();
}
}

View File

@ -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();
}
}
}

View File

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

View File

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