Move iOS implementation to base AppleTextureLoaderStore class

This commit is contained in:
Salman Alshamrani
2024-12-28 14:49:11 -05:00
parent 978253e440
commit cb07a373e7
2 changed files with 80 additions and 80 deletions

View File

@@ -2,22 +2,16 @@
// 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 osu.Framework.Platform.Apple;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using UIKit;
namespace osu.Framework.iOS.Graphics.Textures
{
public class IOSTextureLoaderStore : TextureLoaderStore
internal class IOSTextureLoaderStore : AppleTextureLoaderStore
{
public IOSTextureLoaderStore(IResourceStore<byte[]> store)
: base(store)
@@ -26,81 +20,18 @@ namespace osu.Framework.iOS.Graphics.Textures
protected override unsafe Image<TPixel> ImageFromStream<TPixel>(Stream stream)
{
using (var nativeData = NSData.FromStream(stream))
{
if (nativeData == null)
throw new ArgumentException($"{nameof(Image)} could not be created from {nameof(stream)}.");
int length = (int)(stream.Length - stream.Position);
using var nativeData = NSMutableData.FromLength(length);
using (var uiImage = UIImage.LoadFromData(nativeData))
{
if (uiImage == null) throw new ArgumentException($"{nameof(Image)} could not be created from {nameof(stream)}.");
var bytesSpan = new Span<byte>(nativeData.MutableBytes.ToPointer(), length);
stream.ReadExactly(bytesSpan);
int width = (int)uiImage.Size.Width;
int height = (int)uiImage.Size.Height;
using var uiImage = UIImage.LoadFromData(nativeData);
if (uiImage == null)
throw new ArgumentException($"{nameof(Image)} could not be created from {nameof(stream)}.");
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,
};
vImageBuffer accelerateImage = default;
// perform initial call to retrieve preferred alignment and bytes-per-row values for the given image dimensions.
nuint alignment = (nuint)vImageBuffer_Init(&accelerateImage, (uint)height, (uint)width, 32, vImageFlags.NoAllocate);
Debug.Assert(alignment > 0);
// allocate aligned memory region to contain image pixel data.
int bytesPerRow = accelerateImage.BytesPerRow;
int bytesCount = bytesPerRow * accelerateImage.Height;
accelerateImage.Data = (IntPtr)NativeMemory.AlignedAlloc((nuint)bytesCount, alignment);
var result = vImageBuffer_InitWithCGImage(&accelerateImage, &format, null, uiImage.CGImage!.Handle, vImageFlags.NoAllocate);
Debug.Assert(result == vImageError.NoError);
var image = new Image<TPixel>(width, height);
byte* data = (byte*)accelerateImage.Data;
for (int i = 0; i < height; i++)
{
var imageRow = image.DangerousGetPixelRowMemory(i);
var dataRow = new ReadOnlySpan<TPixel>(&data[bytesPerRow * i], width);
dataRow.CopyTo(imageRow.Span);
}
NativeMemory.AlignedFree(accelerateImage.Data.ToPointer());
return image;
}
}
var cgImage = new Platform.Apple.Native.CGImage(uiImage.CGImage!.Handle);
return ImageFromCGImage<TPixel>(cgImage);
}
#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
}
}

View File

@@ -0,0 +1,69 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Platform.Apple.Native;
using osu.Framework.Platform.Apple.Native.Accelerate;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace osu.Framework.Platform.Apple
{
internal abstract class AppleTextureLoaderStore : TextureLoaderStore
{
protected AppleTextureLoaderStore(IResourceStore<byte[]> store)
: base(store)
{
}
protected unsafe Image<TPixel> ImageFromCGImage<TPixel>(CGImage cgImage)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = (int)cgImage.Width;
int height = (int)cgImage.Height;
var format = new vImage_CGImageFormat
{
BitsPerComponent = 8,
BitsPerPixel = 32,
ColorSpace = CGColorSpace.CreateDeviceRGB(),
// notably, macOS & iOS generally use premultiplied alpha when rendering image to pixels via CGBitmapContext or otherwise,
// but vImage offers rendering as straight alpha by specifying Last instead of PremultipliedLast.
BitmapInfo = (CGBitmapInfo)CGImageAlphaInfo.Last,
Decode = null,
RenderingIntent = CGColorRenderingIntent.Default,
};
vImage_Buffer accImage = default;
// perform initial call to retrieve preferred alignment and bytes-per-row values for the given image dimensions.
nuint alignment = (nuint)vImage.Init(&accImage, (uint)height, (uint)width, 32, vImage_Flags.NoAllocate);
Debug.Assert(alignment > 0);
// allocate aligned memory region to contain image pixel data.
nuint bytesPerRow = accImage.BytesPerRow;
nuint bytesCount = bytesPerRow * accImage.Height;
accImage.Data = (byte*)NativeMemory.AlignedAlloc(bytesCount, alignment);
var result = vImage.InitWithCGImage(&accImage, &format, null, cgImage.Handle, vImage_Flags.NoAllocate);
Debug.Assert(result == vImage_Error.NoError);
var image = new Image<TPixel>(width, height);
for (int i = 0; i < height; i++)
{
var imageRow = image.DangerousGetPixelRowMemory(i);
var dataRow = new ReadOnlySpan<TPixel>(&accImage.Data[(int)bytesPerRow * i], width);
dataRow.CopyTo(imageRow.Span);
}
NativeMemory.AlignedFree(accImage.Data);
return image;
}
}
}