using System; using System.IO; using System.Runtime.InteropServices; namespace ReFuel.Stb { /// /// A class that encompasses all features of stb_image.h in a safe way. /// public unsafe class StbImage : IDisposable { private bool isDisposed = false; /// /// Pointer to the image. /// public IntPtr ImagePointer { get; } /// /// Width of the image. /// public int Width { get; } /// /// Height of the image. /// /// public int Height { get; } /// /// Internal image format. /// public StbiImageFormat Format { get; } /// /// True if the image is a floating point image. /// public bool IsFloat { get; } private StbImage(IntPtr image, int x, int y, StbiImageFormat format, bool isFloat) { ImagePointer = image; Width = x; Height = y; Format = format; IsFloat = isFloat; } ~StbImage() { Dispose(false); } public void Dispose() => Dispose(true); private void Dispose(bool disposing) { if (isDisposed) return; if (disposing) { GC.SuppressFinalize(this); } Stbi.image_free(ImagePointer.ToPointer()); isDisposed = true; } /// /// Set to flip the y-axis of loaded images on load. /// public static bool FlipVerticallyOnLoad { set => Stbi.set_flip_vertically_on_load(1); } /// /// Set to unpremultiply images on load. /// /// /// According to the stb_image documentation, only iPhone PNG images /// can come with premultiplied alpha. /// public static bool UnpremultiplyOnLoad { set => Stbi.set_unpremultiply_on_load(1); } /// /// Try loading an image, without raising exceptions. /// /// The resulting image. /// Source stream. /// The desired image format. /// True on success. public static bool TryLoad(out StbImage image, Stream stream, StbiImageFormat format = StbiImageFormat.Default, bool isFloat = false) { int x, y, iFormat; StbiStreamWrapper wrapper = new StbiStreamWrapper(stream, true); wrapper.CreateCallbacks(out stbi_io_callbacks cb); stream.Position = 0; IntPtr imagePtr; if (isFloat) { imagePtr = (IntPtr)Stbi.loadf_from_callbacks(&cb, null, &x, &y, &iFormat, (int)format); } else { imagePtr = (IntPtr)Stbi.load_from_callbacks(&cb, null, &x, &y, &iFormat, (int)format); } if (imagePtr != IntPtr.Zero) { image = new StbImage(imagePtr, x, y, (StbiImageFormat)iFormat, isFloat); return true; } else { image = null; return false; } } /// /// Try loading an image, without raising exceptions. /// /// The resulting image. /// Source memory span. /// The desired image format. /// True on success. public static bool TryLoad(out StbImage image, ReadOnlySpan span, StbiImageFormat format = StbiImageFormat.Default, bool isFloat = false) where T : unmanaged { IntPtr imagePtr = IntPtr.Zero; int x, y, iFormat; fixed (byte *ptr = MemoryMarshal.AsBytes(span)) { if (isFloat) { imagePtr = (IntPtr)Stbi.loadf_from_memory(ptr, span.Length * sizeof(T), &x, &y, &iFormat, (int)format); } else { imagePtr = (IntPtr)Stbi.load_from_memory(ptr, span.Length * sizeof(T), &x, &y, &iFormat, (int)format); } if (imagePtr != IntPtr.Zero) { image = new StbImage(imagePtr, x, y, (StbiImageFormat)iFormat, isFloat); return true; } else { image = null; return false; } } } /// /// Load an image. /// /// The stream to load from. /// The desired image format. /// The image object. public static StbImage Load(Stream stream, StbiImageFormat format = StbiImageFormat.Default, bool isFloat = false) { if (TryLoad(out StbImage image, stream, format, isFloat)) { return image; } string reason = Marshal.PtrToStringUTF8((IntPtr)Stbi.failure_reason()); throw new Exception($"Failed to load image: {reason}"); } /// /// Load an image. /// /// The span of memory to load from. /// The desired image format. /// The image object. public static StbImage Load(ReadOnlySpan span, StbiImageFormat format = StbiImageFormat.Default, bool isFloat = false) where T : unmanaged { if (TryLoad(out StbImage image, span, format, isFloat)) { return image; } string reason = Marshal.PtrToStringUTF8((IntPtr)Stbi.failure_reason()); throw new Exception($"Failed to load image: {reason}"); } /// /// Peek image info from a stream. /// /// The stream to peek into. /// Width of the image. /// Height of the image. /// The image format. /// True if the stream contained an image. public static bool PeekInfo(Stream stream, out int width, out int height, out StbiImageFormat format) { int x, y, iFormat; StbiStreamWrapper wrapper = new StbiStreamWrapper(stream, true); wrapper.CreateCallbacks(out stbi_io_callbacks cb); stream.Position = 0; int result = Stbi.info_from_callbacks(&cb, null, &x, &y, &iFormat); width = x; height = y; format = (StbiImageFormat)iFormat; return result != 0; } /// /// Peek image info from a span. /// /// The span to peek into. /// Width of the image. /// Height of the image. /// The image format. /// True if the stream contained an image. public static bool PeekInfo(ReadOnlySpan span, out int width, out int height, out StbiImageFormat format) where T : unmanaged { fixed (byte* ptr = MemoryMarshal.AsBytes(span)) { int x, y, iFormat; int result = Stbi.info_from_memory(ptr, span.Length * sizeof(T), &x, &y, &iFormat); width = x; height = y; format = (StbiImageFormat)iFormat; return result != 0; } } } }