File: ImageSources\iOS\ImageSourceExtensions.cs
Web Access
Project: src\src\Core\src\Core.csproj (Microsoft.Maui)
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CoreGraphics;
using CoreImage;
using Foundation;
using ImageIO;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Storage;
using UIKit;
 
namespace Microsoft.Maui
{
	public static partial class ImageSourceExtensions
	{
		internal static UIImage? GetPlatformImage(this IFontImageSource imageSource, IFontManager fontManager, float scale)
		{
			var font = fontManager.GetFont(imageSource.Font);
			var color = (imageSource.Color ?? Colors.White).ToPlatform();
			var glyph = (NSString)imageSource.Glyph;
 
			if (string.IsNullOrWhiteSpace(imageSource.Glyph))
			{
				return null;
			}
 
			var attString = new NSAttributedString(glyph, font, color);
			var imagesize = glyph.GetSizeUsingAttributes(attString.GetUIKitAttributes(0, out _)!);
 
			if (imagesize.Width <= 0 || imagesize.Height <= 0)
			{
				return null;
			}
 
			UIGraphics.BeginImageContextWithOptions(imagesize, false, scale);
			var ctx = new NSStringDrawingContext();
 
			var boundingRect = attString.GetBoundingRect(imagesize, 0, ctx);
			attString.DrawString(new CGRect(
				imagesize.Width / 2 - boundingRect.Size.Width / 2,
				imagesize.Height / 2 - boundingRect.Size.Height / 2,
				imagesize.Width,
				imagesize.Height));
 
			var image = UIGraphics.GetImageFromCurrentImageContext();
			UIGraphics.EndImageContext();
 
			return image.ImageWithRenderingMode(UIImageRenderingMode.Automatic);
		}
 
		internal static UIImage? GetPlatformImage(this IFileImageSource imageSource)
		{
			var filename = imageSource.File;
			return UIImage.FromBundle(filename) ?? UIImage.FromFile(filename);
		}
 
		internal static CGImageSource? GetPlatformImageSource(this IFileImageSource imageSource, out int scale)
		{
			ArgumentNullException.ThrowIfNull(imageSource);
 
			var filename = imageSource.File;
 
			// search the bundle for the scaled image
			var bundle = FileSystemUtils.PlatformGetFullAppPackageFilePath(filename);
			bundle = GetScaledFile(bundle, out scale);
 
			var url = File.Exists(bundle)
				? NSUrl.CreateFileUrl(bundle)
				: NSUrl.CreateFileUrl(filename);
			return CGImageSource.FromUrl(url);
		}
 
		internal static string GetScaledFile(string filename, out int scale)
		{
			const int MaxScale = 3; // max of 3 seems to be what Apple has gone up to
			const int MinScale = 2; // only 2 because 1 is "no scale" without any special logic
 
			var screenScale = (int)UIScreen.MainScreen.Scale;
			if (screenScale > 1)
			{
				var dir = Path.GetDirectoryName(filename);
				var name = Path.GetFileNameWithoutExtension(filename);
				var ext = Path.GetExtension(filename);
 
				var loopMax = Math.Min(MaxScale, screenScale);
				for (var s = loopMax; s >= MinScale; s--)
				{
					var newName = $"{name}@{(int)s}x{ext}";
					var scaled = dir is null
						? newName
						: Path.Combine(dir, newName);
 
					if (File.Exists(scaled))
					{
						scale = s;
						return scaled;
					}
				}
			}
 
			scale = 1;
			return filename;
		}
 
		internal static async Task<CGImageSource?> GetPlatformImageSourceAsync(this IStreamImageSource imageSource, CancellationToken cancellationToken = default)
		{
			ArgumentNullException.ThrowIfNull(imageSource);
 
			var stream = await imageSource.GetStreamAsync(cancellationToken).ConfigureAwait(false);
			if (stream is null)
				throw new ArgumentException("Unable to load image stream.");
 
			return stream.GetPlatformImageSource();
		}
 
		internal static CGImageSource? GetPlatformImageSource(this Stream stream)
		{
			ArgumentNullException.ThrowIfNull(stream);
 
			var data = NSData.FromStream(stream);
			if (data is null)
				throw new ArgumentException("Stream contained no data.", nameof(stream));
 
			return data.GetPlatformImageSource();
		}
 
		internal static CGImageSource? GetPlatformImageSource(this NSData data)
		{
			ArgumentNullException.ThrowIfNull(data);
 
			return CGImageSource.FromData(data);
		}
 
		internal static UIImage GetPlatformImage(this CGImageSource cgImageSource, int scale = 1)
		{
			ArgumentNullException.ThrowIfNull(cgImageSource);
 
			if (cgImageSource.ImageCount == 0)
				throw new InvalidOperationException("CGImageSource does not contain any images.");
 
			UIImage image;
 
			if (cgImageSource.IsAnimated())
			{
				var animated = ImageAnimationHelper.Create(cgImageSource, scale);
				if (animated is null)
					throw new InvalidOperationException("Unable to create animation from CGImageSource.");
 
				image = animated;
			}
			else
			{
				using var cgimage = cgImageSource.CreateImage(0, new() { ShouldCache = false });
				if (cgimage is null)
					throw new InvalidOperationException("Unable to create CGImage from CGImageSource.");
 
				image = new UIImage(cgimage, scale, ToUIImageOrientation(cgImageSource));
			}
 
			return image;
		}
 
		static UIImageOrientation ToUIImageOrientation(CGImageSource cgImageSource)
		{
			var props = cgImageSource.GetProperties(0);
			if (props is null || props.Orientation is null)
				return UIImageOrientation.Up;
 
			return ToUIImageOrientation(props.Orientation.Value);
		}
 
		static UIImageOrientation ToUIImageOrientation(CIImageOrientation cgOrient) => cgOrient switch
		{
			CIImageOrientation.TopLeft => UIImageOrientation.Up,
			CIImageOrientation.TopRight => UIImageOrientation.UpMirrored,
			CIImageOrientation.BottomRight => UIImageOrientation.Down,
			CIImageOrientation.BottomLeft => UIImageOrientation.DownMirrored,
			CIImageOrientation.LeftTop => UIImageOrientation.LeftMirrored,
			CIImageOrientation.RightTop => UIImageOrientation.Right,
			CIImageOrientation.RightBottom => UIImageOrientation.RightMirrored,
			CIImageOrientation.LeftBottom => UIImageOrientation.Left,
			_ => throw new ArgumentOutOfRangeException(nameof(cgOrient)),
		};
	}
}