mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Animation rendering
This commit is contained in:
@@ -212,14 +212,12 @@ using AS::MutexLocker;
|
||||
displayBlock = ^id{
|
||||
CHECK_CANCELLED_AND_RETURN_NIL();
|
||||
|
||||
ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
|
||||
|
||||
for (dispatch_block_t block in displayBlocks) {
|
||||
CHECK_CANCELLED_AND_RETURN_NIL(ASGraphicsEndImageContext());
|
||||
block();
|
||||
}
|
||||
|
||||
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
UIImage *image = ASGraphicsCreateImage(self.primitiveTraitCollection, bounds.size, opaque, contentsScaleForDisplay, nil, isCancelledBlock, ^{
|
||||
for (dispatch_block_t block in displayBlocks) {
|
||||
if (isCancelledBlock()) return;
|
||||
block();
|
||||
}
|
||||
});
|
||||
|
||||
ASDN_DELAY_FOR_DISPLAY();
|
||||
return image;
|
||||
@@ -228,38 +226,35 @@ using AS::MutexLocker;
|
||||
displayBlock = ^id{
|
||||
CHECK_CANCELLED_AND_RETURN_NIL();
|
||||
|
||||
if (shouldCreateGraphicsContext) {
|
||||
ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
|
||||
CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); );
|
||||
}
|
||||
|
||||
CGContextRef currentContext = UIGraphicsGetCurrentContext();
|
||||
UIImage *image = nil;
|
||||
|
||||
if (shouldCreateGraphicsContext && !currentContext) {
|
||||
//ASDisplayNodeAssert(NO, @"Failed to create a CGContext (size: %@)", NSStringFromCGSize(bounds.size));
|
||||
return nil;
|
||||
}
|
||||
|
||||
// For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or
|
||||
// _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs.
|
||||
[self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters];
|
||||
|
||||
if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly.
|
||||
image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock];
|
||||
} else if (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext.
|
||||
[self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
|
||||
}
|
||||
|
||||
[self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor];
|
||||
|
||||
if (shouldCreateGraphicsContext) {
|
||||
CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); );
|
||||
image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
}
|
||||
|
||||
ASDN_DELAY_FOR_DISPLAY();
|
||||
return image;
|
||||
__block UIImage *image = nil;
|
||||
void (^workWithContext)() = ^{
|
||||
CGContextRef currentContext = UIGraphicsGetCurrentContext();
|
||||
|
||||
if (shouldCreateGraphicsContext && !currentContext) {
|
||||
ASDisplayNodeAssert(NO, @"Failed to create a CGContext (size: %@)", NSStringFromCGSize(bounds.size));
|
||||
return;
|
||||
}
|
||||
|
||||
// For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or
|
||||
// _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs.
|
||||
[self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters];
|
||||
|
||||
if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly.
|
||||
image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock];
|
||||
} else if (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext.
|
||||
[self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
|
||||
}
|
||||
|
||||
[self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor];
|
||||
ASDN_DELAY_FOR_DISPLAY();
|
||||
};
|
||||
|
||||
if (shouldCreateGraphicsContext) {
|
||||
return ASGraphicsCreateImage(self.primitiveTraitCollection, bounds.size, opaque, contentsScaleForDisplay, nil, isCancelledBlock, workWithContext);
|
||||
} else {
|
||||
workWithContext();
|
||||
return image;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -309,9 +304,6 @@ using AS::MutexLocker;
|
||||
}
|
||||
|
||||
__instanceLock__.lock();
|
||||
ASCornerRoundingType cornerRoundingType = _cornerRoundingType;
|
||||
CGFloat cornerRadius = _cornerRadius;
|
||||
CGFloat contentsScale = _contentsScaleForDisplay;
|
||||
ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext;
|
||||
__instanceLock__.unlock();
|
||||
|
||||
@@ -320,48 +312,6 @@ using AS::MutexLocker;
|
||||
didDisplayNodeContentWithRenderingContext(context, drawParameters);
|
||||
}
|
||||
}
|
||||
|
||||
if (cornerRoundingType == ASCornerRoundingTypePrecomposited && cornerRadius > 0.0f) {
|
||||
CGRect bounds = CGRectZero;
|
||||
if (context == NULL) {
|
||||
bounds = self.threadSafeBounds;
|
||||
bounds.size.width *= contentsScale;
|
||||
bounds.size.height *= contentsScale;
|
||||
CGFloat white = 0.0f, alpha = 0.0f;
|
||||
[backgroundColor getWhite:&white alpha:&alpha];
|
||||
ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale);
|
||||
[*image drawInRect:bounds];
|
||||
} else {
|
||||
bounds = CGContextGetClipBoundingBox(context);
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self);
|
||||
|
||||
UIBezierPath *roundedHole = [UIBezierPath bezierPathWithRect:bounds];
|
||||
[roundedHole appendPath:[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius * contentsScale]];
|
||||
roundedHole.usesEvenOddFillRule = YES;
|
||||
|
||||
UIBezierPath *roundedPath = nil;
|
||||
if (borderWidth > 0.0f) { // Don't create roundedPath and stroke if borderWidth is 0.0
|
||||
CGFloat strokeThickness = borderWidth * contentsScale;
|
||||
CGFloat strokeInset = ((strokeThickness + 1.0f) / 2.0f) - 1.0f;
|
||||
roundedPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(bounds, strokeInset, strokeInset)
|
||||
cornerRadius:_cornerRadius * contentsScale];
|
||||
roundedPath.lineWidth = strokeThickness;
|
||||
[[UIColor colorWithCGColor:borderColor] setStroke];
|
||||
}
|
||||
|
||||
// Punch out the corners by copying the backgroundColor over them.
|
||||
// This works for everything from clearColor to opaque colors.
|
||||
[backgroundColor setFill];
|
||||
[roundedHole fillWithBlendMode:kCGBlendModeCopy alpha:1.0f];
|
||||
|
||||
[roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil.
|
||||
|
||||
if (*image) {
|
||||
*image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously
|
||||
|
||||
@@ -1539,41 +1539,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor:(UIColor *)backgroundColor
|
||||
{
|
||||
ASPerformBlockOnMainThread(^{
|
||||
for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) {
|
||||
// Layers are, in order: Top Left, Top Right, Bottom Right, Bottom Left.
|
||||
// anchorPoint is Bottom Left at 0,0 and Top Right at 1,1.
|
||||
BOOL isTop = (idx == 0 || idx == 1);
|
||||
BOOL isRight = (idx == 1 || idx == 2);
|
||||
|
||||
CGSize size = CGSizeMake(radius + 1, radius + 1);
|
||||
ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay);
|
||||
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
if (isRight == YES) {
|
||||
CGContextTranslateCTM(ctx, -radius + 1, 0);
|
||||
}
|
||||
if (isTop == YES) {
|
||||
CGContextTranslateCTM(ctx, 0, -radius + 1);
|
||||
}
|
||||
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius];
|
||||
[roundedRect setUsesEvenOddFillRule:YES];
|
||||
[roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]];
|
||||
[backgroundColor setFill];
|
||||
[roundedRect fill];
|
||||
|
||||
// No lock needed, as _clipCornerLayers is only modified on the main thread.
|
||||
CALayer *clipCornerLayer = _clipCornerLayers[idx];
|
||||
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);
|
||||
}
|
||||
[self _layoutClipCornersIfNeeded];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_setClipCornerLayersVisible:(BOOL)visible
|
||||
{
|
||||
}
|
||||
@@ -1634,7 +1599,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
}
|
||||
else if (newRoundingType == ASCornerRoundingTypeClipping) {
|
||||
// Clip corners already exist, but the radius has changed.
|
||||
[self _updateClipCornerLayerContentsWithRadius:newCornerRadius backgroundColor:self.backgroundColor];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,161 +7,130 @@
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import <AsyncDisplayKit/ASCGImageBuffer.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <UIKit/UIGraphics.h>
|
||||
#import <UIKit/UIImage.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
/**
|
||||
* Our version of the private CGBitmapGetAlignedBytesPerRow function.
|
||||
*
|
||||
* In both 32-bit and 64-bit, this function rounds up to nearest multiple of 32
|
||||
* in iOS 9, 10, and 11. We'll try to catch if this ever changes by asserting that
|
||||
* the bytes-per-row for a 1x1 context from the system is 32.
|
||||
*/
|
||||
static size_t ASGraphicsGetAlignedBytesPerRow(size_t baseValue) {
|
||||
// Add 31 then zero out low 5 bits.
|
||||
return (baseValue + 31) & ~0x1F;
|
||||
}
|
||||
|
||||
/**
|
||||
* A key used to associate CGContextRef -> NSMutableData, nonatomic retain.
|
||||
*
|
||||
* That way the data will be released when the context dies. If they pull an image,
|
||||
* we will retain the data object (in a CGDataProvider) before releasing the context.
|
||||
*/
|
||||
static UInt8 __contextDataAssociationKey;
|
||||
#if AS_AT_LEAST_IOS13
|
||||
#define ASPerformBlockWithTraitCollection(work, traitCollection) \
|
||||
if (@available(iOS 13.0, tvOS 13.0, *)) { \
|
||||
UITraitCollection *uiTraitCollection = ASPrimitiveTraitCollectionToUITraitCollection(traitCollection); \
|
||||
[uiTraitCollection performAsCurrentTraitCollection:^{ \
|
||||
work(); \
|
||||
}];\
|
||||
} else { \
|
||||
work(); \
|
||||
}
|
||||
#else
|
||||
#define ASPerformBlockWithTraitCollection(work, traitCollection) work();
|
||||
#endif
|
||||
|
||||
#pragma mark - Graphics Contexts
|
||||
|
||||
void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
|
||||
NS_AVAILABLE_IOS(10)
|
||||
NS_INLINE void ASConfigureExtendedRange(UIGraphicsImageRendererFormat *format)
|
||||
{
|
||||
if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) {
|
||||
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
|
||||
return;
|
||||
if (AS_AVAILABLE_IOS_TVOS(12, 12)) {
|
||||
// nop. We always use automatic range on iOS >= 12.
|
||||
} else {
|
||||
// Currently we never do wide color. One day we could pipe this information through from the ASImageNode if it was worth it.
|
||||
format.prefersExtendedRange = NO;
|
||||
}
|
||||
|
||||
// We use "reference contexts" to get device-specific options that UIKit
|
||||
// uses.
|
||||
static dispatch_once_t onceToken;
|
||||
static CGContextRef refCtxOpaque;
|
||||
static CGContextRef refCtxTransparent;
|
||||
dispatch_once(&onceToken, ^{
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 1);
|
||||
refCtxOpaque = CGContextRetain(UIGraphicsGetCurrentContext());
|
||||
ASDisplayNodeCAssert(CGBitmapContextGetBytesPerRow(refCtxOpaque) == 32, @"Expected bytes per row to be aligned to 32. Has CGBitmapGetAlignedBytesPerRow implementation changed?");
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
// Make transparent ref context.
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1);
|
||||
refCtxTransparent = CGContextRetain(UIGraphicsGetCurrentContext());
|
||||
UIGraphicsEndImageContext();
|
||||
});
|
||||
|
||||
// These options are taken from UIGraphicsBeginImageContext.
|
||||
CGContextRef refCtx = opaque ? refCtxOpaque : refCtxTransparent;
|
||||
CGBitmapInfo bitmapInfo = CGBitmapContextGetBitmapInfo(refCtx);
|
||||
|
||||
if (scale == 0) {
|
||||
scale = ASScreenScale();
|
||||
}
|
||||
size_t intWidth = (size_t)ceil(size.width * scale);
|
||||
size_t intHeight = (size_t)ceil(size.height * scale);
|
||||
size_t bitsPerComponent = CGBitmapContextGetBitsPerComponent(refCtx);
|
||||
size_t bytesPerRow = CGBitmapContextGetBitsPerPixel(refCtx) * intWidth / 8;
|
||||
bytesPerRow = ASGraphicsGetAlignedBytesPerRow(bytesPerRow);
|
||||
size_t bufferSize = bytesPerRow * intHeight;
|
||||
CGColorSpaceRef colorspace = CGBitmapContextGetColorSpace(refCtx);
|
||||
|
||||
// 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.
|
||||
ASCGImageBuffer *buffer = [[ASCGImageBuffer alloc] initWithLength:bufferSize];
|
||||
|
||||
CGContextRef context = CGBitmapContextCreate(buffer.mutableBytes, intWidth, intHeight, bitsPerComponent, bytesPerRow, colorspace, bitmapInfo);
|
||||
|
||||
// Transfer ownership of the data to the context. So that if the context
|
||||
// is destroyed before we create an image from it, the data will be released.
|
||||
objc_setAssociatedObject((__bridge id)context, &__contextDataAssociationKey, buffer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
// 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);
|
||||
|
||||
// Transfer context ownership to the UIKit stack.
|
||||
UIGraphicsPushContext(context);
|
||||
CGContextRelease(context);
|
||||
}
|
||||
|
||||
UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() NS_RETURNS_RETAINED
|
||||
UIImage *ASGraphicsCreateImageWithOptions(CGSize size, BOOL opaque, CGFloat scale, UIImage *sourceImage,
|
||||
asdisplaynode_iscancelled_block_t NS_NOESCAPE isCancelled,
|
||||
void (^NS_NOESCAPE work)())
|
||||
{
|
||||
if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Read the device-specific ICC-based color space to use for the image.
|
||||
// For DeviceRGB contexts (e.g. UIGraphics), CGBitmapContextCreateImage
|
||||
// generates an image in a device-specific color space (for wide color support).
|
||||
// We replicate that behavior, even though at this time CA does not
|
||||
// require the image to be in this space. Plain DeviceRGB images seem
|
||||
// to be treated exactly the same, but better safe than sorry.
|
||||
static CGColorSpaceRef imageColorSpace;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
|
||||
UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
imageColorSpace = CGColorSpaceRetain(CGImageGetColorSpace(refImage.CGImage));
|
||||
ASDisplayNodeCAssertNotNil(imageColorSpace, nil);
|
||||
UIGraphicsEndImageContext();
|
||||
});
|
||||
|
||||
// Retrieve our buffer and create a CGDataProvider from it.
|
||||
ASCGImageBuffer *buffer = objc_getAssociatedObject((__bridge id)context, &__contextDataAssociationKey);
|
||||
ASDisplayNodeCAssertNotNil(buffer, nil);
|
||||
CGDataProviderRef provider = [buffer createDataProviderAndInvalidate];
|
||||
|
||||
// Create the CGImage. Options taken from CGBitmapContextCreateImage.
|
||||
CGImageRef cgImg = CGImageCreate(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context), CGBitmapContextGetBitsPerComponent(context), CGBitmapContextGetBitsPerPixel(context), CGBitmapContextGetBytesPerRow(context), imageColorSpace, 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;
|
||||
|
||||
// Note: popping from the UIKit stack will probably destroy the context.
|
||||
context = NULL;
|
||||
UIGraphicsPopContext();
|
||||
|
||||
UIImage *result = [[UIImage alloc] initWithCGImage:cgImg scale:scale orientation:UIImageOrientationUp];
|
||||
CGImageRelease(cgImg);
|
||||
return result;
|
||||
return ASGraphicsCreateImage(ASPrimitiveTraitCollectionMakeDefault(), size, opaque, scale, sourceImage, isCancelled, work);
|
||||
}
|
||||
|
||||
void ASGraphicsEndImageContext()
|
||||
{
|
||||
if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) {
|
||||
UIGraphicsEndImageContext();
|
||||
return;
|
||||
UIImage *ASGraphicsCreateImage(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * sourceImage, asdisplaynode_iscancelled_block_t NS_NOESCAPE isCancelled, void (NS_NOESCAPE ^work)()) {
|
||||
if (@available(iOS 10.0, *)) {
|
||||
if (true /*ASActivateExperimentalFeature(ASExperimentalDrawingGlobal)*/) {
|
||||
// If they used default scale, reuse one of two preferred formats.
|
||||
static UIGraphicsImageRendererFormat *defaultFormat;
|
||||
static UIGraphicsImageRendererFormat *opaqueFormat;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
|
||||
defaultFormat = [UIGraphicsImageRendererFormat preferredFormat];
|
||||
opaqueFormat = [UIGraphicsImageRendererFormat preferredFormat];
|
||||
} else {
|
||||
defaultFormat = [UIGraphicsImageRendererFormat defaultFormat];
|
||||
opaqueFormat = [UIGraphicsImageRendererFormat defaultFormat];
|
||||
}
|
||||
opaqueFormat.opaque = YES;
|
||||
ASConfigureExtendedRange(defaultFormat);
|
||||
ASConfigureExtendedRange(opaqueFormat);
|
||||
});
|
||||
|
||||
UIGraphicsImageRendererFormat *format;
|
||||
if (sourceImage) {
|
||||
if (sourceImage.renderingMode == UIImageRenderingModeAlwaysTemplate) {
|
||||
// Template images will be black and transparent, so if we use
|
||||
// sourceImage.imageRenderFormat it will assume a grayscale color space.
|
||||
// This is not good because a template image should be able to tint to any color,
|
||||
// so we'll just use the default here.
|
||||
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
|
||||
format = [UIGraphicsImageRendererFormat preferredFormat];
|
||||
} else {
|
||||
format = [UIGraphicsImageRendererFormat defaultFormat];
|
||||
}
|
||||
} else {
|
||||
format = sourceImage.imageRendererFormat;
|
||||
}
|
||||
// We only want the private bits (color space and bits per component) from the image.
|
||||
// We have our own ideas about opacity and scale.
|
||||
format.opaque = opaque;
|
||||
format.scale = scale;
|
||||
} else if (scale == 0 || scale == ASScreenScale()) {
|
||||
format = opaque ? opaqueFormat : defaultFormat;
|
||||
} else {
|
||||
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
|
||||
format = [UIGraphicsImageRendererFormat preferredFormat];
|
||||
} else {
|
||||
format = [UIGraphicsImageRendererFormat defaultFormat];
|
||||
}
|
||||
if (opaque) format.opaque = YES;
|
||||
format.scale = scale;
|
||||
ASConfigureExtendedRange(format);
|
||||
}
|
||||
|
||||
// Avoid using the imageWithActions: method because it does not support cancellation at the
|
||||
// last moment i.e. before actually creating the resulting image.
|
||||
__block UIImage *image;
|
||||
NSError *error;
|
||||
[[[UIGraphicsImageRenderer alloc] initWithSize:size format:format]
|
||||
runDrawingActions:^(UIGraphicsImageRendererContext *rendererContext) {
|
||||
ASDisplayNodeCAssert(UIGraphicsGetCurrentContext(), @"Should have a context!");
|
||||
ASPerformBlockWithTraitCollection(work, traitCollection);
|
||||
}
|
||||
completionActions:^(UIGraphicsImageRendererContext *rendererContext) {
|
||||
if (isCancelled == nil || !isCancelled()) {
|
||||
image = rendererContext.currentImage;
|
||||
}
|
||||
}
|
||||
error:&error];
|
||||
if (error) {
|
||||
NSCAssert(NO, @"Error drawing: %@", error);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
UIGraphicsPopContext();
|
||||
|
||||
// Bad OS or experiment flag. Use UIGraphics* API.
|
||||
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
|
||||
ASPerformBlockWithTraitCollection(work, traitCollection)
|
||||
UIImage *image = nil;
|
||||
if (isCancelled == nil || !isCancelled()) {
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
}
|
||||
UIGraphicsEndImageContext();
|
||||
return image;
|
||||
}
|
||||
|
||||
UIImage *ASGraphicsCreateImageWithTraitCollectionAndOptions(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * sourceImage, void (NS_NOESCAPE ^work)()) {
|
||||
return ASGraphicsCreateImage(traitCollection, size, opaque, scale, sourceImage, nil, work);
|
||||
}
|
||||
|
||||
@@ -6,46 +6,60 @@
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.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.
|
||||
*
|
||||
* Note: You must not mix-and-match between ASGraphics* and UIGraphics* functions
|
||||
* within the same drawing operation.
|
||||
*/
|
||||
#import <AsyncDisplayKit/ASBlockTypes.h>
|
||||
#import <AsyncDisplayKit/ASTraitCollection.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Creates a one-shot context.
|
||||
* A wrapper for the UIKit drawing APIs. If you are in ASExperimentalDrawingGlobal, and you have iOS >= 10, we will create
|
||||
* a UIGraphicsRenderer with an appropriate format. Otherwise, we will use UIGraphicsBeginImageContext et al.
|
||||
*
|
||||
* Behavior is the same as UIGraphicsBeginImageContextWithOptions.
|
||||
* @param size The size of the context.
|
||||
* @param opaque Whether the context should be opaque or not.
|
||||
* @param scale The scale of the context. 0 uses main screen scale.
|
||||
* @param sourceImage If you are planning to render a UIImage into this context, provide it here and we will use its
|
||||
* preferred renderer format if we are using UIGraphicsImageRenderer.
|
||||
* @param isCancelled An optional block for canceling the drawing before forming the image. Only takes effect under
|
||||
* the legacy code path, as UIGraphicsRenderer does not support cancellation.
|
||||
* @param work A block, wherein the current UIGraphics context is set based on the arguments.
|
||||
*
|
||||
* @return The rendered image. You can also render intermediary images using UIGraphicsGetImageFromCurrentImageContext.
|
||||
*/
|
||||
AS_EXTERN void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);
|
||||
UIImage *ASGraphicsCreateImageWithOptions(CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, asdisplaynode_iscancelled_block_t NS_NOESCAPE _Nullable isCancelled, void (NS_NOESCAPE ^work)(void)) ASDISPLAYNODE_DEPRECATED_MSG("Use ASGraphicsCreateImageWithTraitCollectionAndOptions instead");
|
||||
|
||||
/**
|
||||
* Generates and image and ends the current one-shot context.
|
||||
*
|
||||
* Behavior is the same as UIGraphicsGetImageFromCurrentImageContext followed by UIGraphicsEndImageContext.
|
||||
*/
|
||||
AS_EXTERN UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext(void) NS_RETURNS_RETAINED;
|
||||
* A wrapper for the UIKit drawing APIs. If you are in ASExperimentalDrawingGlobal, and you have iOS >= 10, we will create
|
||||
* a UIGraphicsRenderer with an appropriate format. Otherwise, we will use UIGraphicsBeginImageContext et al.
|
||||
*
|
||||
* @param traitCollection Trait collection. The `work` block will be executed with this trait collection, so it will affect dynamic colors, etc.
|
||||
* @param size The size of the context.
|
||||
* @param opaque Whether the context should be opaque or not.
|
||||
* @param scale The scale of the context. 0 uses main screen scale.
|
||||
* @param sourceImage If you are planning to render a UIImage into this context, provide it here and we will use its
|
||||
* preferred renderer format if we are using UIGraphicsImageRenderer.
|
||||
* @param isCancelled An optional block for canceling the drawing before forming the image.
|
||||
* @param work A block, wherein the current UIGraphics context is set based on the arguments.
|
||||
*
|
||||
* @return The rendered image. You can also render intermediary images using UIGraphicsGetImageFromCurrentImageContext.
|
||||
*/
|
||||
UIImage *ASGraphicsCreateImage(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, asdisplaynode_iscancelled_block_t _Nullable NS_NOESCAPE isCancelled, void (NS_NOESCAPE ^work)(void));
|
||||
|
||||
/**
|
||||
* Call this if you want to end the current context without making an image.
|
||||
*
|
||||
* Behavior is the same as UIGraphicsEndImageContext.
|
||||
*/
|
||||
AS_EXTERN void ASGraphicsEndImageContext(void);
|
||||
* A wrapper for the UIKit drawing APIs.
|
||||
*
|
||||
* @param traitCollection Trait collection. The `work` block will be executed with this trait collection, so it will affect dynamic colors, etc.
|
||||
* @param size The size of the context.
|
||||
* @param opaque Whether the context should be opaque or not.
|
||||
* @param scale The scale of the context. 0 uses main screen scale.
|
||||
* @param sourceImage If you are planning to render a UIImage into this context, provide it here and we will use its
|
||||
* preferred renderer format if we are using UIGraphicsImageRenderer.
|
||||
* @param work A block, wherein the current UIGraphics context is set based on the arguments.
|
||||
*
|
||||
* @return The rendered image. You can also render intermediary images using UIGraphicsGetImageFromCurrentImageContext.
|
||||
*/
|
||||
UIImage *ASGraphicsCreateImageWithTraitCollectionAndOptions(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, void (NS_NOESCAPE ^work)(void)) ASDISPLAYNODE_DEPRECATED_MSG("Use ASGraphicsCreateImage instead");
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
Reference in New Issue
Block a user