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 "ui/base/l10n/l10n_util.h"
18 #include "ui/base/l10n/l10n_util_mac.h"
19 #include "ui/gfx/ios/uikit_util.h"
20 #include "ui/gfx/scoped_cg_context_save_gstate_mac.h"
24 // Linearly interpolate between |a| and |b| by fraction |f|. Satisfies
25 // |Lerp(a,b,0) == a| and |Lerp(a,b,1) == b|.
26 CGFloat Lerp(CGFloat a, CGFloat b, CGFloat fraction) {
27 return a * (1.0f - fraction) + b * fraction;
30 // Gets the RGBA components from a UIColor in RBG or monochrome color space.
31 void GetRGBA(UIColor* color, CGFloat* r, CGFloat* g, CGFloat* b, CGFloat* a) {
32 switch (CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor))) {
33 case kCGColorSpaceModelRGB: {
34 BOOL success = [color getRed:r green:g blue:b alpha:a];
38 case kCGColorSpaceModelMonochrome: {
39 const size_t componentsCount =
40 CGColorGetNumberOfComponents(color.CGColor);
41 DCHECK(componentsCount == 1 || componentsCount == 2);
42 const CGFloat* components = CGColorGetComponents(color.CGColor);
46 *a = componentsCount == 1 ? 1 : components[1];
50 NOTREACHED() << "Unsupported color space.";
57 void SetA11yLabelAndUiAutomationName(UIView* element,
58 int idsAccessibilityLabel,
59 NSString* englishUiAutomationName) {
60 [element setAccessibilityLabel:l10n_util::GetNSString(idsAccessibilityLabel)];
61 [element setAccessibilityIdentifier:englishUiAutomationName];
64 void GetSizeButtonWidthToFit(UIButton* button) {
65 // Resize the button's width to fit the new text, but keep the original
66 // height. sizeToFit appears to ignore the image size, so re-add the size of
67 // the button's image to the frame width.
68 CGFloat buttonHeight = [button frame].size.height;
69 CGFloat imageWidth = [[button imageView] frame].size.width;
71 CGRect newFrame = [button frame];
72 newFrame.size.height = buttonHeight;
73 newFrame.size.width += imageWidth;
74 [button setFrame:newFrame];
77 void TranslateFrame(UIView* view, UIOffset offset) {
81 CGRect frame = [view frame];
82 frame.origin.x = frame.origin.x + offset.horizontal;
83 frame.origin.y = frame.origin.y + offset.vertical;
84 [view setFrame:frame];
87 UIFont* GetUIFont(int fontFace, bool isBold, CGFloat fontSize) {
88 NSString* fontFaceName;
91 fontFaceName = isBold ? @"Helvetica-Bold" : @"Helvetica";
93 case FONT_HELVETICA_NEUE:
94 fontFaceName = isBold ? @"HelveticaNeue-Bold" : @"HelveticaNeue";
96 case FONT_HELVETICA_NEUE_LIGHT:
97 // FONT_HELVETICA_NEUE_LIGHT does not support Bold.
99 fontFaceName = @"HelveticaNeue-Light";
103 fontFaceName = @"Helvetica";
106 return [UIFont fontWithName:fontFaceName size:fontSize];
109 void AddBorderShadow(UIView* view, CGFloat offset, UIColor* color) {
110 CGRect rect = CGRectInset(view.bounds, -offset, -offset);
111 CGPoint waypoints[] = {
112 CGPointMake(rect.origin.x, rect.origin.y),
113 CGPointMake(rect.origin.x, rect.origin.y + rect.size.height),
114 CGPointMake(rect.origin.x + rect.size.width,
115 rect.origin.y + rect.size.height),
116 CGPointMake(rect.origin.x + rect.size.width, rect.origin.y),
117 CGPointMake(rect.origin.x, rect.origin.y)};
118 int numberOfWaypoints = sizeof(waypoints) / sizeof(waypoints[0]);
119 CGMutablePathRef outline = CGPathCreateMutable();
120 CGPathAddLines(outline, nullptr, waypoints, numberOfWaypoints);
121 view.layer.shadowColor = [color CGColor];
122 view.layer.shadowOpacity = 1.0;
123 view.layer.shadowOffset = CGSizeZero;
124 view.layer.shadowPath = outline;
125 CGPathRelease(outline);
128 // TODO(pkl): The implementation of this has some duplicated code with
129 // AddBorderShadow and ToolsPopupView newPathForRect:withRadius:withArrow:.
130 // There is an opportunity to refactor them into a common shadow library.
131 void AddRoundedBorderShadow(UIView* view, CGFloat radius, UIColor* color) {
132 CGRect rect = view.bounds;
133 CGMutablePathRef path = CGPathCreateMutable();
134 CGFloat minX = CGRectGetMinX(rect);
135 CGFloat midX = CGRectGetMidX(rect);
136 CGFloat maxX = CGRectGetMaxX(rect);
137 CGFloat minY = CGRectGetMinY(rect);
138 CGFloat midY = CGRectGetMidY(rect);
139 CGFloat maxY = CGRectGetMaxY(rect);
140 CGPathMoveToPoint(path, nullptr, minX, midY);
141 CGPathAddArcToPoint(path, nullptr, minX, minY, midX, minY, radius);
142 CGPathAddArcToPoint(path, nullptr, maxX, minY, maxX, midY, radius);
143 CGPathAddArcToPoint(path, nullptr, maxX, maxY, midX, maxY, radius);
144 CGPathAddArcToPoint(path, nullptr, minX, maxY, minX, midY, radius);
145 CGPathCloseSubpath(path);
146 view.layer.shadowColor = [color CGColor];
147 view.layer.shadowOpacity = 1.0;
148 view.layer.shadowRadius = radius;
149 view.layer.shadowOffset = CGSizeZero;
150 view.layer.shadowPath = path;
151 view.layer.borderWidth = 0.0;
155 UIImage* CaptureView(UIView* view, CGFloat scale) {
156 UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES /* opaque */,
158 CGContext* context = UIGraphicsGetCurrentContext();
159 [view.layer renderInContext:context];
160 UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
161 UIGraphicsEndImageContext();
165 UIImage* GreyImage(UIImage* image) {
167 // Grey images are always non-retina to improve memory performance.
168 UIGraphicsBeginImageContextWithOptions(image.size, YES, 1.0);
169 CGRect greyImageRect = CGRectMake(0, 0, image.size.width, image.size.height);
170 [image drawInRect:greyImageRect blendMode:kCGBlendModeLuminosity alpha:1.0];
171 UIImage* greyImage = UIGraphicsGetImageFromCurrentImageContext();
172 UIGraphicsEndImageContext();
176 UIColor* GetPrimaryActionButtonColor() {
177 return UIColorFromRGB(0x2d6ada, 1.0);
180 UIColor* GetSettingsBackgroundColor() {
181 CGFloat rgb = 237.0 / 255.0;
182 return [UIColor colorWithWhite:rgb alpha:1];
185 UIImage* ResizeImage(UIImage* image,
187 ProjectionMode projectionMode) {
188 CGSize revisedTargetSize;
191 CalculateProjection([image size], targetSize, projectionMode,
192 revisedTargetSize, projectTo);
194 if (CGRectEqualToRect(projectTo, CGRectZero))
197 // Resize photo. Use UIImage drawing methods because they respect
198 // UIImageOrientation as opposed to CGContextDrawImage().
199 UIGraphicsBeginImageContextWithOptions(revisedTargetSize, NO, image.scale);
200 [image drawInRect:projectTo];
201 UIImage* resizedPhoto = UIGraphicsGetImageFromCurrentImageContext();
202 UIGraphicsEndImageContext();
206 UIImage* DarkenImage(UIImage* image) {
207 UIColor* tintColor = [UIColor colorWithWhite:0.22 alpha:0.6];
208 return BlurImage(image,
211 1.8, // saturationDeltaFactor
215 UIImage* BlurImage(UIImage* image,
218 CGFloat saturationDeltaFactor,
219 UIImage* maskImage) {
220 // This code is heavily inspired by the UIImageEffect sample project,
221 // presented at WWDC and available from Apple.
222 DCHECK(image.size.width >= 1 && image.size.height >= 1);
223 DCHECK(image.CGImage);
224 DCHECK(!maskImage || maskImage.CGImage);
226 CGRect imageRect = {CGPointZero, image.size};
227 UIImage* effectImage = nil;
229 BOOL hasBlur = blurRadius > __FLT_EPSILON__;
230 BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__;
231 if (hasBlur || hasSaturationChange) {
232 UIGraphicsBeginImageContextWithOptions(image.size,
234 [[UIScreen mainScreen] scale]);
235 CGContextRef effectInContext = UIGraphicsGetCurrentContext();
236 CGContextScaleCTM(effectInContext, 1.0, -1.0);
237 CGContextTranslateCTM(effectInContext, 0, -image.size.height);
238 CGContextDrawImage(effectInContext, imageRect, image.CGImage);
240 vImage_Buffer effectInBuffer;
241 effectInBuffer.data = CGBitmapContextGetData(effectInContext);
242 effectInBuffer.width = CGBitmapContextGetWidth(effectInContext);
243 effectInBuffer.height = CGBitmapContextGetHeight(effectInContext);
244 effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext);
246 UIGraphicsBeginImageContextWithOptions(image.size,
248 [[UIScreen mainScreen] scale]);
249 CGContextRef effectOutContext = UIGraphicsGetCurrentContext();
250 vImage_Buffer effectOutBuffer;
251 effectOutBuffer.data = CGBitmapContextGetData(effectOutContext);
252 effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext);
253 effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext);
254 effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext);
256 // Those are swapped as effects are applied.
257 vImage_Buffer* inBuffer = &effectInBuffer;
258 vImage_Buffer* outBuffer = &effectOutBuffer;
261 // A description of how to compute the box kernel width from the Gaussian
262 // radius (aka standard deviation) appears in the SVG spec:
263 // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
265 // For larger values of 's' (s >= 2.0), an approximation can be used:
266 // Three successive box-blurs build a piece-wise quadratic convolution
267 // kernel, which approximates the Gaussian kernel to within roughly 3%.
269 // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
271 // ... if d is odd, use three box-blurs of size 'd', centered on the
274 CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale];
275 NSUInteger radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5);
276 if (radius % 2 != 1) {
277 // force radius to be odd so that the three box-blur methodology works.
280 for (int i = 0; i < 3; ++i) {
281 vImageBoxConvolve_ARGB8888(inBuffer, // src.
283 nullptr, // tempBuffer.
284 0, // srcOffsetToROI_X.
285 0, // srcOffsetToROI_Y
286 radius, // kernel_height
287 radius, // kernel_width
288 0, // backgroundColor
289 kvImageEdgeExtend); // flags
290 vImage_Buffer* temp = inBuffer;
291 inBuffer = outBuffer;
295 if (hasSaturationChange) {
296 CGFloat s = saturationDeltaFactor;
297 CGFloat floatingPointSaturationMatrix[] = {
298 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
299 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
300 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
302 const int32_t divisor = 256;
303 NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix) /
304 sizeof(floatingPointSaturationMatrix[0]);
305 int16_t saturationMatrix[matrixSize];
306 for (NSUInteger i = 0; i < matrixSize; ++i) {
307 saturationMatrix[i] =
308 (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor);
310 vImageMatrixMultiply_ARGB8888(inBuffer, outBuffer, saturationMatrix,
311 divisor, nullptr, nullptr, kvImageNoFlags);
313 if (outBuffer == &effectOutBuffer)
314 effectImage = UIGraphicsGetImageFromCurrentImageContext();
315 UIGraphicsEndImageContext();
318 effectImage = UIGraphicsGetImageFromCurrentImageContext();
319 UIGraphicsEndImageContext();
322 // Set up output context.
323 UIGraphicsBeginImageContextWithOptions(image.size,
325 [[UIScreen mainScreen] scale]);
326 CGContextRef outputContext = UIGraphicsGetCurrentContext();
327 CGContextScaleCTM(outputContext, 1.0, -1.0);
328 CGContextTranslateCTM(outputContext, 0, -image.size.height);
331 CGContextDrawImage(outputContext, imageRect, image.CGImage);
333 // Draw effect image.
335 gfx::ScopedCGContextSaveGState context(outputContext);
337 CGContextClipToMask(outputContext, imageRect, maskImage.CGImage);
338 CGContextDrawImage(outputContext, imageRect, effectImage.CGImage);
341 // Add in color tint.
343 gfx::ScopedCGContextSaveGState context(outputContext);
344 CGContextSetFillColorWithColor(outputContext, tintColor.CGColor);
345 CGContextFillRect(outputContext, imageRect);
348 // Output image is ready.
349 UIImage* outputImage = UIGraphicsGetImageFromCurrentImageContext();
350 UIGraphicsEndImageContext();
355 UIImage* CropImage(UIImage* image, const CGRect& cropRect) {
356 CGImageRef cgImage = CGImageCreateWithImageInRect([image CGImage], cropRect);
357 UIImage* result = [UIImage imageWithCGImage:cgImage];
358 CGImageRelease(cgImage);
362 UIInterfaceOrientation GetInterfaceOrientation() {
363 return [[UIApplication sharedApplication] statusBarOrientation];
366 CGFloat CurrentKeyboardHeight(NSValue* keyboardFrameValue) {
367 CGSize keyboardSize = [keyboardFrameValue CGRectValue].size;
368 if (base::ios::IsRunningOnIOS8OrLater()) {
369 return keyboardSize.height;
371 return IsPortrait() ? keyboardSize.height : keyboardSize.width;
375 UIImage* ImageWithColor(UIColor* color) {
376 CGRect rect = CGRectMake(0, 0, 1, 1);
377 UIGraphicsBeginImageContext(rect.size);
378 CGContextRef context = UIGraphicsGetCurrentContext();
379 CGContextSetFillColorWithColor(context, [color CGColor]);
380 CGContextFillRect(context, rect);
381 UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
382 UIGraphicsEndImageContext();
386 UIImage* CircularImageFromImage(UIImage* image, CGFloat width) {
388 CGRectMakeAlignedAndCenteredAt(width / 2.0, width / 2.0, width);
390 UIGraphicsBeginImageContextWithOptions(frame.size, NO, 0.0);
391 CGContextRef context = UIGraphicsGetCurrentContext();
393 CGContextBeginPath(context);
394 CGContextAddEllipseInRect(context, frame);
395 CGContextClosePath(context);
396 CGContextClip(context);
398 CGFloat scaleX = frame.size.width / image.size.width;
399 CGFloat scaleY = frame.size.height / image.size.height;
400 CGFloat scale = std::max(scaleX, scaleY);
401 CGContextScaleCTM(context, scale, scale);
403 [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
405 image = UIGraphicsGetImageFromCurrentImageContext();
406 UIGraphicsEndImageContext();
411 UIColor* InterpolateFromColorToColor(UIColor* firstColor,
412 UIColor* secondColor,
414 DCHECK_LE(0.0, fraction);
415 DCHECK_LE(fraction, 1.0);
416 CGFloat r1, r2, g1, g2, b1, b2, a1, a2;
417 GetRGBA(firstColor, &r1, &g1, &b1, &a1);
418 GetRGBA(secondColor, &r2, &g2, &b2, &a2);
419 return [UIColor colorWithRed:Lerp(r1, r2, fraction)
420 green:Lerp(g1, g2, fraction)
421 blue:Lerp(b1, b2, fraction)
422 alpha:Lerp(a1, a2, fraction)];
425 void ApplyVisualConstraints(NSArray* constraints,
426 NSDictionary* subviewsDictionary,
427 UIView* parentView) {
428 ApplyVisualConstraintsWithMetricsAndOptions(constraints, subviewsDictionary,
432 void ApplyVisualConstraintsWithOptions(NSArray* constraints,
433 NSDictionary* subviewsDictionary,
434 NSLayoutFormatOptions options,
435 UIView* parentView) {
436 ApplyVisualConstraintsWithMetricsAndOptions(constraints, subviewsDictionary,
437 nil, options, parentView);
440 void ApplyVisualConstraintsWithMetrics(NSArray* constraints,
441 NSDictionary* subviewsDictionary,
442 NSDictionary* metrics,
443 UIView* parentView) {
444 ApplyVisualConstraintsWithMetricsAndOptions(constraints, subviewsDictionary,
445 metrics, 0, parentView);
448 void ApplyVisualConstraintsWithMetricsAndOptions(
449 NSArray* constraints,
450 NSDictionary* subviewsDictionary,
451 NSDictionary* metrics,
452 NSLayoutFormatOptions options,
453 UIView* parentView) {
454 for (NSString* constraint in constraints) {
455 DCHECK([constraint isKindOfClass:[NSString class]]);
457 addConstraints:[NSLayoutConstraint
458 constraintsWithVisualFormat:constraint
461 views:subviewsDictionary]];
465 void AddSameCenterXConstraint(UIView* parentView, UIView* subview) {
466 DCHECK_EQ(parentView, [subview superview]);
467 [parentView addConstraint:[NSLayoutConstraint
468 constraintWithItem:subview
469 attribute:NSLayoutAttributeCenterX
470 relatedBy:NSLayoutRelationEqual
472 attribute:NSLayoutAttributeCenterX
477 void AddSameCenterXConstraint(UIView *parentView, UIView *subview1,
479 DCHECK_EQ(parentView, [subview1 superview]);
480 DCHECK_EQ(parentView, [subview2 superview]);
481 DCHECK_NE(subview1, subview2);
482 [parentView addConstraint:[NSLayoutConstraint
483 constraintWithItem:subview1
484 attribute:NSLayoutAttributeCenterX
485 relatedBy:NSLayoutRelationEqual
487 attribute:NSLayoutAttributeCenterX
492 void AddSameCenterYConstraint(UIView* parentView, UIView* subview) {
493 DCHECK_EQ(parentView, [subview superview]);
494 [parentView addConstraint:[NSLayoutConstraint
495 constraintWithItem:subview
496 attribute:NSLayoutAttributeCenterY
497 relatedBy:NSLayoutRelationEqual
499 attribute:NSLayoutAttributeCenterY
504 void AddSameCenterYConstraint(UIView* parentView,
507 DCHECK_EQ(parentView, [subview1 superview]);
508 DCHECK_EQ(parentView, [subview2 superview]);
509 DCHECK_NE(subview1, subview2);
510 [parentView addConstraint:[NSLayoutConstraint
511 constraintWithItem:subview1
512 attribute:NSLayoutAttributeCenterY
513 relatedBy:NSLayoutRelationEqual
515 attribute:NSLayoutAttributeCenterY
520 bool IsCompact(id<UITraitEnvironment> environment) {
521 if (base::ios::IsRunningOnIOS8OrLater()) {
522 return environment.traitCollection.horizontalSizeClass ==
523 UIUserInterfaceSizeClassCompact;
525 // Prior to iOS 8, iPad is always regular, iPhone is always compact.
526 return !IsIPadIdiom();
531 UIWindow* keyWindow = [UIApplication sharedApplication].keyWindow;
532 return IsCompact(keyWindow);
535 bool IsCompactTablet(id<UITraitEnvironment> environment) {
536 return IsIPadIdiom() && IsCompact(environment);
539 bool IsCompactTablet() {
540 return IsIPadIdiom() && IsCompact();