UIImage+ImageEffects.m 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. // Created by Tencent on 2023/06/09.
  2. // Copyright © 2023 Tencent. All rights reserved.
  3. #import "UIImage+ImageEffects.h"
  4. @import Accelerate;
  5. #import <float.h>
  6. @implementation UIImage (ImageEffects)
  7. - (UIImage *)applyLightEffect {
  8. UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3];
  9. return [self applyBlurWithRadius:10 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
  10. }
  11. - (UIImage *)applyExtraLightEffect {
  12. UIColor *tintColor = [UIColor colorWithWhite:0.97 alpha:0.82];
  13. return [self applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
  14. }
  15. - (UIImage *)applyDarkEffect {
  16. UIColor *tintColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4];
  17. return [self applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
  18. }
  19. - (UIImage *)applyTintEffectWithColor:(UIColor *)tintColor {
  20. const CGFloat effectColorAlpha = 0.6;
  21. UIColor *effectColor = tintColor;
  22. int componentCount = CGColorGetNumberOfComponents(tintColor.CGColor);
  23. if (componentCount == 2) {
  24. CGFloat b;
  25. if ([tintColor getWhite:&b alpha:NULL]) {
  26. effectColor = [UIColor colorWithWhite:b alpha:effectColorAlpha];
  27. }
  28. } else {
  29. CGFloat r, g, b;
  30. if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
  31. effectColor = [UIColor colorWithRed:r green:g blue:b alpha:effectColorAlpha];
  32. }
  33. }
  34. return [self applyBlurWithRadius:10 tintColor:effectColor saturationDeltaFactor:-1.0 maskImage:nil];
  35. }
  36. - (UIImage *)applyBlurWithRadius:(CGFloat)blurRadius
  37. tintColor:(UIColor *)tintColor
  38. saturationDeltaFactor:(CGFloat)saturationDeltaFactor
  39. maskImage:(UIImage *)maskImage {
  40. // Check pre-conditions.
  41. if (self.size.width < 1 || self.size.height < 1) {
  42. NSLog(@"*** error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
  43. return nil;
  44. }
  45. if (!self.CGImage) {
  46. NSLog(@"*** error: image must be backed by a CGImage: %@", self);
  47. return nil;
  48. }
  49. if (maskImage && !maskImage.CGImage) {
  50. NSLog(@"*** error: maskImage must be backed by a CGImage: %@", maskImage);
  51. return nil;
  52. }
  53. CGRect imageRect = {CGPointZero, self.size};
  54. UIImage *effectImage = self;
  55. BOOL hasBlur = blurRadius > __FLT_EPSILON__;
  56. BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__;
  57. if (hasBlur || hasSaturationChange) {
  58. UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
  59. CGContextRef effectInContext = UIGraphicsGetCurrentContext();
  60. CGContextScaleCTM(effectInContext, 1.0, -1.0);
  61. CGContextTranslateCTM(effectInContext, 0, -self.size.height);
  62. CGContextDrawImage(effectInContext, imageRect, self.CGImage);
  63. vImage_Buffer effectInBuffer;
  64. effectInBuffer.data = CGBitmapContextGetData(effectInContext);
  65. effectInBuffer.width = CGBitmapContextGetWidth(effectInContext);
  66. effectInBuffer.height = CGBitmapContextGetHeight(effectInContext);
  67. effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext);
  68. UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
  69. CGContextRef effectOutContext = UIGraphicsGetCurrentContext();
  70. vImage_Buffer effectOutBuffer;
  71. effectOutBuffer.data = CGBitmapContextGetData(effectOutContext);
  72. effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext);
  73. effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext);
  74. effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext);
  75. if (hasBlur) {
  76. // A description of how to compute the box kernel width from the Gaussian
  77. // radius (aka standard deviation) appears in the SVG spec:
  78. // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
  79. //
  80. // For larger values of 's' (s >= 2.0), an approximation can be used: Three
  81. // successive box-blurs build a piece-wise quadratic convolution kernel, which
  82. // approximates the Gaussian kernel to within roughly 3%.
  83. //
  84. // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
  85. //
  86. // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
  87. //
  88. CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale];
  89. NSUInteger radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5);
  90. if (radius % 2 != 1) {
  91. radius += 1; // force radius to be odd so that the three box-blur methodology works.
  92. }
  93. vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
  94. vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
  95. vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
  96. }
  97. BOOL effectImageBuffersAreSwapped = NO;
  98. if (hasSaturationChange) {
  99. CGFloat s = saturationDeltaFactor;
  100. CGFloat floatingPointSaturationMatrix[] = {
  101. 0.0722 + 0.9278 * s,
  102. 0.0722 - 0.0722 * s,
  103. 0.0722 - 0.0722 * s,
  104. 0,
  105. 0.7152 - 0.7152 * s,
  106. 0.7152 + 0.2848 * s,
  107. 0.7152 - 0.7152 * s,
  108. 0,
  109. 0.2126 - 0.2126 * s,
  110. 0.2126 - 0.2126 * s,
  111. 0.2126 + 0.7873 * s,
  112. 0,
  113. 0,
  114. 0,
  115. 0,
  116. 1,
  117. };
  118. const int32_t divisor = 256;
  119. NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix) / sizeof(floatingPointSaturationMatrix[0]);
  120. int16_t saturationMatrix[matrixSize];
  121. for (NSUInteger i = 0; i < matrixSize; ++i) {
  122. saturationMatrix[i] = (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor);
  123. }
  124. if (hasBlur) {
  125. vImageMatrixMultiply_ARGB8888(&effectOutBuffer, &effectInBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags);
  126. effectImageBuffersAreSwapped = YES;
  127. } else {
  128. vImageMatrixMultiply_ARGB8888(&effectInBuffer, &effectOutBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags);
  129. }
  130. }
  131. if (!effectImageBuffersAreSwapped) effectImage = UIGraphicsGetImageFromCurrentImageContext();
  132. UIGraphicsEndImageContext();
  133. if (effectImageBuffersAreSwapped) effectImage = UIGraphicsGetImageFromCurrentImageContext();
  134. UIGraphicsEndImageContext();
  135. }
  136. // Set up output context.
  137. UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
  138. CGContextRef outputContext = UIGraphicsGetCurrentContext();
  139. CGContextScaleCTM(outputContext, 1.0, -1.0);
  140. CGContextTranslateCTM(outputContext, 0, -self.size.height);
  141. // Draw base image.
  142. CGContextDrawImage(outputContext, imageRect, self.CGImage);
  143. // Draw effect image.
  144. if (hasBlur) {
  145. CGContextSaveGState(outputContext);
  146. if (maskImage) {
  147. CGContextClipToMask(outputContext, imageRect, maskImage.CGImage);
  148. }
  149. CGContextDrawImage(outputContext, imageRect, effectImage.CGImage);
  150. CGContextRestoreGState(outputContext);
  151. }
  152. // Add in color tint.
  153. if (tintColor) {
  154. CGContextSaveGState(outputContext);
  155. CGContextSetFillColorWithColor(outputContext, tintColor.CGColor);
  156. CGContextFillRect(outputContext, imageRect);
  157. CGContextRestoreGState(outputContext);
  158. }
  159. // Output image is ready.
  160. UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
  161. UIGraphicsEndImageContext();
  162. return outputImage;
  163. }
  164. @end
  165. @implementation UIImage (SnapshotImage)
  166. + (UIImage *)snapshotImageWithView:(UIView *)view {
  167. // currentView The current view creates a bitmap-based graphics context and specifies the size of
  168. UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, [UIScreen mainScreen].scale);
  169. // renderInContext Renders the receiver and its subscopes to the specified context
  170. [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
  171. // Returns an image based on the current graphics context
  172. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  173. // generated image
  174. UIGraphicsEndImageContext();
  175. return image;
  176. }
  177. @end