UIImage+ASConvenience.m 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. //
  2. // UIImage+ASConvenience.m
  3. // AsyncDisplayKit
  4. //
  5. // Created by Hannah Troisi on 6/24/16.
  6. //
  7. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  8. // This source code is licensed under the BSD-style license found in the
  9. // LICENSE file in the root directory of this source tree. An additional grant
  10. // of patent rights can be found in the PATENTS file in the same directory.
  11. //
  12. #import "UIImage+ASConvenience.h"
  13. #import "ASInternalHelpers.h"
  14. #import "ASAssert.h"
  15. @implementation UIImage (ASDKAdditions)
  16. + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius
  17. cornerColor:(UIColor *)cornerColor
  18. fillColor:(UIColor *)fillColor
  19. {
  20. return [self as_resizableRoundedImageWithCornerRadius:cornerRadius
  21. cornerColor:cornerColor
  22. fillColor:fillColor
  23. borderColor:nil
  24. borderWidth:1.0
  25. roundedCorners:UIRectCornerAllCorners
  26. scale:0.0];
  27. }
  28. + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius
  29. cornerColor:(UIColor *)cornerColor
  30. fillColor:(UIColor *)fillColor
  31. borderColor:(UIColor *)borderColor
  32. borderWidth:(CGFloat)borderWidth
  33. {
  34. return [self as_resizableRoundedImageWithCornerRadius:cornerRadius
  35. cornerColor:cornerColor
  36. fillColor:fillColor
  37. borderColor:borderColor
  38. borderWidth:borderWidth
  39. roundedCorners:UIRectCornerAllCorners
  40. scale:0.0];
  41. }
  42. + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius
  43. cornerColor:(UIColor *)cornerColor
  44. fillColor:(UIColor *)fillColor
  45. borderColor:(UIColor *)borderColor
  46. borderWidth:(CGFloat)borderWidth
  47. roundedCorners:(UIRectCorner)roundedCorners
  48. scale:(CGFloat)scale
  49. {
  50. static NSCache *__pathCache = nil;
  51. static dispatch_once_t onceToken;
  52. dispatch_once(&onceToken, ^{
  53. __pathCache = [[NSCache alloc] init];
  54. // UIBezierPath objects are fairly small and these are equally sized. 20 should be plenty for many different parameters.
  55. __pathCache.countLimit = 20;
  56. });
  57. // Treat clear background color as no background color
  58. if ([cornerColor isEqual:[UIColor clearColor]]) {
  59. cornerColor = nil;
  60. }
  61. CGFloat dimension = (cornerRadius * 2) + 1;
  62. CGRect bounds = CGRectMake(0, 0, dimension, dimension);
  63. typedef struct {
  64. UIRectCorner corners;
  65. CGFloat radius;
  66. } PathKey;
  67. PathKey key = { roundedCorners, cornerRadius };
  68. NSValue *pathKeyObject = [[NSValue alloc] initWithBytes:&key objCType:@encode(PathKey)];
  69. CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius);
  70. UIBezierPath *path = [__pathCache objectForKey:pathKeyObject];
  71. if (path == nil) {
  72. path = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:roundedCorners cornerRadii:cornerRadii];
  73. [__pathCache setObject:path forKey:pathKeyObject];
  74. }
  75. // We should probably check if the background color has any alpha component but that
  76. // might be expensive due to needing to check mulitple color spaces.
  77. UIGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale);
  78. BOOL contextIsClean = YES;
  79. if (cornerColor) {
  80. contextIsClean = NO;
  81. [cornerColor setFill];
  82. // Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overrides directly.
  83. UIRectFillUsingBlendMode(bounds, kCGBlendModeCopy);
  84. }
  85. BOOL canUseCopy = contextIsClean || (CGColorGetAlpha(fillColor.CGColor) == 1);
  86. [fillColor setFill];
  87. [path fillWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1];
  88. if (borderColor) {
  89. [borderColor setStroke];
  90. // Inset border fully inside filled path (not halfway on each side of path)
  91. CGRect strokeRect = CGRectInset(bounds, borderWidth / 2.0, borderWidth / 2.0);
  92. // It is rarer to have a stroke path, and our cache key only handles rounded rects for the exact-stretchable
  93. // size calculated by cornerRadius, so we won't bother caching this path. Profiling validates this decision.
  94. UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect
  95. byRoundingCorners:roundedCorners
  96. cornerRadii:cornerRadii];
  97. [strokePath setLineWidth:borderWidth];
  98. BOOL canUseCopy = (CGColorGetAlpha(borderColor.CGColor) == 1);
  99. [strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1];
  100. }
  101. UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
  102. UIGraphicsEndImageContext();
  103. UIEdgeInsets capInsets = UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius);
  104. result = [result resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch];
  105. return result;
  106. }
  107. #pragma mark - as_imageNamed
  108. UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollection)
  109. {
  110. static NSCache *imageCache = nil;
  111. static dispatch_once_t onceToken;
  112. dispatch_once(&onceToken, ^{
  113. // Because NSCache responds to memory warnings, we do not need an explicit limit.
  114. // all of these objects contain compressed image data and are relatively small
  115. // compared to the backing stores of text and image views.
  116. imageCache = [[NSCache alloc] init];
  117. });
  118. UIImage *image = nil;
  119. if ([imageName length] > 0) {
  120. NSString *imageKey = imageName;
  121. if (traitCollection) {
  122. char imageKeyBuffer[256];
  123. snprintf(imageKeyBuffer, sizeof(imageKeyBuffer), "%s|%ld|%ld", imageName.UTF8String, (long)traitCollection.horizontalSizeClass, (long)traitCollection.verticalSizeClass);
  124. imageKey = [NSString stringWithUTF8String:imageKeyBuffer];
  125. }
  126. image = [imageCache objectForKey:imageKey];
  127. if (!image) {
  128. image = [UIImage imageNamed:imageName inBundle:nil compatibleWithTraitCollection:traitCollection];
  129. if (image) {
  130. [imageCache setObject:image forKey:imageKey];
  131. }
  132. }
  133. }
  134. return image;
  135. }
  136. + (UIImage *)as_imageNamed:(NSString *)imageName
  137. {
  138. return cachedImageNamed(imageName, nil);
  139. }
  140. + (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(UITraitCollection *)traitCollection
  141. {
  142. return cachedImageNamed(imageName, traitCollection);
  143. }
  144. @end