mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
11 KiB
Matlab
297 lines
11 KiB
Matlab
7 years ago
|
//
|
||
7 years ago
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||
7 years ago
|
//
|
||
8 years ago
|
|
||
|
#import "OWSAvatarBuilder.h"
|
||
|
#import "OWSContactAvatarBuilder.h"
|
||
|
#import "OWSGroupAvatarBuilder.h"
|
||
|
#import "TSContactThread.h"
|
||
|
#import "TSGroupThread.h"
|
||
6 years ago
|
#import "Theme.h"
|
||
7 years ago
|
#import "UIColor+OWS.h"
|
||
6 years ago
|
#import "UIFont+OWS.h"
|
||
6 years ago
|
#import "UIView+OWS.h"
|
||
4 years ago
|
|
||
8 years ago
|
|
||
|
NS_ASSUME_NONNULL_BEGIN
|
||
|
|
||
6 years ago
|
const NSUInteger kStandardAvatarSize = 48;
|
||
6 years ago
|
const NSUInteger kLargeAvatarSize = 68;
|
||
6 years ago
|
|
||
6 years ago
|
typedef void (^OWSAvatarDrawBlock)(CGContextRef context);
|
||
|
|
||
8 years ago
|
@implementation OWSAvatarBuilder
|
||
|
|
||
6 years ago
|
+ (nullable UIImage *)buildImageForThread:(TSThread *)thread
|
||
|
diameter:(NSUInteger)diameter
|
||
8 years ago
|
{
|
||
6 years ago
|
OWSAssertDebug(thread);
|
||
6 years ago
|
|
||
8 years ago
|
OWSAvatarBuilder *avatarBuilder;
|
||
|
if ([thread isKindOfClass:[TSContactThread class]]) {
|
||
7 years ago
|
TSContactThread *contactThread = (TSContactThread *)thread;
|
||
5 years ago
|
avatarBuilder = [[OWSContactAvatarBuilder alloc] initWithSignalId:contactThread.contactIdentifier colorName:contactThread.conversationColorName diameter:diameter];
|
||
8 years ago
|
} else if ([thread isKindOfClass:[TSGroupThread class]]) {
|
||
6 years ago
|
avatarBuilder = [[OWSGroupAvatarBuilder alloc] initWithThread:(TSGroupThread *)thread diameter:diameter];
|
||
8 years ago
|
} else {
|
||
6 years ago
|
OWSLogError(@"called with unsupported thread: %@", thread);
|
||
8 years ago
|
}
|
||
|
return [avatarBuilder build];
|
||
|
}
|
||
|
|
||
6 years ago
|
+ (nullable UIImage *)buildRandomAvatarWithDiameter:(NSUInteger)diameter
|
||
7 years ago
|
{
|
||
|
NSArray<NSString *> *eyes = @[ @":", @"=", @"8", @"B" ];
|
||
|
NSArray<NSString *> *mouths = @[ @"3", @")", @"(", @"|", @"\\", @"P", @"D", @"o" ];
|
||
|
// eyebrows are rare
|
||
|
NSArray<NSString *> *eyebrows = @[ @">", @"", @"", @"", @"" ];
|
||
|
|
||
|
NSString *randomEye = eyes[arc4random_uniform((uint32_t)eyes.count)];
|
||
|
NSString *randomMouth = mouths[arc4random_uniform((uint32_t)mouths.count)];
|
||
|
NSString *randomEyebrow = eyebrows[arc4random_uniform((uint32_t)eyebrows.count)];
|
||
|
NSString *face = [NSString stringWithFormat:@"%@%@%@", randomEyebrow, randomEye, randomMouth];
|
||
|
|
||
6 years ago
|
UIColor *backgroundColor = [UIColor colorWithRGBHex:0xaca6633];
|
||
|
|
||
|
return [self avatarImageWithDiameter:diameter
|
||
|
backgroundColor:backgroundColor
|
||
|
drawBlock:^(CGContextRef context) {
|
||
|
CGContextTranslateCTM(context, diameter / 2, diameter / 2);
|
||
|
CGContextRotateCTM(context, (CGFloat)M_PI_2);
|
||
|
CGContextTranslateCTM(context, -diameter / 2, -diameter / 2);
|
||
|
|
||
|
[self drawInitialsInAvatar:face
|
||
|
textColor:self.avatarForegroundColor
|
||
6 years ago
|
font:[self avatarTextFontForDiameter:diameter]
|
||
6 years ago
|
diameter:diameter];
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
+ (UIColor *)avatarForegroundColor
|
||
|
{
|
||
|
return (Theme.isDarkThemeEnabled ? UIColor.ows_gray05Color : UIColor.ows_whiteColor);
|
||
|
}
|
||
|
|
||
6 years ago
|
+ (UIFont *)avatarTextFontForDiameter:(NSUInteger)diameter
|
||
6 years ago
|
{
|
||
6 years ago
|
// Adapt the font size to reflect the diameter.
|
||
|
CGFloat fontSize = 20.f * diameter / kStandardAvatarSize;
|
||
|
return [UIFont ows_mediumFontWithSize:fontSize];
|
||
6 years ago
|
}
|
||
|
|
||
|
+ (nullable UIImage *)avatarImageWithInitials:(NSString *)initials
|
||
|
backgroundColor:(UIColor *)backgroundColor
|
||
|
diameter:(NSUInteger)diameter
|
||
|
{
|
||
|
return [self avatarImageWithInitials:initials
|
||
|
backgroundColor:backgroundColor
|
||
|
textColor:self.avatarForegroundColor
|
||
6 years ago
|
font:[self avatarTextFontForDiameter:diameter]
|
||
6 years ago
|
diameter:diameter];
|
||
|
}
|
||
|
|
||
|
+ (nullable UIImage *)avatarImageWithInitials:(NSString *)initials
|
||
|
backgroundColor:(UIColor *)backgroundColor
|
||
|
textColor:(UIColor *)textColor
|
||
|
font:(UIFont *)font
|
||
|
diameter:(NSUInteger)diameter
|
||
|
{
|
||
|
OWSAssertDebug(initials);
|
||
|
OWSAssertDebug(textColor);
|
||
|
OWSAssertDebug(font);
|
||
|
|
||
|
return [self avatarImageWithDiameter:diameter
|
||
|
backgroundColor:backgroundColor
|
||
|
drawBlock:^(CGContextRef context) {
|
||
|
[self drawInitialsInAvatar:initials textColor:textColor font:font diameter:diameter];
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
+ (nullable UIImage *)avatarImageWithIcon:(UIImage *)icon
|
||
6 years ago
|
iconSize:(CGSize)iconSize
|
||
6 years ago
|
backgroundColor:(UIColor *)backgroundColor
|
||
|
diameter:(NSUInteger)diameter
|
||
|
{
|
||
|
return [self avatarImageWithIcon:icon
|
||
6 years ago
|
iconSize:iconSize
|
||
6 years ago
|
iconColor:self.avatarForegroundColor
|
||
|
backgroundColor:backgroundColor
|
||
|
diameter:diameter];
|
||
|
}
|
||
|
|
||
|
+ (nullable UIImage *)avatarImageWithIcon:(UIImage *)icon
|
||
6 years ago
|
iconSize:(CGSize)iconSize
|
||
6 years ago
|
iconColor:(UIColor *)iconColor
|
||
|
backgroundColor:(UIColor *)backgroundColor
|
||
|
diameter:(NSUInteger)diameter
|
||
|
{
|
||
|
OWSAssertDebug(icon);
|
||
|
OWSAssertDebug(iconColor);
|
||
|
|
||
|
return [self avatarImageWithDiameter:diameter
|
||
|
backgroundColor:backgroundColor
|
||
|
drawBlock:^(CGContextRef context) {
|
||
6 years ago
|
[self drawIconInAvatar:icon
|
||
|
iconSize:iconSize
|
||
|
iconColor:iconColor
|
||
|
diameter:diameter
|
||
|
context:context];
|
||
6 years ago
|
}];
|
||
|
}
|
||
7 years ago
|
|
||
6 years ago
|
+ (nullable UIImage *)avatarImageWithDiameter:(NSUInteger)diameter
|
||
|
backgroundColor:(UIColor *)backgroundColor
|
||
|
drawBlock:(OWSAvatarDrawBlock)drawBlock
|
||
|
{
|
||
|
OWSAssertDebug(drawBlock);
|
||
|
OWSAssertDebug(backgroundColor);
|
||
|
OWSAssertDebug(diameter > 0);
|
||
7 years ago
|
|
||
6 years ago
|
CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter);
|
||
7 years ago
|
|
||
6 years ago
|
UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale);
|
||
|
CGContextRef _Nullable context = UIGraphicsGetCurrentContext();
|
||
|
if (!context) {
|
||
|
return nil;
|
||
|
}
|
||
7 years ago
|
|
||
6 years ago
|
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
|
||
|
CGContextFillRect(context, frame);
|
||
7 years ago
|
|
||
6 years ago
|
// Gradient
|
||
|
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
|
||
|
CGFloat gradientLocations[] = { 0.0, 1.0 };
|
||
|
CGGradientRef _Nullable gradient = CGGradientCreateWithColors(colorspace,
|
||
|
(__bridge CFArrayRef) @[
|
||
|
(id)[UIColor colorWithWhite:0.f alpha:0.f].CGColor,
|
||
|
(id)[UIColor colorWithWhite:0.f alpha:0.15f].CGColor,
|
||
|
],
|
||
|
gradientLocations);
|
||
|
if (!gradient) {
|
||
|
return nil;
|
||
|
}
|
||
|
CGPoint startPoint = CGPointMake(diameter * 0.5f, 0);
|
||
|
CGPoint endPoint = CGPointMake(diameter * 0.5f, diameter);
|
||
|
CGContextDrawLinearGradient(context,
|
||
|
gradient,
|
||
|
startPoint,
|
||
|
endPoint,
|
||
|
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
||
|
CFRelease(gradient);
|
||
7 years ago
|
|
||
6 years ago
|
CGContextSaveGState(context);
|
||
|
drawBlock(context);
|
||
|
CGContextRestoreGState(context);
|
||
|
|
||
|
UIImage *_Nullable image = UIGraphicsGetImageFromCurrentImageContext();
|
||
7 years ago
|
|
||
|
UIGraphicsEndImageContext();
|
||
6 years ago
|
|
||
7 years ago
|
return image;
|
||
|
}
|
||
|
|
||
6 years ago
|
+ (void)drawInitialsInAvatar:(NSString *)initials
|
||
|
textColor:(UIColor *)textColor
|
||
|
font:(UIFont *)font
|
||
|
diameter:(NSUInteger)diameter
|
||
|
{
|
||
|
OWSAssertDebug(initials);
|
||
|
OWSAssertDebug(textColor);
|
||
|
OWSAssertDebug(font);
|
||
|
OWSAssertDebug(diameter > 0);
|
||
|
|
||
|
CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter);
|
||
|
|
||
|
NSDictionary *textAttributes = @{
|
||
|
NSFontAttributeName : font,
|
||
|
NSForegroundColorAttributeName : textColor,
|
||
|
};
|
||
|
CGSize textSize =
|
||
|
[initials boundingRectWithSize:frame.size
|
||
|
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
|
||
|
attributes:textAttributes
|
||
|
context:nil]
|
||
|
.size;
|
||
|
// Ensure that the text fits within the avatar bounds, with a margin.
|
||
|
if (textSize.width > 0 && textSize.height > 0) {
|
||
|
CGFloat textDiameter = (CGFloat)sqrt(textSize.width * textSize.width + textSize.height * textSize.height);
|
||
|
// Leave a 10% margin.
|
||
|
CGFloat maxTextDiameter = diameter * 0.9f;
|
||
|
if (textDiameter > maxTextDiameter) {
|
||
|
font = [font fontWithSize:font.pointSize * maxTextDiameter / textDiameter];
|
||
|
textAttributes = @{
|
||
|
NSFontAttributeName : font,
|
||
|
NSForegroundColorAttributeName : textColor,
|
||
|
};
|
||
|
textSize =
|
||
|
[initials boundingRectWithSize:frame.size
|
||
|
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
|
||
|
attributes:textAttributes
|
||
|
context:nil]
|
||
|
.size;
|
||
|
}
|
||
6 years ago
|
} else {
|
||
|
OWSFailDebug(@"Text has invalid bounds.");
|
||
6 years ago
|
}
|
||
|
|
||
|
CGPoint drawPoint = CGPointMake((diameter - textSize.width) * 0.5f, (diameter - textSize.height) * 0.5f);
|
||
|
|
||
|
[initials drawAtPoint:drawPoint withAttributes:textAttributes];
|
||
|
}
|
||
|
|
||
6 years ago
|
+ (void)drawIconInAvatar:(UIImage *)icon
|
||
|
iconSize:(CGSize)iconSize
|
||
|
iconColor:(UIColor *)iconColor
|
||
|
diameter:(NSUInteger)diameter
|
||
|
context:(CGContextRef)context
|
||
6 years ago
|
{
|
||
|
OWSAssertDebug(icon);
|
||
|
OWSAssertDebug(iconColor);
|
||
|
OWSAssertDebug(diameter > 0);
|
||
6 years ago
|
OWSAssertDebug(context);
|
||
|
|
||
|
// UIKit uses an ULO coordinate system (upper-left-origin).
|
||
|
// Core Graphics uses an LLO coordinate system (lower-left-origin).
|
||
|
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, diameter);
|
||
|
CGContextConcatCTM(context, flipVertical);
|
||
|
|
||
|
CGRect imageRect = CGRectZero;
|
||
|
imageRect.size = CGSizeMake(diameter, diameter);
|
||
|
|
||
|
// The programmatic equivalent of UIImageRenderingModeAlwaysTemplate/tintColor.
|
||
|
CGContextSetBlendMode(context, kCGBlendModeNormal);
|
||
|
CGRect maskRect = CGRectZero;
|
||
|
maskRect.origin = CGPointScale(
|
||
|
CGPointSubtract(CGPointMake(diameter, diameter), CGPointMake(iconSize.width, iconSize.height)), 0.5f);
|
||
|
maskRect.size = iconSize;
|
||
|
CGContextClipToMask(context, maskRect, icon.CGImage);
|
||
|
CGContextSetFillColor(context, CGColorGetComponents(iconColor.CGColor));
|
||
|
CGContextFillRect(context, imageRect);
|
||
6 years ago
|
}
|
||
|
|
||
|
- (nullable UIImage *)build
|
||
8 years ago
|
{
|
||
|
UIImage *_Nullable savedImage = [self buildSavedImage];
|
||
|
if (savedImage) {
|
||
|
return savedImage;
|
||
|
} else {
|
||
|
return [self buildDefaultImage];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (nullable UIImage *)buildSavedImage
|
||
|
{
|
||
6 years ago
|
OWSAbstractMethod();
|
||
6 years ago
|
return nil;
|
||
8 years ago
|
}
|
||
|
|
||
6 years ago
|
- (nullable UIImage *)buildDefaultImage
|
||
8 years ago
|
{
|
||
6 years ago
|
OWSAbstractMethod();
|
||
6 years ago
|
return nil;
|
||
8 years ago
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
NS_ASSUME_NONNULL_END
|