using ReFuel.Stb.Native; using System; using System.IO; using System.Runtime.InteropServices; namespace ReFuel.Stb { /// <summary> /// Pointer to STBI stream read function. /// </summary> /// <param name="userdata">User provided userdata pointer.</param> /// <param name="buffer">C array to read into.</param> /// <param name="count">Size of the C array in bytes.</param> /// <returns>The number of bytes read from the stream.</returns> [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public unsafe delegate int StbiReadProc(void *userdata, byte* buffer, int count); /// <summary> /// Pointer to STBI stream skip function. /// </summary> /// <param name="userdata">User provided userdata pointer.</param> /// <param name="count">Number of bytes to skip.</param> [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public unsafe delegate void StbiSkipProc(void *userdata, int count); /// <summary> /// Pointer to STBI stream end of file function. /// </summary> /// <param name="userdata">User provided userdata pointer.</param> /// <returns>Non-zero value if the end of the stream has been reached.</returns> [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public unsafe delegate int StbiEofProc(void *userdata); /// <summary> /// An easy to use stream wrapper for use with STBI image load functions. /// </summary> public unsafe class StbiStreamWrapper : IDisposable { private readonly stbi_io_callbacks _callbacks; private readonly Stream _stream; private readonly bool _keepOpen; private bool _isDisposed; private StbiReadProc _readCb; private StbiSkipProc _skipCb; private StbiEofProc _eofCb; public ref readonly stbi_io_callbacks Callbacks => ref _callbacks; public StbiStreamWrapper(Stream stream, bool keepOpen = false) { if (stream == null) throw new ArgumentNullException(nameof(stream)); _stream = stream; _keepOpen = keepOpen; _readCb = ReadCb; _skipCb = SkipCb; _eofCb = EofCb; _callbacks = default; _callbacks.read = Marshal.GetFunctionPointerForDelegate<StbiReadProc>(_readCb); _callbacks.skip = Marshal.GetFunctionPointerForDelegate<StbiSkipProc>(_skipCb); _callbacks.eof = Marshal.GetFunctionPointerForDelegate<StbiEofProc>(_eofCb); } public void CreateCallbacks(out stbi_io_callbacks cb) { cb = default; cb.read = Marshal.GetFunctionPointerForDelegate<StbiReadProc>(_readCb); cb.skip = Marshal.GetFunctionPointerForDelegate<StbiSkipProc>(_skipCb); cb.eof = Marshal.GetFunctionPointerForDelegate<StbiEofProc>(_eofCb); } private int ReadCb(void *userdata, byte* buffer, int count) { Span<byte> bytes = new Span<byte>(buffer, count); return _stream.Read(bytes); } private void SkipCb(void *userdata, int count) { _stream.Seek(count, SeekOrigin.Current); } private int EofCb(void *userdata) { if (!_stream.CanRead || _stream.Position == _stream.Length) return 1; return 0; } public void Dispose() { if (_isDisposed) return; if (!_keepOpen) _stream.Dispose(); _isDisposed = true; } } /// <summary> /// An easy to use stream wrapper for STBI image write functions. /// </summary> /// <remarks>Keep object alive for the duration of the write operation.</remarks> public class StbiWriteStreamWrapper { private readonly Stream _stream; private readonly StbiWriteProc _cb; public IntPtr Callback { get; } public StbiWriteStreamWrapper(Stream stream) { _stream = stream; unsafe { _cb = WriteCb; } Callback = Marshal.GetFunctionPointerForDelegate(_cb); } private unsafe void WriteCb(void *context, void *data, int size) { _stream.Write(new ReadOnlySpan<byte>((byte*)data, size)); } public static implicit operator IntPtr(in StbiWriteStreamWrapper wrapper) => wrapper.Callback; } }