| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719 |
- //
- // ASImageNode.mm
- // AsyncDisplayKit
- //
- // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
- // This source code is licensed under the BSD-style license found in the
- // LICENSE file in the root directory of this source tree. An additional grant
- // of patent rights can be found in the PATENTS file in the same directory.
- //
- #import "ASImageNode.h"
- #import <tgmath.h>
- #import "_ASDisplayLayer.h"
- #import "ASAssert.h"
- #import "ASDimension.h"
- #import "ASDisplayNode+FrameworkSubclasses.h"
- #import "ASDisplayNodeExtras.h"
- #import "ASDisplayNode+Beta.h"
- #import "ASLayout.h"
- #import "ASTextNode.h"
- #import "ASImageNode+AnimatedImagePrivate.h"
- #import "ASImageNode+CGExtras.h"
- #import "AsyncDisplayKit+Debug.h"
- #import "ASInternalHelpers.h"
- #import "ASEqualityHelpers.h"
- #import "ASEqualityHashHelpers.h"
- #import "ASWeakMap.h"
- #import "CoreGraphics+ASConvenience.h"
- // TODO: It would be nice to remove this dependency; it's the only subclass using more than +FrameworkSubclasses.h
- #import "ASDisplayNodeInternal.h"
- #include <functional>
- struct ASImageNodeDrawParameters {
- BOOL opaque;
- CGRect bounds;
- CGFloat contentsScale;
- UIColor *backgroundColor;
- UIViewContentMode contentMode;
- BOOL cropEnabled;
- BOOL forceUpscaling;
- CGSize forcedSize;
- CGRect cropRect;
- CGRect cropDisplayBounds;
- asimagenode_modification_block_t imageModificationBlock;
- };
- /**
- * Contains all data that is needed to generate the content bitmap.
- */
- @interface ASImageNodeContentsKey : NSObject {}
- @property (nonatomic, strong) UIImage *image;
- @property CGSize backingSize;
- @property CGRect imageDrawRect;
- @property BOOL isOpaque;
- @property (nonatomic, strong) UIColor *backgroundColor;
- @property (nonatomic, copy) ASDisplayNodeContextModifier preContextBlock;
- @property (nonatomic, copy) ASDisplayNodeContextModifier postContextBlock;
- @property (nonatomic, copy) asimagenode_modification_block_t imageModificationBlock;
- @end
- @implementation ASImageNodeContentsKey
- - (BOOL)isEqual:(id)object
- {
- if (self == object) {
- return YES;
- }
- // Optimization opportunity: The `isKindOfClass` call here could be avoided by not using the NSObject `isEqual:`
- // convention and instead using a custom comparison function that assumes all items are heterogeneous.
- // However, profiling shows that our entire `isKindOfClass` expression is only ~1/40th of the total
- // overheard of our caching, so it's likely not high-impact.
- if ([object isKindOfClass:[ASImageNodeContentsKey class]]) {
- ASImageNodeContentsKey *other = (ASImageNodeContentsKey *)object;
- return [_image isEqual:other.image]
- && CGSizeEqualToSize(_backingSize, other.backingSize)
- && CGRectEqualToRect(_imageDrawRect, other.imageDrawRect)
- && _isOpaque == other.isOpaque
- && [_backgroundColor isEqual:other.backgroundColor]
- && _preContextBlock == other.preContextBlock
- && _postContextBlock == other.postContextBlock
- && _imageModificationBlock == other.imageModificationBlock;
- } else {
- return NO;
- }
- }
- - (NSUInteger)hash
- {
- NSUInteger subhashes[] = {
- // Profiling shows that the work done in UIImage's `hash` is on the order of 0.005ms on an A5 processor
- // and isn't proportional to the size of the image.
- [_image hash],
-
- // TODO: Hashing the floats in a CGRect or CGSize is tricky. Equality of floats is
- // fuzzy, but it's a 100% requirement that two equal values must produce an identical hash value.
- // Until there's a robust solution for hashing floats, leave all float values out of the hash.
- // This may lead to a greater number of isEqual comparisons but does not comprimise correctness.
- //AS::hash<CGRect>()(_backingSize),
- //AS::hash<CGRect>()(_imageDrawRect),
- AS::hash<BOOL>()(_isOpaque),
- [_backgroundColor hash],
- AS::hash<void *>()((void*)_preContextBlock),
- AS::hash<void *>()((void*)_postContextBlock),
- AS::hash<void *>()((void*)_imageModificationBlock),
- };
- return ASIntegerArrayHash(subhashes, sizeof(subhashes) / sizeof(subhashes[0]));
- }
- @end
- @implementation ASImageNode
- {
- @private
- UIImage *_image;
- ASWeakMapEntry *_weakCacheEntry; // Holds a reference that keeps our contents in cache.
- void (^_displayCompletionBlock)(BOOL canceled);
-
- // Drawing
- ASImageNodeDrawParameters _drawParameter;
- ASTextNode *_debugLabelNode;
-
- // Cropping.
- BOOL _cropEnabled; // Defaults to YES.
- BOOL _forceUpscaling; //Defaults to NO.
- CGSize _forcedSize; //Defaults to CGSizeZero, indicating no forced size.
- CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0)
- CGRect _cropDisplayBounds; // Defaults to CGRectNull
- }
- @synthesize image = _image;
- @synthesize imageModificationBlock = _imageModificationBlock;
- #pragma mark - NSObject
- + (void)initialize
- {
- [super initialize];
-
- if (self != [ASImageNode class]) {
- // Prevent custom drawing in subclasses
- ASDisplayNodeAssert(!ASSubclassOverridesClassSelector([ASImageNode class], self, @selector(displayWithParameters:isCancelled:)), @"Subclass %@ must not override displayWithParameters:isCancelled: method. Custom drawing in %@ subclass is not supported.", NSStringFromClass(self), NSStringFromClass([ASImageNode class]));
- }
- }
- - (instancetype)init
- {
- if (!(self = [super init]))
- return nil;
- // TODO can this be removed?
- self.contentsScale = ASScreenScale();
- self.contentMode = UIViewContentModeScaleAspectFill;
- self.opaque = NO;
-
- // If no backgroundColor is set to the image node and it's a subview of UITableViewCell, UITableView is setting
- // the opaque value of all subviews to YES if highlighting / selection is happening and does not set it back to the
- // initial value. With setting a explicit backgroundColor we can prevent that change.
- self.backgroundColor = [UIColor clearColor];
- _cropEnabled = YES;
- _forceUpscaling = NO;
- _cropRect = CGRectMake(0.5, 0.5, 0, 0);
- _cropDisplayBounds = CGRectNull;
- _placeholderColor = ASDisplayNodeDefaultPlaceholderColor();
- _animatedImageRunLoopMode = ASAnimatedImageDefaultRunLoopMode;
-
- return self;
- }
- - (void)dealloc
- {
- // Invalidate all components around animated images
- [self invalidateAnimatedImage];
- }
- - (UIImage *)placeholderImage
- {
- // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set.
- // This would completely eliminate the memory and performance cost of the backing store.
- CGSize size = self.calculatedSize;
- if ((size.width * size.height) < CGFLOAT_EPSILON) {
- return nil;
- }
-
- ASDN::MutexLocker l(__instanceLock__);
-
- UIGraphicsBeginImageContext(size);
- [self.placeholderColor setFill];
- UIRectFill(CGRectMake(0, 0, size.width, size.height));
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
-
- return image;
- }
- #pragma mark - Layout and Sizing
- - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
- {
- ASDN::MutexLocker l(__instanceLock__);
- if (_image == nil) {
- return [super calculateSizeThatFits:constrainedSize];
- }
- return _image.size;
- }
- #pragma mark - Setter / Getter
- - (void)setImage:(UIImage *)image
- {
- ASDN::MutexLocker l(__instanceLock__);
- if (!ASObjectIsEqual(_image, image)) {
- _image = image;
-
- [self setNeedsLayout];
- if (image) {
- [self setNeedsDisplay];
-
- if ([ASImageNode shouldShowImageScalingOverlay] && _debugLabelNode == nil) {
- ASPerformBlockOnMainThread(^{
- _debugLabelNode = [[ASTextNode alloc] init];
- _debugLabelNode.layerBacked = YES;
- [self addSubnode:_debugLabelNode];
- });
- }
- } else {
- self.contents = nil;
- }
- }
- }
- - (UIImage *)image
- {
- ASDN::MutexLocker l(__instanceLock__);
- return _image;
- }
- - (void)setPlaceholderColor:(UIColor *)placeholderColor
- {
- _placeholderColor = placeholderColor;
- // prevent placeholders if we don't have a color
- self.placeholderEnabled = placeholderColor != nil;
- }
- #pragma mark - Drawing
- - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
- {
- ASDN::MutexLocker l(__instanceLock__);
-
- _drawParameter = {
- .bounds = self.bounds,
- .opaque = self.opaque,
- .contentsScale = self.contentsScaleForDisplay,
- .backgroundColor = self.backgroundColor,
- .contentMode = self.contentMode,
- .cropEnabled = _cropEnabled,
- .forceUpscaling = _forceUpscaling,
- .forcedSize = _forcedSize,
- .cropRect = _cropRect,
- .cropDisplayBounds = _cropDisplayBounds,
- .imageModificationBlock = _imageModificationBlock
- };
-
- return nil;
- }
- - (NSDictionary *)debugLabelAttributes
- {
- return @{
- NSFontAttributeName: [UIFont systemFontOfSize:15.0],
- NSForegroundColorAttributeName: [UIColor redColor]
- };
- }
- - (UIImage *)displayWithParameters:(id<NSObject> *)parameter isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
- {
- UIImage *image = self.image;
- if (image == nil) {
- return nil;
- }
-
- __instanceLock__.lock();
- ASImageNodeDrawParameters drawParameter = _drawParameter;
- __instanceLock__.unlock();
-
- CGRect drawParameterBounds = drawParameter.bounds;
- BOOL forceUpscaling = drawParameter.forceUpscaling;
- CGSize forcedSize = drawParameter.forcedSize;
- BOOL cropEnabled = drawParameter.cropEnabled;
- BOOL isOpaque = drawParameter.opaque;
- UIColor *backgroundColor = drawParameter.backgroundColor;
- UIViewContentMode contentMode = drawParameter.contentMode;
- CGFloat contentsScale = drawParameter.contentsScale;
- CGRect cropDisplayBounds = drawParameter.cropDisplayBounds;
- CGRect cropRect = drawParameter.cropRect;
- asimagenode_modification_block_t imageModificationBlock = drawParameter.imageModificationBlock;
-
- BOOL hasValidCropBounds = cropEnabled && !CGRectIsEmpty(cropDisplayBounds);
- CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : drawParameterBounds);
-
- ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentWithRenderingContext;
- ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentWithRenderingContext;
-
- 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 (imageModificationBlock != NULL) {
- image = imageModificationBlock(image);
- }
- return image;
- }
-
- CGSize imageSize = image.size;
- CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
- CGSize boundsSizeInPixels = CGSizeMake(std::floor(bounds.size.width * contentsScale), std::floor(bounds.size.height * contentsScale));
-
- if (_debugLabelNode) {
- CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height);
- if (pixelCountRatio != 1.0) {
- NSString *scaleString = [NSString stringWithFormat:@"%.2fx", pixelCountRatio];
- _debugLabelNode.attributedText = [[NSAttributedString alloc] initWithString:scaleString attributes:[self debugLabelAttributes]];
- _debugLabelNode.hidden = NO;
- [self setNeedsLayout];
- } else {
- _debugLabelNode.hidden = YES;
- _debugLabelNode.attributedText = nil;
- }
- }
-
- BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill ||
- contentMode == UIViewContentModeScaleAspectFit ||
- contentMode == UIViewContentModeCenter;
-
- CGSize backingSize = CGSizeZero;
- CGRect imageDrawRect = CGRectZero;
-
- 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 (!cropEnabled || !contentModeSupported || stretchable) {
- backingSize = imageSizeInPixels;
- imageDrawRect = (CGRect){.size = backingSize};
- } else {
- if (CGSizeEqualToSize(CGSizeZero, forcedSize) == NO) {
- //scale forced size
- forcedSize.width *= contentsScale;
- forcedSize.height *= contentsScale;
- }
- ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels,
- boundsSizeInPixels,
- contentMode,
- cropRect,
- forceUpscaling,
- forcedSize,
- &backingSize,
- &imageDrawRect);
- }
-
- if (backingSize.width <= 0.0f || backingSize.height <= 0.0f ||
- imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) {
- return nil;
- }
- ASImageNodeContentsKey *contentsKey = [[ASImageNodeContentsKey alloc] init];
- contentsKey.image = image;
- contentsKey.backingSize = backingSize;
- contentsKey.imageDrawRect = imageDrawRect;
- contentsKey.isOpaque = isOpaque;
- contentsKey.backgroundColor = backgroundColor;
- contentsKey.preContextBlock = preContextBlock;
- contentsKey.postContextBlock = postContextBlock;
- contentsKey.imageModificationBlock = imageModificationBlock;
- if (isCancelled()) {
- return nil;
- }
- ASWeakMapEntry<UIImage *> *entry = [self.class contentsForkey:contentsKey isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled];
- if (entry == nil) { // If nil, we were cancelled.
- return nil;
- }
-
- __instanceLock__.lock();
- _weakCacheEntry = entry; // Retain so that the entry remains in the weak cache
- __instanceLock__.unlock();
- return entry.value;
- }
- static ASWeakMap<ASImageNodeContentsKey *, UIImage *> *cache = nil;
- static ASDN::Mutex cacheLock;
- + (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
- {
- {
- ASDN::MutexLocker l(cacheLock);
- if (!cache) {
- cache = [[ASWeakMap alloc] init];
- }
- ASWeakMapEntry *entry = [cache entryForKey:key];
- if (entry != nil) {
- // cache hit
- return entry;
- }
- }
- // cache miss
- UIImage *contents = [self createContentsForkey:key isCancelled:isCancelled];
- if (contents == nil) { // If nil, we were cancelled
- return nil;
- }
- {
- ASDN::MutexLocker l(cacheLock);
- return [cache setObject:contents forKey:key];
- }
- }
- + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
- {
- // The following `UIGraphicsBeginImageContextWithOptions` 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()) {
- 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(key.backingSize, key.isOpaque, 1.0);
-
- BOOL contextIsClean = YES;
-
- CGContextRef context = UIGraphicsGetCurrentContext();
- if (context && key.preContextBlock) {
- key.preContextBlock(context);
- contextIsClean = NO;
- }
-
- // if view is opaque, fill the context with background color
- if (key.isOpaque && key.backgroundColor) {
- [key.backgroundColor setFill];
- UIRectFill({ .size = key.backingSize });
- contextIsClean = NO;
- }
-
- // 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 Premiere,
- // as well as iOS games, and a small number of ASDK apps that provide the same image reference
- // to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes
- // that may get the same pointer for a given UI asset image, etc.
- // FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and
- // only if the object already exists in the set we should create a semaphore to signal waiting threads
- // upon removal of the object from the set when the operation completes.
- // Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer.
- // Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068
-
- UIImage *image = key.image;
- BOOL canUseCopy = (contextIsClean || ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)));
- CGBlendMode blendMode = canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal;
-
- @synchronized(image) {
- [image drawInRect:key.imageDrawRect blendMode:blendMode alpha:1];
- }
-
- if (context && key.postContextBlock) {
- key.postContextBlock(context);
- }
- // The following `UIGraphicsGetImageFromCurrentImageContext` call will commonly take more than 20ms on an
- // A5 processor. Check for cancellation before we call it.
- if (isCancelled()) {
- UIGraphicsEndImageContext();
- return nil;
- }
- UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
-
- UIGraphicsEndImageContext();
-
- if (key.imageModificationBlock != NULL) {
- result = key.imageModificationBlock(result);
- }
-
- return result;
- }
- - (void)displayDidFinish
- {
- [super displayDidFinish];
- __instanceLock__.lock();
- void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock;
- UIImage *image = _image;
- __instanceLock__.unlock();
-
- // If we've got a block to perform after displaying, do it.
- if (image && displayCompletionBlock) {
- displayCompletionBlock(NO);
- __instanceLock__.lock();
- _displayCompletionBlock = nil;
- __instanceLock__.unlock();
- }
- }
- - (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock
- {
- if (self.displaySuspended) {
- if (displayCompletionBlock)
- displayCompletionBlock(YES);
- return;
- }
- // Stash the block and call-site queue. We'll invoke it in -displayDidFinish.
- ASDN::MutexLocker l(__instanceLock__);
- if (_displayCompletionBlock != displayCompletionBlock) {
- _displayCompletionBlock = displayCompletionBlock;
- }
- [self setNeedsDisplay];
- }
- #pragma mark Interface State
- - (void)clearContents
- {
- [super clearContents];
-
- __instanceLock__.lock();
- _weakCacheEntry = nil; // release contents from the cache.
- __instanceLock__.unlock();
- }
- #pragma mark - Cropping
- - (BOOL)isCropEnabled
- {
- ASDN::MutexLocker l(__instanceLock__);
- return _cropEnabled;
- }
- - (void)setCropEnabled:(BOOL)cropEnabled
- {
- [self setCropEnabled:cropEnabled recropImmediately:NO inBounds:self.bounds];
- }
- - (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds
- {
- ASDN::MutexLocker l(__instanceLock__);
- if (_cropEnabled == cropEnabled)
- return;
- _cropEnabled = cropEnabled;
- _cropDisplayBounds = cropBounds;
- // If we have an image to display, display it, respecting our recrop flag.
- if (self.image)
- {
- ASPerformBlockOnMainThread(^{
- if (recropImmediately)
- [self displayImmediately];
- else
- [self setNeedsDisplay];
- });
- }
- }
- - (CGRect)cropRect
- {
- ASDN::MutexLocker l(__instanceLock__);
- return _cropRect;
- }
- - (void)setCropRect:(CGRect)cropRect
- {
- ASDN::MutexLocker l(__instanceLock__);
- if (CGRectEqualToRect(_cropRect, cropRect))
- return;
- _cropRect = cropRect;
- // TODO: this logic needs to be updated to respect cropRect.
- CGSize boundsSize = self.bounds.size;
- CGSize imageSize = self.image.size;
- BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height));
- // Re-display if we need to.
- ASPerformBlockOnMainThread(^{
- if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage)
- [self setNeedsDisplay];
- });
- }
- - (BOOL)forceUpscaling
- {
- ASDN::MutexLocker l(__instanceLock__);
- return _forceUpscaling;
- }
- - (void)setForceUpscaling:(BOOL)forceUpscaling
- {
- ASDN::MutexLocker l(__instanceLock__);
- _forceUpscaling = forceUpscaling;
- }
- - (CGSize)forcedSize
- {
- ASDN::MutexLocker l(__instanceLock__);
- return _forcedSize;
- }
- - (void)setForcedSize:(CGSize)forcedSize
- {
- ASDN::MutexLocker l(__instanceLock__);
- _forcedSize = forcedSize;
- }
- - (asimagenode_modification_block_t)imageModificationBlock
- {
- ASDN::MutexLocker l(__instanceLock__);
- return _imageModificationBlock;
- }
- - (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock
- {
- ASDN::MutexLocker l(__instanceLock__);
- _imageModificationBlock = imageModificationBlock;
- }
- #pragma mark - Debug
- - (void)layout
- {
- [super layout];
-
- if (_debugLabelNode) {
- CGSize boundsSize = self.bounds.size;
- CGSize debugLabelSize = [_debugLabelNode layoutThatFits:ASSizeRangeMake(CGSizeZero, boundsSize)].size;
- CGPoint debugLabelOrigin = CGPointMake(boundsSize.width - debugLabelSize.width,
- boundsSize.height - debugLabelSize.height);
- _debugLabelNode.frame = (CGRect) {debugLabelOrigin, debugLabelSize};
- }
- }
- @end
- #pragma mark - Extras
- extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor)
- {
- return ^(UIImage *originalImage) {
- UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
- UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}];
- // Make the image round
- [roundOutline addClip];
- // Draw the original image
- [originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];
- // Draw a border on top.
- if (borderWidth > 0.0) {
- [borderColor setStroke];
- [roundOutline setLineWidth:borderWidth];
- [roundOutline stroke];
- }
- UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return modifiedImage;
- };
- }
- extern asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color)
- {
- return ^(UIImage *originalImage) {
- UIGraphicsBeginImageContextWithOptions(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();
- // if the original image was stretchy, keep it stretchy
- if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) {
- modifiedImage = [modifiedImage resizableImageWithCapInsets:originalImage.capInsets resizingMode:originalImage.resizingMode];
- }
- return modifiedImage;
- };
- }
|