using System; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using Bit.Core.Utilities; using SkiaSharp; using Xamarin.Forms; namespace Bit.App.Controls { public class AvatarImageSource : StreamImageSource { private readonly string _text; private readonly string _id; private readonly string _color; public override bool Equals(object obj) { if (obj is null) { return false; } if (obj is AvatarImageSource avatar) { return avatar._id == _id && avatar._text == _text && avatar._color == _color; } return base.Equals(obj); } public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1; public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null) { _id = userId; _text = name; if (string.IsNullOrWhiteSpace(_text)) { _text = email; } _color = color; } public override Func> Stream => GetStreamAsync; private Task GetStreamAsync(CancellationToken userToken = new CancellationToken()) { OnLoadingStarted(); userToken.Register(CancellationTokenSource.Cancel); var result = Draw(); OnLoadingCompleted(CancellationTokenSource.IsCancellationRequested); return Task.FromResult(result); } private Stream Draw() { string chars; string upperCaseText = null; if (string.IsNullOrEmpty(_text)) { chars = ".."; } else if (_text?.Length > 1) { upperCaseText = _text.ToUpper(); chars = GetFirstLetters(upperCaseText, 2); } else { chars = upperCaseText = _text.ToUpper(); } var bgColor = _color ?? CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff"); var textColor = CoreHelpers.TextColorFromBgColor(bgColor); var size = 50; using (var bitmap = new SKBitmap(size * 2, size * 2, SKImageInfo.PlatformColorType, SKAlphaType.Premul)) { using (var canvas = new SKCanvas(bitmap)) { canvas.Clear(SKColors.Transparent); using (var paint = new SKPaint { IsAntialias = true, Style = SKPaintStyle.Fill, StrokeJoin = SKStrokeJoin.Miter, Color = SKColor.Parse(bgColor) }) { var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2; var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2; var radius = midX - midX / 5; using (var circlePaint = new SKPaint { IsAntialias = true, Style = SKPaintStyle.Fill, StrokeJoin = SKStrokeJoin.Miter, Color = SKColor.Parse(bgColor) }) { canvas.DrawCircle(midX, midY, radius, circlePaint); var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal); var textSize = midX / 1.3f; using (var textPaint = new SKPaint { IsAntialias = true, Style = SKPaintStyle.Fill, Color = SKColor.Parse(textColor), TextSize = textSize, TextAlign = SKTextAlign.Center, Typeface = typeface }) { var rect = new SKRect(); textPaint.MeasureText(chars, ref rect); canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint); using (var img = SKImage.FromBitmap(bitmap)) { var data = img.Encode(SKEncodedImageFormat.Png, 100); return data?.AsStream(true); } } } } } } } private string GetFirstLetters(string data, int charCount) { var sanitizedData = data.Trim(); var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length > 1 && charCount <= 2) { var text = string.Empty; for (var i = 0; i < charCount; i++) { text += parts[i][0]; } return text; } if (sanitizedData.Length > 2) { return sanitizedData.Substring(0, 2); } return sanitizedData; } private Color StringToColor(string str) { if (str == null) { return Color.FromHex("#33ffffff"); } var hash = 0; for (var i = 0; i < str.Length; i++) { hash = str[i] + ((hash << 5) - hash); } var color = "#FF"; for (var i = 0; i < 3; i++) { var value = (hash >> (i * 8)) & 0xff; var base16 = "00" + Convert.ToString(value, 16); color += base16.Substring(base16.Length - 2); } return Color.FromHex(color); } } }