using ReFuel.Stb.Native; using System; using System.IO; using System.Runtime.InteropServices; namespace ReFuel.Stb { /// /// Pointer to STBI stream read function. /// /// User provided userdata pointer. /// C array to read into. /// Size of the C array in bytes. /// The number of bytes read from the stream. [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public unsafe delegate int StbiReadProc(void *userdata, byte* buffer, int count); /// /// Pointer to STBI stream skip function. /// /// User provided userdata pointer. /// Number of bytes to skip. [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public unsafe delegate void StbiSkipProc(void *userdata, int count); /// /// Pointer to STBI stream end of file function. /// /// User provided userdata pointer. /// Non-zero value if the end of the stream has been reached. [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public unsafe delegate int StbiEofProc(void *userdata); /// /// An easy-to-use stream wrapper for use with STBI image load functions. /// public unsafe class StbiStreamWrapper : IDisposable { public 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 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(_readCb); Callbacks.skip = Marshal.GetFunctionPointerForDelegate(_skipCb); Callbacks.eof = Marshal.GetFunctionPointerForDelegate(_eofCb); } private int ReadCb(void *userdata, byte* buffer, int count) { Span bytes = new Span(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; } } /// /// An easy to use stream wrapper for STBI image write functions. /// /// Keep object alive for the duration of the write operation. 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*)data, size)); } public static implicit operator IntPtr(in StbiWriteStreamWrapper wrapper) => wrapper.Callback; } }