diff --git a/osu.Framework.iOS/Graphics/Textures/IOSTextureLoaderStore.cs b/osu.Framework.iOS/Graphics/Textures/IOSTextureLoaderStore.cs index def073c20a..e8c2dbd097 100644 --- a/osu.Framework.iOS/Graphics/Textures/IOSTextureLoaderStore.cs +++ b/osu.Framework.iOS/Graphics/Textures/IOSTextureLoaderStore.cs @@ -2,12 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; +using Accelerate; using CoreGraphics; using Foundation; +using ObjCRuntime; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; using UIKit; namespace osu.Framework.iOS.Graphics.Textures @@ -19,7 +24,7 @@ public IOSTextureLoaderStore(IResourceStore store) { } - protected override Image ImageFromStream(Stream stream) + protected override unsafe Image ImageFromStream(Stream stream) { using (var nativeData = NSData.FromStream(stream)) { @@ -33,17 +38,64 @@ protected override Image ImageFromStream(Stream stream) int width = (int)uiImage.Size.Width; int height = (int)uiImage.Size.Height; - // TODO: Use pool/memory when builds success with Xamarin. - // Probably at .NET Core 3.1 time frame. - byte[] data = new byte[width * height * 4]; - using (CGBitmapContext textureContext = new CGBitmapContext(data, width, height, 8, width * 4, CGColorSpace.CreateDeviceRGB(), CGImageAlphaInfo.PremultipliedLast)) - textureContext.DrawImage(new CGRect(0, 0, width, height), uiImage.CGImage); + var format = new vImage_CGImageFormat + { + BitsPerComponent = 8, + BitsPerPixel = 32, + ColorSpace = CGColorSpace.CreateDeviceRGB().Handle, + // notably, iOS generally uses premultiplied alpha when rendering image to pixels via CGBitmapContext or otherwise, + // but vImage offers using straight alpha directly without any conversion from our side (by specifying Last instead of PremultipliedLast). + BitmapInfo = (CGBitmapFlags)CGImageAlphaInfo.Last, + Decode = null, + RenderingIntent = CGColorRenderingIntent.Default, + }; - var image = Image.LoadPixelData(data, width, height); + vImageBuffer accelerateImage = default; + // perform initial call to retrieve preferred alignment and bytes-per-row values for the given image dimensions. + long alignment = (long)vImageBuffer_Init(&accelerateImage, (uint)height, (uint)width, 32, vImageFlags.NoAllocate); + Debug.Assert(alignment > 0); + + // allocate aligned memory region to contain image pixel data. + int bytesCount = accelerateImage.BytesPerRow * accelerateImage.Height; + accelerateImage.Data = (IntPtr)NativeMemory.AlignedAlloc((nuint)(accelerateImage.BytesPerRow * accelerateImage.Height), (nuint)alignment); + + var result = vImageBuffer_InitWithCGImage(&accelerateImage, &format, null, uiImage.CGImage!.Handle, vImageFlags.NoAllocate); + Debug.Assert(result == vImageError.NoError); + + var dataSpan = new ReadOnlySpan(accelerateImage.Data.ToPointer(), bytesCount); + + int stride = accelerateImage.BytesPerRow / 4; + var image = Image.LoadPixelData(dataSpan, stride, height); + image.Mutate(i => i.Crop(width, height)); + + NativeMemory.AlignedFree(accelerateImage.Data.ToPointer()); return image; } } } + + #region Accelerate API + + [DllImport(Constants.AccelerateLibrary)] + private static extern unsafe vImageError vImageBuffer_Init(vImageBuffer* buf, uint height, uint width, uint pixelBits, vImageFlags flags); + + [DllImport(Constants.AccelerateLibrary)] + private static extern unsafe vImageError vImageBuffer_InitWithCGImage(vImageBuffer* buf, vImage_CGImageFormat* format, nfloat* backgroundColour, NativeHandle image, vImageFlags flags); + + // ReSharper disable once InconsistentNaming + [StructLayout(LayoutKind.Sequential)] + public unsafe struct vImage_CGImageFormat + { + public uint BitsPerComponent; + public uint BitsPerPixel; + public NativeHandle ColorSpace; + public CGBitmapFlags BitmapInfo; + public uint Version; + public nfloat* Decode; + public CGColorRenderingIntent RenderingIntent; + } + + #endregion } }