diff --git a/src/NetVips/Internal/Vips.cs b/src/NetVips/Internal/Vips.cs
index d80a3d0f..7d1956cd 100644
--- a/src/NetVips/Internal/Vips.cs
+++ b/src/NetVips/Internal/Vips.cs
@@ -289,6 +289,10 @@ internal static class VipsBlob
[SuppressUnmanagedCodeSecurity]
[DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl, EntryPoint = "vips_blob_get")]
internal static extern nint Get(VipsBlobManaged blob, out nuint length);
+
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl, EntryPoint = "vips_blob_copy")]
+ internal static extern unsafe nint Copy(void* data, nuint length);
}
internal static class VipsArea
@@ -416,12 +420,24 @@ internal static class VipsImage
internal static extern nint NewFromMemory(nint data, nuint size, int width, int height,
int bands, BandFormat format);
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
+ EntryPoint = "vips_image_new_from_memory")]
+ internal static extern unsafe nint NewFromMemory(void* data, nuint size, int width, int height,
+ int bands, BandFormat format);
+
[SuppressUnmanagedCodeSecurity]
[DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
EntryPoint = "vips_image_new_from_memory_copy")]
internal static extern nint NewFromMemoryCopy(nint data, nuint size, int width, int height,
int bands, BandFormat format);
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
+ EntryPoint = "vips_image_new_from_memory_copy")]
+ internal static extern unsafe nint NewFromMemoryCopy(void* data, nuint size, int width, int height,
+ int bands, BandFormat format);
+
[SuppressUnmanagedCodeSecurity]
[DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
EntryPoint = "vips_image_new_matrix_from_array")]
@@ -567,6 +583,11 @@ internal static class VipsForeign
EntryPoint = "vips_foreign_find_load_buffer")]
internal static extern nint FindLoadBuffer(nint data, ulong size);
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
+ EntryPoint = "vips_foreign_find_load_buffer")]
+ internal static extern unsafe nint FindLoadBuffer(void* data, ulong size);
+
[SuppressUnmanagedCodeSecurity]
[DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
EntryPoint = "vips_foreign_find_load_source")]
@@ -617,6 +638,11 @@ internal static class VipsSource
EntryPoint = "vips_source_new_from_file")]
internal static extern nint NewFromFile(byte[] filename);
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
+ EntryPoint = "vips_source_new_from_blob")]
+ internal static extern nint NewFromBlob(VipsBlobManaged blob);
+
[SuppressUnmanagedCodeSecurity]
[DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
EntryPoint = "vips_source_new_from_memory")]
diff --git a/src/NetVips/NetVips.csproj b/src/NetVips/NetVips.csproj
index d2c763e9..1a46cd08 100644
--- a/src/NetVips/NetVips.csproj
+++ b/src/NetVips/NetVips.csproj
@@ -11,6 +11,7 @@
x64;x86;ARM64;ARM32
true
+ true
false
true
true
diff --git a/src/NetVips/Source.cs b/src/NetVips/Source.cs
index f5aaf643..ac6d5388 100644
--- a/src/NetVips/Source.cs
+++ b/src/NetVips/Source.cs
@@ -7,7 +7,7 @@ namespace NetVips;
///
/// An input connection.
///
-public class Source : Connection
+public partial class Source : Connection
{
///
/// Secret ref for .
diff --git a/src/NetVips/net6.0/Image.cs b/src/NetVips/net6.0/Image.cs
new file mode 100644
index 00000000..6cfded9b
--- /dev/null
+++ b/src/NetVips/net6.0/Image.cs
@@ -0,0 +1,171 @@
+#if NET6_0_OR_GREATER
+
+using System;
+using System.Runtime.InteropServices;
+using NetVips.Internal;
+
+namespace NetVips;
+
+///
+/// Wrap a object.
+///
+public partial class Image
+{
+ #region helpers
+
+ ///
+ /// Find the name of the load operation vips will use to load a buffer.
+ ///
+ ///
+ /// For example "VipsForeignLoadJpegBuffer". You can use this to work out what
+ /// options to pass to .
+ ///
+ /// The buffer to test.
+ /// Length of the buffer.
+ /// The name of the load operation, or .
+ private static unsafe string FindLoadBuffer(void* data, ulong size) =>
+ Marshal.PtrToStringAnsi(VipsForeign.FindLoadBuffer(data, size));
+
+ ///
+ /// Find the name of the load operation vips will use to load a buffer.
+ ///
+ ///
+ /// For example "VipsForeignLoadJpegBuffer". You can use this to work out what
+ /// options to pass to .
+ ///
+ /// The buffer to test.
+ /// The name of the load operation, or .
+ public static unsafe string FindLoadBuffer(ReadOnlySpan data)
+ {
+ fixed (byte* dataFixed = data)
+ {
+ return FindLoadBuffer(dataFixed, (ulong)data.Length);
+ }
+ }
+
+ #endregion
+
+ #region constructors
+
+ ///
+ /// Load a formatted image from memory.
+ ///
+ ///
+ /// This behaves exactly as , but the image is
+ /// loaded from the memory object rather than from a file. The memory
+ /// object can be a string or buffer.
+ ///
+ /// The memory object to load the image from.
+ /// Load options as a string. Use for no options.
+ /// Hint the expected access pattern for the image.
+ /// The type of error that will cause load to fail. By
+ /// default, loaders are permissive, that is, .
+ /// Optional options that depend on the load operation.
+ /// A new .
+ /// If unable to load from .
+ public static unsafe Image NewFromBuffer(
+ ReadOnlySpan data,
+ string strOptions = "",
+ Enums.Access? access = null,
+ Enums.FailOn? failOn = null,
+ VOption kwargs = null)
+ {
+ fixed (byte* dataFixed = data)
+ {
+ var operationName = FindLoadBuffer(dataFixed, (ulong)data.Length);
+ if (operationName == null)
+ {
+ throw new VipsException("unable to load from buffer");
+ }
+
+ var options = new VOption();
+ if (kwargs != null)
+ {
+ options.Merge(kwargs);
+ }
+
+ options.AddIfPresent(nameof(access), access);
+ options.AddFailOn(failOn);
+
+ options.Add("string_options", strOptions);
+
+ var ptr = Internal.VipsBlob.Copy(dataFixed, (nuint)data.Length);
+ if (ptr == IntPtr.Zero)
+ {
+ throw new VipsException("unable to load from buffer");
+ }
+
+ using var blob = new VipsBlob(ptr);
+ return Operation.Call(operationName, options, blob) as Image;
+ }
+ }
+
+ ///
+ /// Wrap an image around a memory array.
+ ///
+ /// A .
+ /// Image width in pixels.
+ /// Image height in pixels.
+ /// Number of bands.
+ /// Band format.
+ /// A new .
+ /// If unable to make image from .
+ public static unsafe Image NewFromMemory(
+ ReadOnlyMemory data,
+ int width,
+ int height,
+ int bands,
+ Enums.BandFormat format) where T : unmanaged
+ {
+ var handle = data.Pin();
+ var vi = VipsImage.NewFromMemory(handle.Pointer, (nuint)data.Length, width, height, bands,
+ format);
+ if (vi == IntPtr.Zero)
+ {
+ handle.Dispose();
+
+ throw new VipsException("unable to make image from memory");
+ }
+
+ var image = new Image(vi) { MemoryPressure = data.Length };
+
+ // Need to release the pinned MemoryHandle when the image is closed.
+ image.OnPostClose += () => handle.Dispose();
+
+ return image;
+ }
+
+ ///
+ /// Like , but
+ /// for , so we must copy as it could be allocated on the stack.
+ ///
+ /// A .
+ /// Image width in pixels.
+ /// Image height in pixels.
+ /// Number of bands.
+ /// Band format.
+ /// A new .
+ /// If unable to make image from .
+ public static unsafe Image NewFromMemoryCopy(
+ ReadOnlySpan data,
+ int width,
+ int height,
+ int bands,
+ Enums.BandFormat format) where T : unmanaged
+ {
+ fixed (T* dataFixed = data)
+ {
+ var vi = VipsImage.NewFromMemoryCopy(dataFixed, (nuint)data.Length, width, height, bands, format);
+ if (vi == IntPtr.Zero)
+ {
+ throw new VipsException("unable to make image from memory");
+ }
+
+ return new Image(vi) { MemoryPressure = data.Length };
+ }
+ }
+
+ #endregion
+}
+
+#endif
\ No newline at end of file
diff --git a/src/NetVips/net6.0/Source.cs b/src/NetVips/net6.0/Source.cs
new file mode 100644
index 00000000..2beb512c
--- /dev/null
+++ b/src/NetVips/net6.0/Source.cs
@@ -0,0 +1,47 @@
+#if NET6_0_OR_GREATER
+
+using System;
+
+namespace NetVips;
+
+///
+/// An input connection.
+///
+public partial class Source
+{
+ ///
+ /// Make a new source from a memory object.
+ ///
+ ///
+ /// Make a new source that is attached to the memory object. For example:
+ ///
+ /// using var source = Source.NewFromMemory(data);
+ ///
+ /// You can pass this source to (for example) .
+ ///
+ /// The memory object.
+ /// A new .
+ /// If unable to create a new from .
+ public static unsafe Source NewFromMemory(ReadOnlySpan data)
+ {
+ fixed (byte* dataFixed = data)
+ {
+ var ptr = Internal.VipsBlob.Copy(dataFixed, (nuint)data.Length);
+ if (ptr == IntPtr.Zero)
+ {
+ throw new VipsException("can't create input source from memory");
+ }
+
+ using var blob = new VipsBlob(ptr);
+ var pointer = Internal.VipsSource.NewFromBlob(blob);
+ if (pointer == IntPtr.Zero)
+ {
+ throw new VipsException("can't create input source from memory");
+ }
+
+ return new Source(pointer);
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/tests/NetVips.Tests/ConnectionTests.cs b/tests/NetVips.Tests/ConnectionTests.cs
index 800329a7..5befc9a3 100644
--- a/tests/NetVips.Tests/ConnectionTests.cs
+++ b/tests/NetVips.Tests/ConnectionTests.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Text;
using Xunit;
using Xunit.Abstractions;
@@ -49,6 +50,22 @@ public void TestConnection()
Assert.True((image - image2).Abs().Max() < 10);
}
+ [SkippableFact]
+ public void TestSourceNewFromMemorySpan()
+ {
+ Skip.IfNot(Helper.Have("svgload_source"), "no svg source support, skipping test");
+
+ ReadOnlySpan input =
+ Encoding.UTF8.GetBytes("");
+ var source = Source.NewFromMemory(input);
+ var image = Image.NewFromSource(source, access: Enums.Access.Sequential);
+ var image2 = Image.NewFromBuffer(input, access: Enums.Access.Sequential);
+
+ Assert.Equal(0, (image - image2).Abs().Max());
+ Assert.Equal(200, image.Width);
+ Assert.Equal(200, image.Height);
+ }
+
[SkippableFact]
public void TestSourceCustomNoSeek()
{
diff --git a/tests/NetVips.Tests/IoFuncsTests.cs b/tests/NetVips.Tests/IoFuncsTests.cs
index aa1ee859..19b99f23 100644
--- a/tests/NetVips.Tests/IoFuncsTests.cs
+++ b/tests/NetVips.Tests/IoFuncsTests.cs
@@ -143,6 +143,36 @@ public void TestNewFromMemoryPtr()
Cache.Max = prevMax;
}
+ [Fact]
+ public void TestNewFromMemoryReadOnly()
+ {
+ ReadOnlyMemory s = new byte[200];
+ var im = Image.NewFromMemory(s, 20, 10, 1, Enums.BandFormat.Uchar);
+ Assert.Equal(20, im.Width);
+ Assert.Equal(10, im.Height);
+ Assert.Equal(Enums.BandFormat.Uchar, im.Format);
+ Assert.Equal(1, im.Bands);
+ Assert.Equal(0, im.Avg());
+
+ im += 10;
+ Assert.Equal(10, im.Avg());
+ }
+
+ [Fact]
+ public void TestNewFromMemoryCopySpan()
+ {
+ ReadOnlySpan s = stackalloc byte[200];
+ var im = Image.NewFromMemoryCopy(s, 20, 10, 1, Enums.BandFormat.Uchar);
+ Assert.Equal(20, im.Width);
+ Assert.Equal(10, im.Height);
+ Assert.Equal(Enums.BandFormat.Uchar, im.Format);
+ Assert.Equal(1, im.Bands);
+ Assert.Equal(0, im.Avg());
+
+ im += 10;
+ Assert.Equal(10, im.Avg());
+ }
+
[SkippableFact]
public void TestGetFields()
{