GIDSignInButton.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "GoogleSignIn/Sources/GIDSignInButton_Private.h"
  15. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
  16. #import "GoogleSignIn/Sources/GIDScopes.h"
  17. #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h"
  18. #import "GoogleSignIn/Sources/GIDSignInStrings.h"
  19. #import "GoogleSignIn/Sources/GIDSignIn_Private.h"
  20. #import "GoogleSignIn/Sources/NSBundle+GID3PAdditions.h"
  21. NS_ASSUME_NONNULL_BEGIN
  22. #pragma mark - Constants
  23. // Standard accessibility identifier.
  24. static NSString *const kAccessibilityIdentifier = @"GIDSignInButton";
  25. // The name of the font for button text.
  26. static NSString *const kFontNameRobotoBold = @"Roboto-Bold";
  27. // Button text font size.
  28. static const CGFloat kFontSize = 14;
  29. #pragma mark - Icon Constants
  30. // The name of the image for the Google "G"
  31. static NSString *const kGoogleImageName = @"google";
  32. // Keys used for NSCoding.
  33. static NSString *const kStyleKey = @"style";
  34. static NSString *const kColorSchemeKey = @"color_scheme";
  35. static NSString *const kButtonState = @"state";
  36. #pragma mark - Sizing Constants
  37. // The corner radius of the button
  38. static const int kCornerRadius = 2;
  39. // The standard height of the sign in button.
  40. static const int kButtonHeight = 48;
  41. // The width of the icon part of the button in points.
  42. static const int kIconWidth = 40;
  43. // Left and right text padding.
  44. static const int kTextPadding = 14;
  45. // The icon (UIImage)'s frame.
  46. static const CGRect kIconFrame = { {9, 10}, {29, 30} };
  47. #pragma mark - Appearance Constants
  48. static const CGFloat kBorderWidth = 4;
  49. static const CGFloat kHaloShadowAlpha = 12.0 / 100.0;
  50. static const CGFloat kHaloShadowBlur = 2;
  51. static const CGFloat kDropShadowAlpha = 24.0 / 100.0;
  52. static const CGFloat kDropShadowBlur = 2;
  53. static const CGFloat kDropShadowYOffset = 2;
  54. static const CGFloat kDisabledIconAlpha = 40.0 / 100.0;
  55. #pragma mark - Misc. Constants
  56. // The query parameter name to log the appearance of the button.
  57. static NSString *const kLoggingParameter = @"gpbtn";
  58. #pragma mark - Colors
  59. // All colors in hex RGBA format (0xRRGGBBAA)
  60. static const NSUInteger kColorGoogleBlue = 0x4285f4ff;
  61. static const NSUInteger kColorGoogleDarkBlue = 0x3367d6ff;
  62. static const NSUInteger kColorWhite = 0xffffffff;
  63. static const NSUInteger kColorLightestGrey = 0x00000014;
  64. static const NSUInteger kColorLightGrey = 0xeeeeeeff;
  65. static const NSUInteger kColorDisabledGrey = 0x00000066;
  66. static const NSUInteger kColorDarkestGrey = 0x00000089;
  67. static NSUInteger kColors[12] = {
  68. // |Background|, |Foreground|,
  69. kColorGoogleBlue, kColorWhite, // Dark Google Normal
  70. kColorLightestGrey, kColorDisabledGrey, // Dark Google Disabled
  71. kColorGoogleDarkBlue, kColorWhite, // Dark Google Pressed
  72. kColorWhite, kColorDarkestGrey, // Light Google Normal
  73. kColorLightestGrey, kColorDisabledGrey, // Light Google Disabled
  74. kColorLightGrey, kColorDarkestGrey, // Light Google Pressed
  75. };
  76. // The state of the button:
  77. typedef NS_ENUM(NSUInteger, GIDSignInButtonState) {
  78. kGIDSignInButtonStateNormal = 0,
  79. kGIDSignInButtonStateDisabled = 1,
  80. kGIDSignInButtonStatePressed = 2,
  81. };
  82. static NSUInteger const kNumGIDSignInButtonStates = 3;
  83. // Used to lookup specific colors from the kColors table:
  84. typedef NS_ENUM(NSUInteger, GIDSignInButtonStyleColor) {
  85. kGIDSignInButtonStyleColorBackground = 0,
  86. kGIDSignInButtonStyleColorForeground = 1,
  87. };
  88. static NSUInteger const kNumGIDSignInButtonStyleColors = 2;
  89. // This method just pulls the correct value out of the kColors table and returns it as a UIColor.
  90. static UIColor *colorForStyleState(GIDSignInButtonColorScheme style,
  91. GIDSignInButtonState state,
  92. GIDSignInButtonStyleColor color) {
  93. NSUInteger stateWidth = kNumGIDSignInButtonStyleColors;
  94. NSUInteger styleWidth = kNumGIDSignInButtonStates * stateWidth;
  95. NSUInteger index = (style * styleWidth) + (state * stateWidth) + color;
  96. NSUInteger colorValue = kColors[index];
  97. return [UIColor colorWithRed:(CGFloat)(((colorValue & 0xff000000) >> 24) / 255.0f) \
  98. green:(CGFloat)(((colorValue & 0x00ff0000) >> 16) / 255.0f) \
  99. blue:(CGFloat)(((colorValue & 0x0000ff00) >> 8) / 255.0f) \
  100. alpha:(CGFloat)(((colorValue & 0x000000ff) >> 0) / 255.0f)];
  101. }
  102. #pragma mark - UIImage Category Forward Declaration
  103. @interface UIImage (GIDAdditions_Private)
  104. - (UIImage *)gid_imageWithBlendMode:(CGBlendMode)blendMode color:(UIColor *)color;
  105. @end
  106. #pragma mark - GIDSignInButton Private Properties
  107. @interface GIDSignInButton ()
  108. // The state (normal, pressed, disabled) of the button.
  109. @property(nonatomic, assign) GIDSignInButtonState buttonState;
  110. @end
  111. #pragma mark -
  112. @implementation GIDSignInButton {
  113. UIImageView *_icon;
  114. }
  115. #pragma mark - Object lifecycle
  116. - (id)initWithFrame:(CGRect)frame {
  117. self = [super initWithFrame:frame];
  118. if (self) {
  119. [self sharedInit];
  120. }
  121. return self;
  122. }
  123. - (void)sharedInit {
  124. self.clipsToBounds = YES;
  125. self.backgroundColor = [UIColor clearColor];
  126. // Accessibility settings:
  127. self.isAccessibilityElement = YES;
  128. self.accessibilityTraits = UIAccessibilityTraitButton;
  129. self.accessibilityIdentifier = kAccessibilityIdentifier;
  130. // Default style settings.
  131. _style = kGIDSignInButtonStyleStandard;
  132. _colorScheme = kGIDSignInButtonColorSchemeLight;
  133. _buttonState = kGIDSignInButtonStateNormal;
  134. // Icon for branding image:
  135. _icon = [[UIImageView alloc] initWithFrame:kIconFrame];
  136. _icon.contentMode = UIViewContentModeCenter;
  137. _icon.userInteractionEnabled = NO;
  138. [self addSubview:_icon];
  139. // Load font for "Sign in with Google" text
  140. [NSBundle gid_registerFonts];
  141. // Setup normal/highlighted state transitions:
  142. [self addTarget:self
  143. action:@selector(setNeedsDisplay)
  144. forControlEvents:UIControlEventAllTouchEvents];
  145. [self addTarget:self
  146. action:@selector(switchToPressed)
  147. forControlEvents:UIControlEventTouchDown |
  148. UIControlEventTouchDragInside |
  149. UIControlEventTouchDragEnter];
  150. [self addTarget:self
  151. action:@selector(switchToNormal)
  152. forControlEvents:UIControlEventTouchDragExit |
  153. UIControlEventTouchDragOutside |
  154. UIControlEventTouchCancel];
  155. // Setup convenience touch-up-inside handler:
  156. [self addTarget:self action:@selector(pressed) forControlEvents:UIControlEventTouchUpInside];
  157. // Update the icon, etc.
  158. [self updateUI];
  159. }
  160. #pragma mark - NSCoding
  161. - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
  162. self = [super initWithCoder:aDecoder];
  163. if (self) {
  164. [self sharedInit];
  165. if ([aDecoder containsValueForKey:kStyleKey]) {
  166. _style = [aDecoder decodeIntegerForKey:kStyleKey];
  167. }
  168. if ([aDecoder containsValueForKey:kColorSchemeKey]) {
  169. _colorScheme = [aDecoder decodeIntegerForKey:kColorSchemeKey];
  170. }
  171. if ([aDecoder containsValueForKey:kButtonState]) {
  172. _buttonState = [aDecoder decodeIntegerForKey:kButtonState];
  173. }
  174. }
  175. return self;
  176. }
  177. - (void)encodeWithCoder:(NSCoder *)aCoder {
  178. [super encodeWithCoder:aCoder];
  179. [aCoder encodeInteger:_style forKey:kStyleKey];
  180. [aCoder encodeInteger:_colorScheme forKey:kColorSchemeKey];
  181. [aCoder encodeInteger:_buttonState forKey:kButtonState];
  182. }
  183. #pragma mark - UI
  184. - (void)updateUI {
  185. // Reload the icon.
  186. [self loadIcon];
  187. // Set a useful accessibility label here even if we're not showing text.
  188. // Get localized button text from bundle.
  189. self.accessibilityLabel = [self buttonText];
  190. // Force constrain frame sizes:
  191. [self setFrame:self.frame];
  192. [self setNeedsUpdateConstraints];
  193. [self setNeedsDisplay];
  194. }
  195. - (void)loadIcon {
  196. NSString *resourceName = kGoogleImageName;
  197. NSBundle *gidBundle = [NSBundle gid_frameworkBundle];
  198. NSString *resourcePath = [gidBundle pathForResource:resourceName ofType:@"png"];
  199. UIImage *image = [UIImage imageWithContentsOfFile:resourcePath];
  200. if (_buttonState == kGIDSignInButtonStateDisabled) {
  201. _icon.image = [image gid_imageWithBlendMode:kCGBlendModeMultiply
  202. color:[UIColor colorWithWhite:0
  203. alpha:kDisabledIconAlpha]];
  204. } else {
  205. _icon.image = image;
  206. }
  207. }
  208. #pragma mark - State Transitions
  209. - (void)switchToPressed {
  210. [self setButtonState:kGIDSignInButtonStatePressed];
  211. }
  212. - (void)switchToNormal {
  213. [self setButtonState:kGIDSignInButtonStateNormal];
  214. }
  215. - (void)switchToDisabled {
  216. [self setButtonState:kGIDSignInButtonStateDisabled];
  217. }
  218. #pragma mark - Properties
  219. - (void)setStyle:(GIDSignInButtonStyle)style {
  220. if (style == _style) {
  221. return;
  222. }
  223. _style = style;
  224. [self updateUI];
  225. }
  226. - (void)setColorScheme:(GIDSignInButtonColorScheme)colorScheme {
  227. if (colorScheme == _colorScheme) {
  228. return;
  229. }
  230. _colorScheme = colorScheme;
  231. [self updateUI];
  232. }
  233. - (void)setEnabled:(BOOL)enabled {
  234. if (enabled == self.enabled) {
  235. return;
  236. }
  237. [super setEnabled:enabled];
  238. if (enabled) {
  239. [self switchToNormal];
  240. } else {
  241. [self switchToDisabled];
  242. }
  243. [self updateUI];
  244. }
  245. - (void)setButtonState:(GIDSignInButtonState)buttonState {
  246. if (buttonState == _buttonState) {
  247. return;
  248. }
  249. _buttonState = buttonState;
  250. [self setNeedsDisplay];
  251. }
  252. - (void)setFrame:(CGRect)frame {
  253. // Constrain the frame size to sizes we want.
  254. frame.size = [self sizeThatFits:frame.size];
  255. if (CGRectEqualToRect(frame, self.frame)) {
  256. return;
  257. }
  258. [super setFrame:frame];
  259. [self setNeedsUpdateConstraints];
  260. [self setNeedsDisplay];
  261. }
  262. #pragma mark - UI Actions
  263. - (void)pressed {
  264. [self switchToNormal];
  265. NSString *appearanceCode = [NSString stringWithFormat:@"%ld.%ld",
  266. (long)_style,
  267. (long)_colorScheme];
  268. NSDictionary *params = @{ kLoggingParameter : appearanceCode };
  269. [[GIDSignIn sharedInstance]
  270. signInWithOptions:[GIDSignInInternalOptions optionsWithExtraParams:params]];
  271. }
  272. #pragma mark - Helpers
  273. - (CGFloat)minWidth {
  274. if (_style == kGIDSignInButtonStyleIconOnly) {
  275. return kIconWidth + (kBorderWidth * 2);
  276. }
  277. NSString *text = [self buttonText];
  278. CGSize textSize = [[self class] textSize:text withFont:[[self class] buttonTextFont]];
  279. return ceil(kIconWidth + (kTextPadding * 2) + textSize.width + (kBorderWidth * 2));
  280. }
  281. - (BOOL)isConstraint:(NSLayoutConstraint *)constraintA
  282. equalToConstraint:(NSLayoutConstraint *)constraintB {
  283. return constraintA.priority == constraintB.priority &&
  284. constraintA.firstItem == constraintB.firstItem &&
  285. constraintA.firstAttribute == constraintB.firstAttribute &&
  286. constraintA.relation == constraintB.relation &&
  287. constraintA.secondItem == constraintB.secondItem &&
  288. constraintA.secondAttribute == constraintB.secondAttribute &&
  289. constraintA.multiplier == constraintB.multiplier &&
  290. constraintA.constant == constraintB.constant;
  291. }
  292. #pragma mark - Overrides
  293. - (CGSize)sizeThatFits:(CGSize)size {
  294. switch (_style) {
  295. case kGIDSignInButtonStyleIconOnly:
  296. return CGSizeMake([self minWidth], kButtonHeight);
  297. case kGIDSignInButtonStyleStandard:
  298. case kGIDSignInButtonStyleWide: {
  299. return CGSizeMake(MAX(size.width, [self minWidth]), kButtonHeight);
  300. }
  301. }
  302. }
  303. - (void)updateConstraints {
  304. NSLayoutRelation widthConstraintRelation;
  305. // For icon style, we want to ensure a fixed width
  306. if (_style == kGIDSignInButtonStyleIconOnly) {
  307. widthConstraintRelation = NSLayoutRelationEqual;
  308. } else {
  309. widthConstraintRelation = NSLayoutRelationGreaterThanOrEqual;
  310. }
  311. // Define a width constraint ensuring that we don't go below our minimum width
  312. NSLayoutConstraint *widthConstraint =
  313. [NSLayoutConstraint constraintWithItem:self
  314. attribute:NSLayoutAttributeWidth
  315. relatedBy:widthConstraintRelation
  316. toItem:nil
  317. attribute:NSLayoutAttributeNotAnAttribute
  318. multiplier:1.0
  319. constant:[self minWidth]];
  320. widthConstraint.identifier = @"buttonWidth - auto generated by GIDSignInButton";
  321. // Define a height constraint using our constant height
  322. NSLayoutConstraint *heightConstraint =
  323. [NSLayoutConstraint constraintWithItem:self
  324. attribute:NSLayoutAttributeHeight
  325. relatedBy:NSLayoutRelationEqual
  326. toItem:nil
  327. attribute:NSLayoutAttributeNotAnAttribute
  328. multiplier:1.0
  329. constant:kButtonHeight];
  330. heightConstraint.identifier = @"buttonHeight - auto generated by GIDSignInButton";
  331. // By default, add our width and height constraints
  332. BOOL addWidthConstraint = YES;
  333. BOOL addHeightConstraint = YES;
  334. for (NSLayoutConstraint *constraint in self.constraints) {
  335. // If it is equivalent to our width or height constraint, don't add ours later
  336. if ([self isConstraint:constraint equalToConstraint:widthConstraint]) {
  337. addWidthConstraint = NO;
  338. continue;
  339. }
  340. if ([self isConstraint:constraint equalToConstraint:heightConstraint]) {
  341. addHeightConstraint = NO;
  342. continue;
  343. }
  344. if (constraint.firstItem == self) {
  345. // If it is a height constraint of any relation, remove it
  346. if (constraint.firstAttribute == NSLayoutAttributeHeight) {
  347. [self removeConstraint:constraint];
  348. }
  349. // If it is a width constraint of any relation, remove it if it will conflict with ours
  350. if (constraint.firstAttribute == NSLayoutAttributeWidth &&
  351. (constraint.constant < [self minWidth] || _style == kGIDSignInButtonStyleIconOnly)) {
  352. [self removeConstraint:constraint];
  353. }
  354. }
  355. }
  356. if (addWidthConstraint) {
  357. [self addConstraint:widthConstraint];
  358. }
  359. if (addHeightConstraint) {
  360. [self addConstraint:heightConstraint];
  361. }
  362. [super updateConstraints];
  363. }
  364. #pragma mark - Rendering
  365. - (void)drawRect:(CGRect)rect {
  366. [super drawRect:rect];
  367. CGContextRef context = UIGraphicsGetCurrentContext();
  368. CGContextRetain(context);
  369. if (context == NULL) {
  370. return;
  371. }
  372. // Draw the button background
  373. [self drawButtonBackground:context];
  374. // Draw the text
  375. [self drawButtonText:context];
  376. CGContextRelease(context);
  377. context = NULL;
  378. }
  379. #pragma mark - Button Background Rendering
  380. - (void)drawButtonBackground:(CGContextRef)context {
  381. CGContextSaveGState(context);
  382. // Normalize the coordinate system of our graphics context
  383. // (0,0) -----> +x
  384. // |
  385. // |
  386. // \/ +y
  387. CGContextScaleCTM(context, 1, -1);
  388. CGContextTranslateCTM(context, 0, -self.bounds.size.height);
  389. // Get the colors for the current state and configuration
  390. UIColor *background = colorForStyleState(_colorScheme,
  391. _buttonState,
  392. kGIDSignInButtonStyleColorBackground);
  393. // Create rounded rectangle for button background/outline
  394. CGMutablePathRef path = CGPathCreateMutable();
  395. CGPathAddRoundedRect(path,
  396. NULL,
  397. CGRectInset(self.bounds, kBorderWidth, kBorderWidth),
  398. kCornerRadius,
  399. kCornerRadius);
  400. // Fill the background and apply halo shadow
  401. CGContextSaveGState(context);
  402. CGContextAddPath(context, path);
  403. CGContextSetFillColorWithColor(context, background.CGColor);
  404. // If we're not in the disabled state, we want a shadow
  405. if (_buttonState != kGIDSignInButtonStateDisabled) {
  406. // Draw halo shadow around button
  407. CGContextSetShadowWithColor(context,
  408. CGSizeMake(0, 0),
  409. kHaloShadowBlur,
  410. [UIColor colorWithWhite:0 alpha:kHaloShadowAlpha].CGColor);
  411. }
  412. CGContextFillPath(context);
  413. CGContextRestoreGState(context);
  414. if (_buttonState != kGIDSignInButtonStateDisabled) {
  415. // Fill the background again to apply drop shadow
  416. CGContextSaveGState(context);
  417. CGContextAddPath(context, path);
  418. CGContextSetFillColorWithColor(context, background.CGColor);
  419. CGContextSetShadowWithColor(context,
  420. CGSizeMake(0, kDropShadowYOffset),
  421. kDropShadowBlur,
  422. [UIColor colorWithWhite:0 alpha:kDropShadowAlpha].CGColor);
  423. CGContextFillPath(context);
  424. CGContextRestoreGState(context);
  425. }
  426. if (_colorScheme == kGIDSignInButtonColorSchemeDark &&
  427. _buttonState != kGIDSignInButtonStateDisabled) {
  428. // Create rounded rectangle container for the "G"
  429. CGMutablePathRef gContainerPath = CGPathCreateMutable();
  430. CGPathAddRoundedRect(gContainerPath,
  431. NULL,
  432. CGRectInset(CGRectMake(0, 0, kButtonHeight, kButtonHeight),
  433. kBorderWidth + 1,
  434. kBorderWidth + 1),
  435. kCornerRadius,
  436. kCornerRadius);
  437. CGContextAddPath(context, gContainerPath);
  438. CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
  439. CGContextFillPath(context);
  440. CGPathRelease(gContainerPath);
  441. }
  442. CGPathRelease(path);
  443. CGContextRestoreGState(context);
  444. }
  445. #pragma mark - Text Rendering
  446. - (void)drawButtonText:(CGContextRef)context {
  447. if (_style == kGIDSignInButtonStyleIconOnly) {
  448. return;
  449. }
  450. NSString *text = self.accessibilityLabel;
  451. UIColor *foregroundColor = colorForStyleState(_colorScheme,
  452. _buttonState,
  453. kGIDSignInButtonStyleColorForeground);
  454. UIFont *font = [[self class] buttonTextFont];
  455. CGSize textSize = [[self class] textSize:text withFont:font];
  456. // Draw the button text at the right position with the right color.
  457. CGFloat textLeft = kIconWidth + kTextPadding;
  458. CGFloat textTop = round((self.bounds.size.height - textSize.height) / 2);
  459. [text drawAtPoint:CGPointMake(textLeft, textTop)
  460. withAttributes:@{ NSFontAttributeName : font,
  461. NSForegroundColorAttributeName : foregroundColor }];
  462. }
  463. #pragma mark - Button Text Selection / Localization
  464. - (NSString *)buttonText {
  465. switch (_style) {
  466. case kGIDSignInButtonStyleWide:
  467. return [GIDSignInStrings signInWithGoogleString];
  468. case kGIDSignInButtonStyleStandard:
  469. case kGIDSignInButtonStyleIconOnly:
  470. return [GIDSignInStrings signInString];
  471. }
  472. }
  473. + (UIFont *)buttonTextFont {
  474. UIFont *font = [UIFont fontWithName:kFontNameRobotoBold size:kFontSize];
  475. if (!font) {
  476. font = [UIFont boldSystemFontOfSize:kFontSize];
  477. }
  478. return font;
  479. }
  480. + (CGSize)textSize:(NSString *)text withFont:(UIFont *)font {
  481. return [text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
  482. options:0
  483. attributes:@{ NSFontAttributeName : font }
  484. context:nil].size;
  485. }
  486. @end
  487. #pragma mark - UIImage GIDAdditions_Private Category
  488. @implementation UIImage (GIDAdditions_Private)
  489. - (UIImage *)gid_imageWithBlendMode:(CGBlendMode)blendMode color:(UIColor *)color {
  490. CGSize size = [self size];
  491. CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
  492. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
  493. CGContextRef context = UIGraphicsGetCurrentContext();
  494. CGContextSetShouldAntialias(context, true);
  495. CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
  496. CGContextScaleCTM(context, 1, -1);
  497. CGContextTranslateCTM(context, 0, -rect.size.height);
  498. CGContextClipToMask(context, rect, self.CGImage);
  499. CGContextDrawImage(context, rect, self.CGImage);
  500. CGContextSetBlendMode(context, blendMode);
  501. CGFloat alpha = 1.0;
  502. if (blendMode == kCGBlendModeMultiply) {
  503. CGFloat red, green, blue;
  504. BOOL success = [color getRed:&red green:&green blue:&blue alpha:&alpha];
  505. if (success) {
  506. color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
  507. } else {
  508. CGFloat grayscale;
  509. success = [color getWhite:&grayscale alpha:&alpha];
  510. if (success) {
  511. color = [UIColor colorWithWhite:grayscale alpha:1.0];
  512. }
  513. }
  514. }
  515. CGContextSetFillColorWithColor(context, color.CGColor);
  516. CGContextFillRect(context, rect);
  517. if (blendMode == kCGBlendModeMultiply && alpha != 1.0) {
  518. // Modulate by the alpha.
  519. color = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:alpha];
  520. CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
  521. CGContextSetFillColorWithColor(context, color.CGColor);
  522. CGContextFillRect(context, rect);
  523. }
  524. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  525. UIGraphicsEndImageContext();
  526. if (self.capInsets.bottom > 0 || self.capInsets.top > 0 ||
  527. self.capInsets.left > 0 || self.capInsets.left > 0) {
  528. image = [image resizableImageWithCapInsets:self.capInsets];
  529. }
  530. return image;
  531. }
  532. @end
  533. NS_ASSUME_NONNULL_END