diff --git a/src/NetVips/Internal/Vips.cs b/src/NetVips/Internal/Vips.cs
index 10740bcf..a57b3a7f 100644
--- a/src/NetVips/Internal/Vips.cs
+++ b/src/NetVips/Internal/Vips.cs
@@ -295,6 +295,10 @@ internal static class VipsBlob
[SuppressUnmanagedCodeSecurity]
[DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl, EntryPoint = "vips_blob_get")]
internal static extern IntPtr Get(VipsBlobManaged blob, out UIntPtr length);
+
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl, EntryPoint = "vips_blob_copy")]
+ internal static extern unsafe IntPtr Copy(void* data, UIntPtr length);
}
internal static class VipsArea
@@ -422,12 +426,24 @@ internal static class VipsImage
internal static extern IntPtr NewFromMemory(IntPtr data, UIntPtr 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 IntPtr NewFromMemory(void* data, UIntPtr 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 IntPtr NewFromMemoryCopy(IntPtr data, UIntPtr 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 IntPtr NewFromMemoryCopy(void* data, UIntPtr size, int width, int height,
+ int bands, BandFormat format);
+
[SuppressUnmanagedCodeSecurity]
[DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
EntryPoint = "vips_image_new_matrix_from_array")]
@@ -582,6 +598,11 @@ internal static class VipsForeign
EntryPoint = "vips_foreign_find_load_buffer")]
internal static extern IntPtr FindLoadBuffer(IntPtr data, ulong size);
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
+ EntryPoint = "vips_foreign_find_load_buffer")]
+ internal static extern unsafe IntPtr FindLoadBuffer(void* data, ulong size);
+
[SuppressUnmanagedCodeSecurity]
[DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
EntryPoint = "vips_foreign_find_load_source")]
@@ -637,6 +658,11 @@ internal static class VipsSource
EntryPoint = "vips_source_new_from_file")]
internal static extern IntPtr NewFromFile(byte[] filename);
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(Libraries.Vips, CallingConvention = CallingConvention.Cdecl,
+ EntryPoint = "vips_source_new_from_blob")]
+ internal static extern IntPtr 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 7695020f..c6c94d67 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
{
// private static Logger logger = LogManager.GetCurrentClassLogger();
@@ -96,7 +96,6 @@ public static Source NewFromMemory(byte[] data)
var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
var pointer = Internal.VipsSource.NewFromMemory(handle.AddrOfPinnedObject(), (UIntPtr)data.Length);
-
if (pointer == IntPtr.Zero)
{
if (handle.IsAllocated)
diff --git a/src/NetVips/net6.0/Image.cs b/src/NetVips/net6.0/Image.cs
new file mode 100644
index 00000000..92cd960b
--- /dev/null
+++ b/src/NetVips/net6.0/Image.cs
@@ -0,0 +1,174 @@
+#if NET6_0_OR_GREATER
+
+namespace NetVips
+{
+ using System;
+ using System.Runtime.InteropServices;
+ using Internal;
+
+ ///
+ /// 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 .
+ internal 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, (UIntPtr)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, (UIntPtr)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, (UIntPtr)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..77cf3949
--- /dev/null
+++ b/src/NetVips/net6.0/Source.cs
@@ -0,0 +1,48 @@
+#if NET6_0_OR_GREATER
+
+namespace NetVips
+{
+ using System;
+
+ ///
+ /// 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, (UIntPtr)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 d87a461a..9e8e873d 100644
--- a/tests/NetVips.Tests/ConnectionTests.cs
+++ b/tests/NetVips.Tests/ConnectionTests.cs
@@ -2,6 +2,7 @@ namespace NetVips.Tests
{
using System;
using System.IO;
+ using System.Text;
using Xunit;
using Xunit.Abstractions;
@@ -49,6 +50,23 @@ 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("")
+ .AsSpan();
+ 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/ForeignTests.cs b/tests/NetVips.Tests/ForeignTests.cs
index 0eb14c3e..658574bf 100644
--- a/tests/NetVips.Tests/ForeignTests.cs
+++ b/tests/NetVips.Tests/ForeignTests.cs
@@ -390,7 +390,7 @@ public void TestBufferOverload()
Assert.Equal(_colour.Width, x.Width);
Assert.Equal(_colour.Height, x.Height);
Assert.Equal(_colour.Bands, x.Bands);
- Assert.True((_colour - x).Abs().Max() <= 0);
+ Assert.Equal(0, (_colour - x).Abs().Max());
}
[SkippableFact]
diff --git a/tests/NetVips.Tests/IoFuncsTests.cs b/tests/NetVips.Tests/IoFuncsTests.cs
index a7901f68..4d135892 100644
--- a/tests/NetVips.Tests/IoFuncsTests.cs
+++ b/tests/NetVips.Tests/IoFuncsTests.cs
@@ -107,7 +107,7 @@ public void TestNewFromImage()
[Fact]
public void TestNewFromMemory()
{
- var s = Enumerable.Repeat((byte)0, 200).ToArray();
+ var 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);
@@ -143,6 +143,36 @@ public void TestNewFromMemoryPtr()
Cache.Max = prevMax;
}
+ [Fact]
+ public void TestNewFromMemoryReadOnly()
+ {
+ var s = new ReadOnlyMemory(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()
{