1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "ios/chrome/browser/ui/uikit_ui_util.h"
7 #import <Accelerate/Accelerate.h>
8 #import <Foundation/Foundation.h>
9 #import <QuartzCore/QuartzCore.h>
10 #import <UIKit/UIKit.h>
13 #include "base/ios/ios_util.h"
14 #include "base/logging.h"
15 #include "base/mac/foundation_util.h"
16 #include "ios/chrome/browser/ui/ui_util.h"
17 #include "ios/chrome/browser/ui/ui_util.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/l10n/l10n_util_mac.h"
20 #include "ui/gfx/ios/uikit_util.h"
21 #include "ui/gfx/scoped_cg_context_save_gstate_mac.h"
25 // Linearly interpolate between |a| and |b| by fraction |f|. Satisfies
26 // |Lerp(a,b,0) == a| and |Lerp(a,b,1) == b|.
27 CGFloat Lerp(CGFloat a, CGFloat b, CGFloat fraction) {
28 return a * (1.0f - fraction) + b * fraction;
31 // Gets the RGBA components from a UIColor in RBG or monochrome color space.
32 void GetRGBA(UIColor* color, CGFloat* r, CGFloat* g, CGFloat* b, CGFloat* a) {
33 switch (CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor))) {
34 case kCGColorSpaceModelRGB: {
35 BOOL success = [color getRed:r green:g blue:b alpha:a];
39 case kCGColorSpaceModelMonochrome: {
40 const size_t componentsCount =
41 CGColorGetNumberOfComponents(color.CGColor);
42 DCHECK(componentsCount == 1 || componentsCount == 2);
43 const CGFloat* components = CGColorGetComponents(color.CGColor);
47 *a = componentsCount == 1 ? 1 : components[1];
51 NOTREACHED() << "Unsupported color space.";
58 void SetA11yLabelAndUiAutomationName(UIView* element,
59 int idsAccessibilityLabel,
60 NSString* englishUiAutomationName) {
61 [element setAccessibilityLabel:l10n_util::GetNSString(idsAccessibilityLabel)];
62 [element setAccessibilityIdentifier:englishUiAutomationName];
65 void GetSizeButtonWidthToFit(UIButton* button) {
66 // Resize the button's width to fit the new text, but keep the original
67 // height. sizeToFit appears to ignore the image size, so re-add the size of
68 // the button's image to the frame width.
69 CGFloat buttonHeight = [button frame].size.height;
70 CGFloat imageWidth = [[button imageView] frame].size.width;
72 CGRect newFrame = [button frame];
73 newFrame.size.height = buttonHeight;
74 newFrame.size.width += imageWidth;
75 [button setFrame:newFrame];
78 void TranslateFrame(UIView* view, UIOffset offset) {
82 CGRect frame = [view frame];
83 frame.origin.x = frame.origin.x + offset.horizontal;
84 frame.origin.y = frame.origin.y + offset.vertical;
85 [view setFrame:frame];
88 UIFont* GetUIFont(int fontFace, bool isBold, CGFloat fontSize) {
89 NSString* fontFaceName;
92 fontFaceName = isBold ? @"Helvetica-Bold" : @"Helvetica";
94 case FONT_HELVETICA_NEUE:
95 fontFaceName = isBold ? @"HelveticaNeue-Bold" : @"HelveticaNeue";
97 case FONT_HELVETICA_NEUE_LIGHT:
98 // FONT_HELVETICA_NEUE_LIGHT does not support Bold.
100 fontFaceName = @"HelveticaNeue-Light";
104 fontFaceName = @"Helvetica";
107 return [UIFont fontWithName:fontFaceName size:fontSize];
110 void AddBorderShadow(UIView* view, CGFloat offset, UIColor* color) {
111 CGRect rect = CGRectInset(view.bounds, -offset, -offset);
112 CGPoint waypoints[] = {
113 CGPointMake(rect.origin.x, rect.origin.y),
114 CGPointMake(rect.origin.x, rect.origin.y + rect.size.height),
115 CGPointMake(rect.origin.x + rect.size.width,
116 rect.origin.y + rect.size.height),
117 CGPointMake(rect.origin.x + rect.size.width, rect.origin.y),
118 CGPointMake(rect.origin.x, rect.origin.y)};
119 int numberOfWaypoints = sizeof(waypoints) / sizeof(waypoints[0]);
120 CGMutablePathRef outline = CGPathCreateMutable();
121 CGPathAddLines(outline, nullptr, waypoints, numberOfWaypoints);
122 view.layer.shadowColor = [color CGColor];
123 view.layer.shadowOpacity = 1.0;
124 view.layer.shadowOffset = CGSizeZero;
125 view.layer.shadowPath = outline;
126 CGPathRelease(outline);
129 // TODO(pkl): The implementation of this has some duplicated code with
130 // AddBorderShadow and ToolsPopupView newPathForRect:withRadius:withArrow:.
131 // There is an opportunity to refactor them into a common shadow library.
132 void AddRoundedBorderShadow(UIView* view, CGFloat radius, UIColor* color) {
133 CGRect rect = view.bounds;
134 CGMutablePathRef path = CGPathCreateMutable();
135 CGFloat minX = CGRectGetMinX(rect);
136 CGFloat midX = CGRectGetMidX(rect);
137 CGFloat maxX = CGRectGetMaxX(rect);
138 CGFloat minY = CGRectGetMinY(rect);
139 CGFloat midY = CGRectGetMidY(rect);
140 CGFloat maxY = CGRectGetMaxY(rect);
141 CGPathMoveToPoint(path, nullptr, minX, midY);
142 CGPathAddArcToPoint(path, nullptr, minX, minY, midX, minY, radius);
143 CGPathAddArcToPoint(path, nullptr, maxX, minY, maxX, midY, radius);
144 CGPathAddArcToPoint(path, nullptr, maxX, maxY, midX, maxY, radius);
145 CGPathAddArcToPoint(path, nullptr, minX, maxY, minX, midY, radius);
146 CGPathCloseSubpath(path);
147 view.layer.shadowColor = [color CGColor];
148 view.layer.shadowOpacity = 1.0;
149 view.layer.shadowRadius = radius;
150 view.layer.shadowOffset = CGSizeZero;
151 view.layer.shadowPath = path;
152 view.layer.borderWidth = 0.0;
156 UIImage* CaptureView(UIView* view, CGFloat scale) {
157 UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES /* opaque */,
159 CGContext* context = UIGraphicsGetCurrentContext();
160 [view.layer renderInContext:context];
161 UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
162 UIGraphicsEndImageContext();
166 UIImage* GreyImage(UIImage* image) {
168 // Grey images are always non-retina to improve memory performance.
169 UIGraphicsBeginImageContextWithOptions(image.size, YES, 1.0);
170 CGRect greyImageRect = CGRectMake(0, 0, image.size.width, image.size.height);
171 [image drawInRect:greyImageRect blendMode:kCGBlendModeLuminosity alpha:1.0];
172 UIImage* greyImage = UIGraphicsGetImageFromCurrentImageContext();
173 UIGraphicsEndImageContext();
177 UIColor* GetPrimaryActionButtonColor() {
178 return UIColorFromRGB(0x2d6ada, 1.0);
181 UIColor* GetSettingsBackgroundColor() {
182 CGFloat rgb = 237.0 / 255.0;
183 return [UIColor colorWithWhite:rgb alpha:1];
186 UIImage* ResizeImage(UIImage* image,
188 ProjectionMode projectionMode) {
189 CGSize revisedTargetSize;
192 CalculateProjection([image size], targetSize, projectionMode,
193 revisedTargetSize, projectTo);
195 if (CGRectEqualToRect(projectTo, CGRectZero))
198 // Resize photo. Use UIImage drawing methods because they respect
199 // UIImageOrientation as opposed to CGContextDrawImage().
200 UIGraphicsBeginImageContextWithOptions(revisedTargetSize, NO, image.scale);
201 [image drawInRect:projectTo];
202 UIImage* resizedPhoto = UIGraphicsGetImageFromCurrentImageContext();
203 UIGraphicsEndImageContext();
207 UIImage* DarkenImage(UIImage* image) {
208 UIColor* tintColor = [UIColor colorWithWhite:0.22 alpha:0.6];
209 return BlurImage(image,
212 1.8, // saturationDeltaFactor
216 UIImage* BlurImage(UIImage* image,
219 CGFloat saturationDeltaFactor,
220 UIImage* maskImage) {
221 // This code is heavily inspired by the UIImageEffect sample project,
222 // presented at WWDC and available from Apple.
223 DCHECK(image.size.width >= 1 && image.size.height >= 1);
224 DCHECK(image.CGImage);
225 DCHECK(!maskImage || maskImage.CGImage);
227 CGRect imageRect = {CGPointZero, image.size};
228 UIImage* effectImage = nil;
230 BOOL hasBlur = blurRadius > __FLT_EPSILON__;
231 BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__;
232 if (hasBlur || hasSaturationChange) {
233 UIGraphicsBeginImageContextWithOptions(image.size,
235 [[UIScreen mainScreen] scale]);
236 CGContextRef effectInContext = UIGraphicsGetCurrentContext();
237 CGContextScaleCTM(effectInContext, 1.0, -1.0);
238 CGContextTranslateCTM(effectInContext, 0, -image.size.height);
239 CGContextDrawImage(effectInContext, imageRect, image.CGImage);
241 vImage_Buffer effectInBuffer;
242 effectInBuffer.data = CGBitmapContextGetData(effectInContext);
243 effectInBuffer.width = CGBitmapContextGetWidth(effectInContext);
244 effectInBuffer.height = CGBitmapContextGetHeight(effectInContext);
245 effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext);
247 UIGraphicsBeginImageContextWithOptions(image.size,
249 [[UIScreen mainScreen] scale]);
250 CGContextRef effectOutContext = UIGraphicsGetCurrentContext();
251 vImage_Buffer effectOutBuffer;
252 effectOutBuffer.data = CGBitmapContextGetData(effectOutContext);
253 effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext);
254 effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext);
255 effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext);
257 // Those are swapped as effects are applied.
258 vImage_Buffer* inBuffer = &effectInBuffer;
259 vImage_Buffer* outBuffer = &effectOutBuffer;
262 // A description of how to compute the box kernel width from the Gaussian
263 // radius (aka standard deviation) appears in the SVG spec:
264 // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
266 // For larger values of 's' (s >= 2.0), an approximation can be used:
267 // Three successive box-blurs build a piece-wise quadratic convolution
268 // kernel, which approximates the Gaussian kernel to within roughly 3%.
270 // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
272 // ... if d is odd, use three box-blurs of size 'd', centered on the
275 CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale];
276 NSUInteger radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5);
277 if (radius % 2 != 1) {
278 // force radius to be odd so that the three box-blur methodology works.
281 for (int i = 0; i < 3; ++i) {
282 vImageBoxConvolve_ARGB8888(inBuffer, // src.
284 nullptr, // tempBuffer.
285 0, // srcOffsetToROI_X.
286 0, // srcOffsetToROI_Y
287 radius, // kernel_height
288 radius, // kernel_width
289 0, // backgroundColor
290 kvImageEdgeExtend); // flags
291 vImage_Buffer* temp = inBuffer;
292 inBuffer = outBuffer;
296 if (hasSaturationChange) {
297 CGFloat s = saturationDeltaFactor;
298 CGFloat floatingPointSaturationMatrix[] = {
299 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
300 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
301 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
303 const int32_t divisor = 256;
304 NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix) /
305 sizeof(floatingPointSaturationMatrix[0]);
306 int16_t saturationMatrix[matrixSize];
307 for (NSUInteger i = 0; i < matrixSize; ++i) {
308 saturationMatrix[i] =
309 (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor);
311 vImageMatrixMultiply_ARGB8888(inBuffer, outBuffer, saturationMatrix,
312 divisor, nullptr, nullptr, kvImageNoFlags);
314 if (outBuffer == &effectOutBuffer)
315 effectImage = UIGraphicsGetImageFromCurrentImageContext();
316 UIGraphicsEndImageContext();
319 effectImage = UIGraphicsGetImageFromCurrentImageContext();
320 UIGraphicsEndImageContext();
323 // Set up output context.
324 UIGraphicsBeginImageContextWithOptions(image.size,
326 [[UIScreen mainScreen] scale]);
327 CGContextRef outputContext = UIGraphicsGetCurrentContext();
328 CGContextScaleCTM(outputContext, 1.0, -1.0);
329 CGContextTranslateCTM(outputContext, 0, -image.size.height);
332 CGContextDrawImage(outputContext, imageRect, image.CGImage);
334 // Draw effect image.
336 gfx::ScopedCGContextSaveGState context(outputContext);
338 CGContextClipToMask(outputContext, imageRect, maskImage.CGImage);
339 CGContextDrawImage(outputContext, imageRect, effectImage.CGImage);
342 // Add in color tint.
344 gfx::ScopedCGContextSaveGState context(outputContext);
345 CGContextSetFillColorWithColor(outputContext, tintColor.CGColor);
346 CGContextFillRect(outputContext, imageRect);
349 // Output image is ready.
350 UIImage* outputImage = UIGraphicsGetImageFromCurrentImageContext();
351 UIGraphicsEndImageContext();
356 UIImage* CropImage(UIImage* image, const CGRect& cropRect) {
357 CGImageRef cgImage = CGImageCreateWithImageInRect([image CGImage], cropRect);
358 UIImage* result = [UIImage imageWithCGImage:cgImage];
359 CGImageRelease(cgImage);
363 UIInterfaceOrientation GetInterfaceOrientation() {
364 return [[UIApplication sharedApplication] statusBarOrientation];
367 CGFloat CurrentKeyboardHeight(NSValue* keyboardFrameValue) {
368 CGSize keyboardSize = [keyboardFrameValue CGRectValue].size;
369 if (base::ios::IsRunningOnIOS8OrLater()) {
370 return keyboardSize.height;
372 return IsPortrait() ? keyboardSize.height : keyboardSize.width;
376 UIImage* ImageWithColor(UIColor* color) {
377 CGRect rect = CGRectMake(0, 0, 1, 1);
378 UIGraphicsBeginImageContext(rect.size);
379 CGContextRef context = UIGraphicsGetCurrentContext();
380 CGContextSetFillColorWithColor(context, [color CGColor]);
381 CGContextFillRect(context, rect);
382 UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
383 UIGraphicsEndImageContext();
387 UIImage* CircularImageFromImage(UIImage* image, CGFloat width) {
389 CGRectMakeAlignedAndCenteredAt(width / 2.0, width / 2.0, width);
391 UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0);
392 CGContextRef context = UIGraphicsGetCurrentContext();
394 CGContextBeginPath(context);
395 CGContextAddEllipseInRect(context, frame);
396 CGContextClosePath(context);
397 CGContextClip(context);
399 CGFloat scaleX = frame.size.width / image.size.width;
400 CGFloat scaleY = frame.size.height / image.size.height;
401 CGFloat scale = std::max(scaleX, scaleY);
402 CGContextScaleCTM(context, scale, scale);
404 [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
406 image = UIGraphicsGetImageFromCurrentImageContext();
407 UIGraphicsEndImageContext();
412 UIColor* InterpolateFromColorToColor(UIColor* firstColor,
413 UIColor* secondColor,
415 DCHECK_LE(0.0, fraction);
416 DCHECK_LE(fraction, 1.0);
417 CGFloat r1, r2, g1, g2, b1, b2, a1, a2;
418 GetRGBA(firstColor, &r1, &g1, &b1, &a1);
419 GetRGBA(secondColor, &r2, &g2, &b2, &a2);
420 return [UIColor colorWithRed:Lerp(r1, r2, fraction)
421 green:Lerp(g1, g2, fraction)
422 blue:Lerp(b1, b2, fraction)
423 alpha:Lerp(a1, a2, fraction)];
426 void ApplyVisualConstraints(NSArray* constraints,
427 NSDictionary* subviewsDictionary,
428 UIView* parentView) {
429 ApplyVisualConstraintsWithMetrics(constraints, subviewsDictionary, nil,
433 void ApplyVisualConstraintsWithMetrics(NSArray* constraints,
434 NSDictionary* subviewsDictionary,
435 NSDictionary* metrics,
436 UIView* parentView) {
437 for (NSString* constraint in constraints) {
438 DCHECK([constraint isKindOfClass:[NSString class]]);
440 addConstraints:[NSLayoutConstraint
441 constraintsWithVisualFormat:constraint
444 views:subviewsDictionary]];
448 void AddSameCenterXConstraint(UIView* parentView, UIView* subview) {
449 DCHECK_EQ(parentView, [subview superview]);
450 [parentView addConstraint:[NSLayoutConstraint
451 constraintWithItem:subview
452 attribute:NSLayoutAttributeCenterX
453 relatedBy:NSLayoutRelationEqual
455 attribute:NSLayoutAttributeCenterX
460 void AddSameCenterXConstraint(UIView *parentView, UIView *subview1,
462 DCHECK_EQ(parentView, [subview1 superview]);
463 DCHECK_EQ(parentView, [subview2 superview]);
464 DCHECK_NE(subview1, subview2);
465 [parentView addConstraint:[NSLayoutConstraint
466 constraintWithItem:subview1
467 attribute:NSLayoutAttributeCenterX
468 relatedBy:NSLayoutRelationEqual
470 attribute:NSLayoutAttributeCenterX
475 void AddSameCenterYConstraint(UIView* parentView, UIView* subview) {
476 DCHECK_EQ(parentView, [subview superview]);
477 [parentView addConstraint:[NSLayoutConstraint
478 constraintWithItem:subview
479 attribute:NSLayoutAttributeCenterY
480 relatedBy:NSLayoutRelationEqual
482 attribute:NSLayoutAttributeCenterY
487 void AddSameCenterYConstraint(UIView* parentView,
490 DCHECK_EQ(parentView, [subview1 superview]);
491 DCHECK_EQ(parentView, [subview2 superview]);
492 DCHECK_NE(subview1, subview2);
493 [parentView addConstraint:[NSLayoutConstraint
494 constraintWithItem:subview1
495 attribute:NSLayoutAttributeCenterY
496 relatedBy:NSLayoutRelationEqual
498 attribute:NSLayoutAttributeCenterY
503 bool IsCompactTablet() {
504 if (base::ios::IsRunningOnIOS8OrLater()) {
505 UIViewController* rootController =
506 [UIApplication sharedApplication].keyWindow.rootViewController;
507 return IsCompactTabletSizeClass(
508 [rootController.traitCollection horizontalSizeClass]);
510 return IsCompactTabletSizeClass(UIUserInterfaceSizeClassRegular);
514 bool IsCompactTabletSizeClass(UIUserInterfaceSizeClass sizeClass) {
515 return IsIPadIdiom() && sizeClass == UIUserInterfaceSizeClassCompact;
518 BOOL IsRTLUILayout() {
519 if (base::ios::IsRunningOnIOS9OrLater()) {
521 // Calling this method is better than using the locale on iOS9 since it will
522 // work with the right to left pseudolanguage.
523 return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:
524 UISemanticContentAttributeUnspecified] ==
525 UIUserInterfaceLayoutDirectionRightToLeft;
528 // Using NSLocale instead of base::i18n::IsRTL() in order to take into account
529 // right to left pseudolanguage correctly (which base::i18n::IsRTL() doesn't).
532 characterDirectionForLanguage:
533 [[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode]] ==
534 NSLocaleLanguageDirectionRightToLeft;