using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Threading; using NetVips.Internal; namespace NetVips; /// /// Wrap a object. /// public partial class Image : VipsObject { /// /// A evaluation delegate that can be used on the /// , and /// signals. /// /// /// Use to enable progress reporting on an image. /// public delegate void EvalDelegate(Image image, VipsProgress progressStruct); /// /// Internal marshaller delegate for . /// [SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void EvalMarshalDelegate(nint imagePtr, nint progressPtr, nint userDataPtr); /// internal Image(nint pointer) : base(pointer) { } #region helpers /// /// Run a complex function on a non-complex image. /// /// /// The image needs to be complex, or have an even number of bands. The input /// can be int, the output is always float or double. /// /// A complex function. /// A non-complex image. /// A new . /// If image doesn't have an even number of bands. private static Image RunCmplx(Func func, Image image) { var originalFormat = image.Format; if (image.Format != Enums.BandFormat.Complex && image.Format != Enums.BandFormat.Dpcomplex) { if (image.Bands % 2 != 0) { throw new ArgumentException("not an even number of bands"); } if (image.Format != Enums.BandFormat.Float && image.Format != Enums.BandFormat.Double) { using (image) { image = image.Cast(Enums.BandFormat.Float); } } var newFormat = image.Format == Enums.BandFormat.Double ? Enums.BandFormat.Dpcomplex : Enums.BandFormat.Complex; using (image) { image = image.Copy(format: newFormat, bands: image.Bands / 2); } } using (image) { image = func(image); } if (originalFormat != Enums.BandFormat.Complex && originalFormat != Enums.BandFormat.Dpcomplex) { var newFormat = image.Format == Enums.BandFormat.Dpcomplex ? Enums.BandFormat.Double : Enums.BandFormat.Float; using (image) { image = image.Copy(format: newFormat, bands: image.Bands * 2); } } return image; } /// /// Turn a constant (eg. 1, "12", new[] {1, 2, 3}, new[] {new[] {1}}) into an image using /// as a guide. /// /// Image guide. /// A constant. /// A new . public static Image Imageize(Image matchImage, object value) { // careful! this can be None if value is a 2D array return value switch { Image image => image, double[,] doubleArray => NewFromArray(doubleArray), int[,] intArray => NewFromArray(intArray), double[] doubles => matchImage.NewFromImage(doubles), int[] ints => matchImage.NewFromImage(ints), double doubleValue => matchImage.NewFromImage(doubleValue), int intValue => matchImage.NewFromImage(intValue), _ => throw new ArgumentException($"unsupported value type {value.GetType()} for Imageize") }; } /// /// Find the name of the load operation vips will use to load a file. /// /// /// For example "VipsForeignLoadJpegFile". You can use this to work out what /// options to pass to . /// /// The file to test. /// The name of the load operation, or . public static string FindLoad(string filename) { var bytes = Encoding.UTF8.GetBytes(filename + char.MinValue); // Ensure null-terminated string return Marshal.PtrToStringAnsi(VipsForeign.FindLoad(bytes)); } /// /// 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 string FindLoadBuffer(byte[] data) => Marshal.PtrToStringAnsi(VipsForeign.FindLoadBuffer(data, (ulong)data.Length)); /// /// 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 string FindLoadBuffer(string data) => FindLoadBuffer(Encoding.UTF8.GetBytes(data)); /// /// 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 string FindLoadBuffer(char[] data) => FindLoadBuffer(Encoding.UTF8.GetBytes(data)); /// /// Find the name of the load operation vips will use to load a source. /// /// /// For example "VipsForeignLoadJpegSource". You can use this to work out what /// options to pass to . /// /// The source to test. /// The name of the load operation, or . public static string FindLoadSource(Source source) => Marshal.PtrToStringAnsi(VipsForeign.FindLoadSource(source)); /// /// Find the name of the load operation vips will use to load a stream. /// /// /// For example "VipsForeignLoadJpegSource". You can use this to work out what /// options to pass to . /// /// The stream to test. /// The name of the load operation, or . public static string FindLoadStream(Stream stream) { using var source = SourceStream.NewFromStream(stream); return FindLoadSource(source); } #endregion #region constructors /// /// Load an image from a file. /// /// /// This method can load images in any format supported by vips. The /// filename can include load options, for example: /// /// using var image = Image.NewFromFile("fred.jpg[shrink=2]"); /// /// You can also supply options as keyword arguments, for example: /// /// using var image = Image.NewFromFile("fred.jpg", new VOption /// { /// {"shrink", 2} /// }); /// /// The full set of options available depend upon the load operation that /// will be executed. Try something like: /// /// $ vips jpegload /// /// at the command-line to see a summary of the available options for the /// JPEG loader. /// /// Loading is fast: only enough of the image is loaded to be able to fill /// out the header. Pixels will only be decompressed when they are needed. /// /// The disc file to load the image from, with /// optional appended arguments. /// If set to , load the image /// via memory rather than via a temporary disc file. See /// for notes on where temporary files are created. Small images are loaded via memory /// by default, use `VIPS_DISC_THRESHOLD` to set the definition of small. /// 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, . /// Don't use a cached result for this operation. /// Optional options that depend on the load operation. /// A new . /// If unable to load from . public static Image NewFromFile( string vipsFilename, bool? memory = null, Enums.Access? access = null, Enums.FailOn? failOn = null, bool? revalidate = null, VOption kwargs = null) { var bytes = Encoding.UTF8.GetBytes(vipsFilename + char.MinValue); // Ensure null-terminated string var filename = Vips.GetFilename(bytes); var fileOptions = Vips.GetOptions(bytes).ToUtf8String(true); var operationName = Marshal.PtrToStringAnsi(VipsForeign.FindLoad(filename)); if (operationName == null) { throw new VipsException($"unable to load from file {vipsFilename}"); } var options = new VOption(); if (kwargs != null) { options.Merge(kwargs); } options.AddIfPresent(nameof(memory), memory); options.AddIfPresent(nameof(access), access); options.AddFailOn(failOn); options.AddIfPresent(nameof(revalidate), revalidate); options.Add("string_options", fileOptions); return Operation.Call(operationName, options, filename.ToUtf8String(true)) as Image; } /// /// 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 Image NewFromBuffer( byte[] data, string strOptions = "", Enums.Access? access = null, Enums.FailOn? failOn = null, VOption kwargs = null) { var operationName = FindLoadBuffer(data); 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); return Operation.Call(operationName, options, data) as Image; } /// /// 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 Image NewFromBuffer( string data, string strOptions = "", Enums.Access? access = null, Enums.FailOn? failOn = null, VOption kwargs = null) => NewFromBuffer(Encoding.UTF8.GetBytes(data), strOptions, access, failOn, kwargs); /// /// 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 Image NewFromBuffer( char[] data, string strOptions = "", Enums.Access? access = null, Enums.FailOn? failOn = null, VOption kwargs = null) => NewFromBuffer(Encoding.UTF8.GetBytes(data), strOptions, access, failOn, kwargs); /// /// Load a formatted image from a source. /// /// /// This behaves exactly as , but the image is /// loaded from a source rather than from a file. /// At least libvips 8.9 is needed. /// /// The source 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 Image NewFromSource( Source source, string strOptions = "", Enums.Access? access = null, Enums.FailOn? failOn = null, VOption kwargs = null) { // Load with the new source API if we can. Fall back to the older // mechanism in case the loader we need has not been converted yet. // We need to hide any errors from this first phase. Vips.ErrorFreeze(); var operationName = FindLoadSource(source); Vips.ErrorThaw(); var options = new VOption(); if (kwargs != null) { options.Merge(kwargs); } options.AddIfPresent(nameof(access), access); options.AddFailOn(failOn); options.Add("string_options", strOptions); if (operationName != null) { return Operation.Call(operationName, options, source) as Image; } #region fallback mechanism var filename = VipsConnection.FileName(source); if (filename != IntPtr.Zero) { // Try with the old file-based loaders. operationName = Marshal.PtrToStringAnsi(VipsForeign.FindLoad(filename)); if (operationName == null) { throw new VipsException("unable to load from source"); } return Operation.Call(operationName, options, filename.ToUtf8String()) as Image; } // Try with the old buffer-based loaders. // TODO: // Do we need to check if the source can be efficiently mapped into // memory with `vips_source_is_mappable`? // This implicitly means that it will not work with network streams // (`is_pipe` streams). var ptr = VipsSource.MapBlob(source); if (ptr == IntPtr.Zero) { throw new VipsException("unable to load from source"); } using var blob = new VipsBlob(ptr); var buf = blob.GetData(out var length); operationName = Marshal.PtrToStringAnsi(VipsForeign.FindLoadBuffer(buf, length)); if (operationName == null) { throw new VipsException("unable to load from source"); } return Operation.Call(operationName, options, blob) as Image; #endregion } /// /// Load a formatted image from a stream. /// /// /// This behaves exactly as , but the image is /// loaded from a stream rather than from a source. /// At least libvips 8.9 is needed. /// /// The stream 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 Image NewFromStream( Stream stream, string strOptions = "", Enums.Access? access = null, Enums.FailOn? failOn = null, VOption kwargs = null) { var source = SourceStream.NewFromStream(stream); var image = NewFromSource(source, strOptions, access, failOn, kwargs); // Need to dispose the SourceStream when the image is closed. image.OnPostClose += () => source.Dispose(); return image; } /// /// Create an image from a 2D array. /// /// /// A new one-band image with pixels is /// created from the array. These images are useful with the libvips /// convolution operator . /// /// Create the image from these values. /// Default to 1.0. What to divide each pixel by after /// convolution. Useful for integer convolution masks. /// Default to 0.0. What to subtract from each pixel /// after convolution. Useful for integer convolution masks. /// A new . /// If unable to make image from . public static Image NewFromArray(T[,] array, double scale = 1.0, double offset = 0.0) where T : struct, IEquatable { var height = array.GetLength(0); var width = array.GetLength(1); var n = width * height; var a = new double[n]; for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { ref var value = ref a[x + y * width]; value = Convert.ToDouble(array[y, x]); } } var vi = VipsImage.NewMatrixFromArray(width, height, a, n); if (vi == IntPtr.Zero) { throw new VipsException("unable to make image from matrix"); } using var image = new Image(vi); return image.Mutate(mutable => { // be careful to set them as double mutable.Set(GValue.GDoubleType, nameof(scale), scale); mutable.Set(GValue.GDoubleType, nameof(offset), offset); }); } /// /// Create an image from a 2D array. /// /// /// A new one-band image with pixels is /// created from the array. These images are useful with the libvips /// convolution operator . /// /// Create the image from these values. /// Default to 1.0. What to divide each pixel by after /// convolution. Useful for integer convolution masks. /// Default to 0.0. What to subtract from each pixel /// after convolution. Useful for integer convolution masks. /// A new . /// If unable to make image from . public static Image NewFromArray(T[][] array, double scale = 1.0, double offset = 0.0) where T : struct, IEquatable { var height = array.Length; var width = array[0].Length; var n = width * height; var a = new double[n]; for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { ref var value = ref a[x + y * width]; value = Convert.ToDouble(array[y][x]); } } var vi = VipsImage.NewMatrixFromArray(width, height, a, n); if (vi == IntPtr.Zero) { throw new VipsException("unable to make image from matrix"); } using var image = new Image(vi); return image.Mutate(mutable => { // be careful to set them as double mutable.Set(GValue.GDoubleType, nameof(scale), scale); mutable.Set(GValue.GDoubleType, nameof(offset), offset); }); } /// /// Create an image from a 1D array. /// /// /// A new one-band image with pixels is /// created from the array. These images are useful with the libvips /// convolution operator . /// /// Create the image from these values. /// 1D arrays become a single row of pixels. /// Default to 1.0. What to divide each pixel by after /// convolution. Useful for integer convolution masks. /// Default to 0.0. What to subtract from each pixel /// after convolution. Useful for integer convolution masks. /// A new . /// If unable to make image from . public static Image NewFromArray(T[] array, double scale = 1.0, double offset = 0.0) where T : struct, IEquatable { var height = array.Length; var a = new double[height]; for (var y = 0; y < height; y++) { ref var value = ref a[y]; value = Convert.ToDouble(array[y]); } var vi = VipsImage.NewMatrixFromArray(1, height, a, height); if (vi == IntPtr.Zero) { throw new VipsException("unable to make image from matrix"); } using var image = new Image(vi); return image.Mutate(mutable => { // be careful to set them as double mutable.Set(GValue.GDoubleType, nameof(scale), scale); mutable.Set(GValue.GDoubleType, nameof(offset), offset); }); } /// /// Wrap an image around a memory array. /// /// /// Wraps an image around a C-style memory array. For example, if the /// memory array contains four bytes with the /// values 1, 2, 3, 4, you can make a one-band, 2x2 uchar image from /// it like this: /// /// using var image = Image.NewFromMemory(data, 2, 2, 1, Enums.BandFormat.Uchar); /// /// A reference is kept to the data object, so it will not be /// garbage-collected until the returned image is garbage-collected. /// /// This method is useful for efficiently transferring images from GDI+ /// into libvips. /// /// See for the opposite operation. /// /// Use to set other image attributes. /// /// A memory object. /// Image width in pixels. /// Image height in pixels. /// Number of bands. /// Band format. /// A new . /// If unable to make image from . public static Image NewFromMemory( Array data, int width, int height, int bands, Enums.BandFormat format) { var handle = GCHandle.Alloc(data, GCHandleType.Pinned); var vi = VipsImage.NewFromMemory(handle.AddrOfPinnedObject(), (nuint)data.Length, width, height, bands, format); if (vi == IntPtr.Zero) { if (handle.IsAllocated) { handle.Free(); } throw new VipsException("unable to make image from memory"); } var image = new Image(vi); // Need to release the pinned GCHandle when the image is closed. image.OnPostClose += () => { if (handle.IsAllocated) { handle.Free(); } }; return image; } /// /// Wrap an image around a memory area. /// /// /// Because libvips is "borrowing" from the caller, this function /// is extremely dangerous. Unless you are very careful, you will get crashes or memory /// corruption. Use instead if you are at all unsure. /// /// A unmanaged block of memory. /// Length of memory. /// Image width in pixels. /// Image height in pixels. /// Number of bands. /// Band format. /// A new . /// If unable to make image from . public static Image NewFromMemory( nint data, ulong size, int width, int height, int bands, Enums.BandFormat format) { var vi = VipsImage.NewFromMemory(data, (nuint)size, width, height, bands, format); if (vi == IntPtr.Zero) { throw new VipsException("unable to make image from memory"); } return new Image(vi) { MemoryPressure = (long)size }; } /// /// Like , but libvips /// will make a copy of the memory area. This means more memory use and an extra copy /// operation, but is much simpler and safer. /// /// A unmanaged block of memory. /// Length of memory. /// Image width in pixels. /// Image height in pixels. /// Number of bands. /// Band format. /// A new . /// If unable to make image from . public static Image NewFromMemoryCopy( nint data, ulong size, int width, int height, int bands, Enums.BandFormat format) { var vi = VipsImage.NewFromMemoryCopy(data, (nuint)size, width, height, bands, format); if (vi == IntPtr.Zero) { throw new VipsException("unable to make image from memory"); } return new Image(vi) { MemoryPressure = (long)size }; } /// /// Make a new temporary image. /// /// /// Returns an image backed by a temporary file. When written to with /// , a temporary file will be created on disc in the /// specified format. When the image is closed, the file will be deleted /// automatically. /// /// The file is created in the temporary directory. This is set with /// the environment variable `TMPDIR`. If this is not set, then on /// Unix systems, vips will default to `/tmp`. On Windows, vips uses /// `GetTempPath()` to find the temporary directory. /// /// vips uses `g_mkstemp()` to make the temporary filename. They /// generally look something like `vips-12-EJKJFGH.v`. /// /// The format for the temp file, for example /// `%s.v` for a vips format file. The `%s` is /// substituted by the file path. /// A new . /// If unable to make temp file from . public static Image NewTempFile(string format) { var vi = VipsImage.NewTempFile(Encoding.UTF8.GetBytes(format)); if (vi == IntPtr.Zero) { throw new VipsException("unable to make temp file"); } return new Image(vi); } /// /// Make a new image from an existing one. /// /// /// A new image is created which has the same size, format, interpretation /// and resolution as `this`, but with every pixel set to `value`. /// /// The value for the pixels. Use a /// single number to make a one-band image; use an array constant /// to make a many-band image. /// A new . public Image NewFromImage(Image value) { using var black = Black(1, 1); using var pixel = black + value; using var cast = pixel.Cast(Format); using var image = cast.Embed(0, 0, Width, Height, extend: Enums.Extend.Copy); return image.Copy(interpretation: Interpretation, xres: Xres, yres: Yres, xoffset: Xoffset, yoffset: Yoffset); } /// /// Make a new image from an existing one. /// /// /// A new image is created which has the same size, format, interpretation /// and resolution as `this`, but with every pixel set to `value`. /// /// The value for the pixels. Use a /// single number to make a one-band image; use an array constant /// to make a many-band image. /// A new . public Image NewFromImage(params double[] doubles) { using var black = Black(1, 1); using var pixel = black + doubles; using var cast = pixel.Cast(Format); using var image = cast.Embed(0, 0, Width, Height, extend: Enums.Extend.Copy); return image.Copy(interpretation: Interpretation, xres: Xres, yres: Yres, xoffset: Xoffset, yoffset: Yoffset); } /// /// Make a new image from an existing one. /// /// /// A new image is created which has the same size, format, interpretation /// and resolution as `this`, but with every pixel set to `value`. /// /// The value for the pixels. Use a /// single number to make a one-band image; use an array constant /// to make a many-band image. /// A new . public Image NewFromImage(params int[] ints) => NewFromImage(Array.ConvertAll(ints, Convert.ToDouble)); /// /// Copy an image to memory. /// /// /// A large area of memory is allocated, the image is rendered to that /// memory area, and a new image is returned which wraps that large memory /// area. /// /// A new . /// If unable to copy to memory. public Image CopyMemory() { var vi = VipsImage.CopyMemory(this); if (vi == IntPtr.Zero) { throw new VipsException("unable to copy to memory"); } return new Image(vi) { MemoryPressure = MemoryPressure }; } #endregion #region writers /// /// Write an image to a file on disc. /// /// /// This method can save images in any format supported by vips. The format /// is selected from the filename suffix. The filename can include embedded /// save options, see . /// /// For example: /// /// image.WriteToFile("fred.jpg[Q=95]"); /// /// You can also supply options as keyword arguments, for example: /// /// image.WriteToFile("fred.jpg", new VOption /// { /// {"Q", 95} /// }); /// /// The full set of options available depend upon the save operation that /// will be executed. Try something like: /// /// $ vips jpegsave /// /// at the command-line to see a summary of the available options for the /// JPEG saver. /// /// The disc file to save the image to, with /// optional appended arguments. /// Optional options that depend on the save operation. /// If unable to write to . public void WriteToFile(string vipsFilename, VOption kwargs = null) { var bytes = Encoding.UTF8.GetBytes(vipsFilename + char.MinValue); // Ensure null-terminated string var filename = Vips.GetFilename(bytes); var fileOptions = Vips.GetOptions(bytes).ToUtf8String(true); var operationName = Marshal.PtrToStringAnsi(VipsForeign.FindSave(filename)); if (operationName == null) { throw new VipsException($"unable to write to file {vipsFilename}"); } var stringOptions = new VOption { {"string_options", fileOptions} }; if (kwargs != null) { kwargs.Merge(stringOptions); } else { kwargs = stringOptions; } this.Call(operationName, kwargs, filename.ToUtf8String(true)); } /// /// Write an image to a formatted string. /// /// /// This method can save images in any format supported by vips. The format /// is selected from the suffix in the format string. This can include /// embedded save options, see . /// /// For example: /// /// var data = image.WriteToBuffer(".jpg[Q=95]"); /// /// You can also supply options as keyword arguments, for example: /// /// var data = image.WriteToBuffer(".jpg", new VOption /// { /// {"Q", 95} /// }); /// /// The full set of options available depend upon the load operation that /// will be executed. Try something like: /// /// $ vips jpegsave_buffer /// /// at the command-line to see a summary of the available options for the /// JPEG saver. /// /// The suffix, plus any string-form arguments. /// Optional options that depend on the save operation. /// An array of bytes. /// If unable to write to buffer. public byte[] WriteToBuffer(string formatString, VOption kwargs = null) { var bytes = Encoding.UTF8.GetBytes(formatString + char.MinValue); // Ensure null-terminated string var bufferOptions = Vips.GetOptions(bytes).ToUtf8String(true); string operationName = null; // Save with the new target API if we can. Fall back to the older // mechanism in case the saver we need has not been converted yet. // We need to hide any errors from this first phase. if (NetVips.AtLeastLibvips(8, 9)) { Vips.ErrorFreeze(); operationName = Marshal.PtrToStringAnsi(VipsForeign.FindSaveTarget(bytes)); Vips.ErrorThaw(); } var stringOptions = new VOption { {"string_options", bufferOptions} }; if (kwargs != null) { kwargs.Merge(stringOptions); } else { kwargs = stringOptions; } if (operationName != null) { using var target = Target.NewToMemory(); this.Call(operationName, kwargs, target); return target.Blob; } #region fallback mechanism operationName = Marshal.PtrToStringAnsi(VipsForeign.FindSaveBuffer(bytes)); if (operationName == null) { throw new VipsException("unable to write to buffer"); } return this.Call(operationName, kwargs) as byte[]; #endregion } /// /// Write an image to a target. /// /// /// This behaves exactly as , but the image is /// written to a target rather than a file. /// At least libvips 8.9 is needed. /// /// Write to this target. /// The suffix, plus any string-form arguments. /// Optional options that depend on the save operation. /// If unable to write to target. public void WriteToTarget(Target target, string formatString, VOption kwargs = null) { var bytes = Encoding.UTF8.GetBytes(formatString + char.MinValue); // Ensure null-terminated string var bufferOptions = Vips.GetOptions(bytes).ToUtf8String(true); var operationName = Marshal.PtrToStringAnsi(VipsForeign.FindSaveTarget(bytes)); if (operationName == null) { throw new VipsException("unable to write to target"); } var stringOptions = new VOption { {"string_options", bufferOptions} }; if (kwargs != null) { kwargs.Merge(stringOptions); } else { kwargs = stringOptions; } this.Call(operationName, kwargs, target); } /// /// Write an image to a stream. /// /// /// This behaves exactly as , but the image is /// written to a stream rather than a target. /// At least libvips 8.9 is needed. /// /// Write to this stream. /// The suffix, plus any string-form arguments. /// Optional options that depend on the save operation. /// If unable to write to stream. public void WriteToStream(Stream stream, string formatString, VOption kwargs = null) { using var target = TargetStream.NewFromStream(stream); WriteToTarget(target, formatString, kwargs); } /// /// Write the image to memory as a simple, unformatted C-style array. /// /// /// The caller is responsible for freeing this memory with . /// /// Output buffer length. /// A pointing to an unformatted C-style array. /// If unable to write to memory. public nint WriteToMemory(out ulong size) { var pointer = VipsImage.WriteToMemory(this, out size); if (pointer == IntPtr.Zero) { throw new VipsException("unable to write to memory"); } return pointer; } /// /// Write the image to a large memory array. /// /// /// A large area of memory is allocated, the image is rendered to that /// memory array, and the array is returned as a buffer. /// /// For example, if you have a 2x2 uchar image containing the bytes 1, 2, /// 3, 4, read left-to-right, top-to-bottom, then: /// /// var buf = image.WriteToMemory(); /// /// will return a four byte buffer containing the values 1, 2, 3, 4. /// /// An array of bytes. /// If unable to write to memory. public byte[] WriteToMemory() { var pointer = WriteToMemory(out var size); var managedArray = new byte[size]; Marshal.Copy(pointer, managedArray, 0, (int)size); GLib.GFree(pointer); return managedArray; } /// /// Write an image to another image. /// /// /// This function writes `this` to another image. Use something like /// to make an image that can be written to. /// /// The to write to. /// If unable to write to image. public void Write(Image other) { var result = VipsImage.Write(this, other); if (result != 0) { throw new VipsException("unable to write to image"); } } #endregion #region get metadata /// /// Get the GType of an item of metadata. /// /// /// Fetch the GType of a piece of metadata, or if the named /// item does not exist. See . /// /// The name of the piece of metadata to get the type of. /// A new instance of initialized to the GType or /// if the property does not exist. public new nint GetTypeOf(string name) { // on libvips before 8.5, property types must be fetched separately, // since built-in enums were reported as ints if (!NetVips.AtLeastLibvips(8, 5)) { var gtype = base.GetTypeOf(name); if (gtype != IntPtr.Zero) { return gtype; } } return VipsImage.GetTypeof(this, name); } /// /// Check if the underlying image contains an property of metadata. /// /// The name of the piece of metadata to check for. /// if the metadata exits; otherwise, . public bool Contains(string name) { return GetTypeOf(name) != IntPtr.Zero; } /// /// Get an item of metadata. /// /// /// Fetches an item of metadata as a C# value. For example: /// /// var orientation = image.Get("orientation"); /// /// would fetch the image orientation. /// /// The name of the piece of metadata to get. /// The metadata item as a C# value. /// If unable to get . public new object Get(string name) { switch (name) { // scale and offset have default values case "scale" when !Contains("scale"): return 1.0; case "offset" when !Contains("offset"): return 0.0; } // with old libvips, we must fetch properties (as opposed to // metadata) via VipsObject if (!NetVips.AtLeastLibvips(8, 5)) { var gtype = base.GetTypeOf(name); if (gtype != IntPtr.Zero) { return base.Get(name); } } var result = VipsImage.Get(this, name, out var gvCopy); if (result != 0) { throw new VipsException($"unable to get {name}"); } using var gv = new GValue(gvCopy); return gv.Get(); } /// /// Get a list of all the metadata fields on an image. /// /// /// At least libvips 8.5 is needed. /// /// An array of strings or . public string[] GetFields() { if (!NetVips.AtLeastLibvips(8, 5)) { return null; } var ptrArr = VipsImage.GetFields(this); var names = new List(); var count = 0; nint strPtr; while ((strPtr = Marshal.ReadIntPtr(ptrArr, count * IntPtr.Size)) != IntPtr.Zero) { var name = Marshal.PtrToStringAnsi(strPtr); names.Add(name); GLib.GFree(strPtr); ++count; } GLib.GFree(ptrArr); return names.ToArray(); } /// /// Returns a string that represents the current image. /// /// A string that represents the current image. public override string ToString() { return $""; } #endregion #region handwritten functions /// /// Mutate an image with a delegate. Inside the delegate, you can call methods /// which modify the image, such as setting or removing metadata, or /// modifying pixels. /// /// /// /// using var mutated = image.Mutate(mutable => /// { /// for (var i = 0; i <= 100; i++) /// { /// var j = i / 100.0; /// mutable.DrawLine(new[] { 255.0 }, (int)(mutable.Width * j), 0, 0, (int)(mutable.Height * (1 - j))); /// } /// }); /// /// /// A new . public virtual Image Mutate(Action action) { // We take a copy of the regular Image to ensure we have an unshared (unique) object. using var mutable = new MutableImage(Copy()); action.Invoke(mutable); return mutable.Image; } /// /// Scale an image to 0 - 255. /// /// /// This is the libvips `scale` operation, renamed to avoid a clash with /// the `scale` for convolution masks. /// /// /// /// using Image @out = in.Scale(exp: double, log: bool); /// /// /// Exponent for log scale. /// Log scale. /// A new . public Image ScaleImage(double? exp = null, bool? log = null) { var options = new VOption(); options.AddIfPresent(nameof(exp), exp); options.AddIfPresent(nameof(log), log); return this.Call("scale", options) as Image; } /// /// Ifthenelse an image. /// /// /// /// using Image @out = cond.Ifthenelse(in1, in2, blend: bool); /// /// /// Source for TRUE pixels. /// Source for FALSE pixels. /// Blend smoothly between then and else parts. /// A new . public Image Ifthenelse(object in1, object in2, bool? blend = null) { Image matchImage; if (in1 is Image th) { matchImage = th; } else if (in2 is Image el) { matchImage = el; } else { matchImage = this; } using var im1 = in1 is Image ? null : Imageize(matchImage, in1); using var im2 = in2 is Image ? null : Imageize(matchImage, in2); var options = new VOption(); options.AddIfPresent(nameof(blend), blend); return this.Call("ifthenelse", options, im1 ?? in1, im2 ?? in2) as Image; } /// /// Use pixel values to pick cases from an array of constants. /// /// /// /// using Image @out = index.Case(10.5, 20.5); /// /// /// Array of constants. /// A new . public Image Case(params double[] doubles) => this.Call("case", doubles) as Image; /// /// Use pixel values to pick cases from an array of constants. /// /// /// /// using Image @out = index.Case(10, 20); /// /// /// Array of constants. /// A new . public Image Case(params int[] ints) => Case(Array.ConvertAll(ints, Convert.ToDouble)); /// /// Use pixel values to pick cases from an array of images. /// /// /// /// using Image @out = index.Case(images); /// /// /// Array of case images. /// A new . public Image Case(params Image[] images) => this.Call("case", new object[] { images }) as Image; /// /// Use pixel values to pick cases from an a set of mixed images and constants. /// /// /// /// using Image @out = index.Case(image, 10); /// /// /// Array of mixed images and constants. /// A new . public Image Case(params object[] objects) => this.Call("case", new object[] { objects }) as Image; /// /// Append a set of constants bandwise. /// /// /// /// using Image @out = image.Bandjoin(127.5, 255.0); /// /// /// Array of constants. /// A new . public Image Bandjoin(params double[] doubles) => BandjoinConst(doubles); /// /// Append a set of constants bandwise. /// /// /// /// using Image @out = image.Bandjoin(255, 128); /// /// /// Array of constants. /// A new . public Image Bandjoin(params int[] ints) => BandjoinConst(Array.ConvertAll(ints, Convert.ToDouble)); /// /// Append a set of images bandwise. /// /// /// /// using Image @out = image.Bandjoin(image2, image3); /// /// /// Array of images. /// A new . public Image Bandjoin(params Image[] images) => this.Call("bandjoin", new object[] { images.PrependImage(this) }) as Image; /// /// Append a set of mixed images and constants bandwise. /// /// /// /// using Image @out = image.Bandjoin(image2, 255); /// /// /// Array of mixed images and constants. /// A new . public Image Bandjoin(params object[] objects) => this.Call("bandjoin", new object[] { objects.PrependImage(this) }) as Image; /// /// Band-wise rank a set of constants. /// /// /// /// using Image @out = image.Bandrank(other, index: int); /// /// /// Array of constants. /// Select this band element from sorted list. /// A new public Image Bandrank(double[] doubles, int? index = null) { var options = new VOption(); options.AddIfPresent(nameof(index), index); return this.Call("bandrank", options, doubles) as Image; } /// /// Band-wise rank a set of constants. /// /// /// /// using Image @out = image.Bandrank(other, index: int); /// /// /// Array of constants. /// Select this band element from sorted list. /// A new . public Image Bandrank(int[] ints, int? index = null) => Bandrank(Array.ConvertAll(ints, Convert.ToDouble), index); /// /// Band-wise rank a set of images. /// /// /// /// using Image @out = image.Bandrank(other, index: int); /// /// /// Array of input images. /// Select this band element from sorted list. /// A new . public Image Bandrank(Image[] images, int? index = null) { var options = new VOption(); options.AddIfPresent(nameof(index), index); return this.Call("bandrank", options, new object[] { images.PrependImage(this) }) as Image; } /// /// Band-wise rank a image. /// /// /// /// using Image @out = image.Bandrank(other, index: int); /// /// /// Input image. /// Select this band element from sorted list. /// A new . public Image Bandrank(Image other, int? index = null) => Bandrank(new[] { other }, index); /// /// Band-wise rank a set of mixed images and constants. /// /// /// /// using Image @out = image.Bandrank(new object[] { image2, 255 }, index: int); /// /// /// Array of mixed images and constants. /// Select this band element from sorted list. /// A new . public Image Bandrank(object[] objects, int? index = null) { var options = new VOption(); options.AddIfPresent(nameof(index), index); return this.Call("bandrank", options, new object[] { objects.PrependImage(this) }) as Image; } /// /// Blend an array of images with an array of blend modes. /// /// /// /// using Image @out = image.Composite(images, modes, x: int[], y: int[], compositingSpace: Enums.Interpretation, premultiplied: bool); /// /// /// Array of input images. /// Array of VipsBlendMode to join with. /// Array of x coordinates to join at. /// Array of y coordinates to join at. /// Composite images in this colour space. /// Images have premultiplied alpha. /// A new . public Image Composite(Image[] images, Enums.BlendMode[] modes, int[] x = null, int[] y = null, Enums.Interpretation? compositingSpace = null, bool? premultiplied = null) { var options = new VOption(); options.AddIfPresent(nameof(x), x); options.AddIfPresent(nameof(y), y); options.AddIfPresent("compositing_space", compositingSpace); options.AddIfPresent(nameof(premultiplied), premultiplied); return this.Call("composite", options, images.PrependImage(this), modes) as Image; } /// /// A synonym for . /// /// /// /// using Image @out = base.Composite(overlay, mode, x: int, y: int, compositingSpace: Enums.Interpretation, premultiplied: bool); /// /// /// Overlay image. /// VipsBlendMode to join with. /// x position of overlay. /// y position of overlay. /// Composite images in this colour space. /// Images have premultiplied alpha. /// A new . public Image Composite(Image overlay, Enums.BlendMode mode, int? x = null, int? y = null, Enums.Interpretation? compositingSpace = null, bool? premultiplied = null) => Composite2(overlay, mode, x, y, compositingSpace, premultiplied); /// /// A synonym for . /// /// /// /// using Image @out = input.Crop(left, top, width, height); /// /// /// Left edge of extract area. /// Top edge of extract area. /// Width of extract area. /// Height of extract area. /// A new . public Image Crop(int left, int top, int width, int height) => ExtractArea(left, top, width, height); /// /// Return the coordinates of the image maximum. /// /// An array of doubles. public double[] MaxPos() { var v = Max(out var x, out var y); return new[] { v, x, y }; } /// /// Return the coordinates of the image minimum. /// /// An array of doubles. public double[] MinPos() { var v = Min(out var x, out var y); return new[] { v, x, y }; } /// /// Return the real part of a complex image. /// /// A new . public Image Real() => Complexget(Enums.OperationComplexget.Real); /// /// Return the imaginary part of a complex image. /// /// A new . public Image Imag() => Complexget(Enums.OperationComplexget.Imag); /// /// Return an image converted to polar coordinates. /// /// A new . public Image Polar() => RunCmplx(x => x.Complex(Enums.OperationComplex.Polar), this); /// /// Return an image converted to rectangular coordinates. /// /// A new . public Image Rect() => RunCmplx(x => x.Complex(Enums.OperationComplex.Rect), this); /// /// Return the complex conjugate of an image. /// /// A new . public Image Conj() => Complex(Enums.OperationComplex.Conj); /// /// Return the sine of an image in degrees. /// /// A new . public Image Sin() => Math(Enums.OperationMath.Sin); /// /// Return the cosine of an image in degrees. /// /// A new . public Image Cos() => Math(Enums.OperationMath.Cos); /// /// Return the tangent of an image in degrees. /// /// A new . public Image Tan() => Math(Enums.OperationMath.Tan); /// /// Return the inverse sine of an image in degrees. /// /// A new . public Image Asin() => Math(Enums.OperationMath.Asin); /// /// Return the inverse cosine of an image in degrees. /// /// A new . public Image Acos() => Math(Enums.OperationMath.Acos); /// /// Return the inverse tangent of an image in degrees. /// /// A new . public Image Atan() => Math(Enums.OperationMath.Atan); /// /// Return the hyperbolic sine of an image in radians. /// /// A new . public Image Sinh() => Math(Enums.OperationMath.Sinh); /// /// Return the hyperbolic cosine of an image in radians. /// /// A new . public Image Cosh() => Math(Enums.OperationMath.Cosh); /// /// Return the hyperbolic tangent of an image in radians. /// /// A new . public Image Tanh() => Math(Enums.OperationMath.Tanh); /// /// Return the inverse hyperbolic sine of an image in radians. /// /// A new . public Image Asinh() => Math(Enums.OperationMath.Asinh); /// /// Return the inverse hyperbolic cosine of an image in radians. /// /// A new . public Image Acosh() => Math(Enums.OperationMath.Acosh); /// /// Return the inverse hyperbolic tangent of an image in radians. /// /// A new . public Image Atanh() => Math(Enums.OperationMath.Atanh); /// /// Return the natural log of an image. /// /// A new . public Image Log() => Math(Enums.OperationMath.Log); /// /// Return the log base 10 of an image. /// /// A new . public Image Log10() => Math(Enums.OperationMath.Log10); /// /// Return e ** pixel. /// /// A new . public Image Exp() => Math(Enums.OperationMath.Exp); /// /// Return 10 ** pixel. /// /// A new . public Image Exp10() => Math(Enums.OperationMath.Exp10); /// /// Raise to power of an image. /// /// To the power of this. /// A new . public Image Pow(Image exp) => Math2(exp, Enums.OperationMath2.Pow); /// /// Raise to power of an constant. /// /// To the power of this. /// A new . public Image Pow(double exp) => Math2Const(Enums.OperationMath2.Pow, new[] { exp }); /// /// Raise to power of an array. /// /// To the power of this. /// A new . public Image Pow(double[] exp) => Math2Const(Enums.OperationMath2.Pow, exp); /// /// Raise to power of an array. /// /// To the power of this. /// A new . public Image Pow(int[] exp) => Math2Const(Enums.OperationMath2.Pow, Array.ConvertAll(exp, Convert.ToDouble)); /// /// Raise to power of an image, but with the arguments reversed. /// /// To the base of this. /// A new . public Image Wop(Image @base) => Math2(@base, Enums.OperationMath2.Wop); /// /// Raise to power of an constant, but with the arguments reversed. /// /// To the base of this. /// A new . public Image Wop(double @base) => Math2Const(Enums.OperationMath2.Wop, new[] { @base }); /// /// Raise to power of an array, but with the arguments reversed. /// /// To the base of this. /// A new . public Image Wop(double[] @base) => Math2Const(Enums.OperationMath2.Wop, @base); /// /// Raise to power of an array, but with the arguments reversed. /// /// To the base of this. /// A new . public Image Wop(int[] @base) => Math2Const(Enums.OperationMath2.Wop, Array.ConvertAll(@base, Convert.ToDouble)); /// /// Arc tangent of an image in degrees. /// /// Arc tangent of y / . /// A new . public Image Atan2(Image x) => Math2(x, Enums.OperationMath2.Atan2); /// /// Arc tangent of an constant in degrees. /// /// Arc tangent of y / . /// A new . public Image Atan2(double x) => Math2Const(Enums.OperationMath2.Atan2, new[] { x }); /// /// Arc tangent of an array in degrees. /// /// Arc tangent of y / . /// A new . public Image Atan2(double[] x) => Math2Const(Enums.OperationMath2.Atan2, x); /// /// Arc tangent of an array in degrees. /// /// Arc tangent of y / . /// A new . public Image Atan2(int[] x) => Math2Const(Enums.OperationMath2.Atan2, Array.ConvertAll(x, Convert.ToDouble)); /// /// Erode with a structuring element. /// /// The structuring element. /// A new . public Image Erode(Image mask) => Morph(mask, Enums.OperationMorphology.Erode); /// /// Dilate with a structuring element. /// /// The structuring element. /// A new . public Image Dilate(Image mask) => Morph(mask, Enums.OperationMorphology.Dilate); /// /// size x size median filter. /// /// The median filter. /// A new . public Image Median(int size) => Rank(size, size, size * size / 2); /// /// Flip horizontally. /// /// A new . public Image FlipHor() => Flip(Enums.Direction.Horizontal); /// /// Flip vertically. /// /// A new . public Image FlipVer() => Flip(Enums.Direction.Vertical); /// /// Rotate 90 degrees clockwise. /// /// A new . public Image Rot90() => Rot(Enums.Angle.D90); /// /// Rotate 180 degrees. /// /// A new . public Image Rot180() => Rot(Enums.Angle.D180); /// /// Rotate 270 degrees clockwise. /// /// A new . public Image Rot270() => Rot(Enums.Angle.D270); /// /// Return the largest integral value not greater than the argument. /// /// A new . public Image Floor() => this.Call("round", Enums.OperationRound.Floor) as Image; /// /// Return the largest integral value not greater than the argument. /// /// A new . public Image Ceil() => this.Call("round", Enums.OperationRound.Ceil) as Image; /// /// Return the nearest integral value. /// /// A new . public Image Rint() => this.Call("round", Enums.OperationRound.Rint) as Image; /// /// AND image bands together. /// /// A new . public Image BandAnd() => this.Call("bandbool", Enums.OperationBoolean.And) as Image; /// /// OR image bands together. /// /// A new . public Image BandOr() => this.Call("bandbool", Enums.OperationBoolean.Or) as Image; /// /// EOR image bands together. /// /// A new . public Image BandEor() => this.Call("bandbool", Enums.OperationBoolean.Eor) as Image; /// /// This operation compares two images on equality. /// /// A to compare. /// A new . public Image Equal(Image right) => this.Call("relational", right, Enums.OperationRelational.Equal) as Image; /// /// This operation compares two images on equality. /// /// A double array to compare. /// A new . public Image Equal(double[] right) => this.Call("relational_const", Enums.OperationRelational.Equal, right) as Image; /// /// This operation compares two images on equality. /// /// A integer array to compare. /// A new . public Image Equal(int[] right) => this.Call("relational_const", Enums.OperationRelational.Equal, right) as Image; /// /// This operation compares two images on equality. /// /// A double constant to compare. /// A new . public Image Equal(double right) => this.Call("relational_const", Enums.OperationRelational.Equal, right) as Image; /// /// This operation compares two images on inequality. /// /// A to compare. /// A new . public Image NotEqual(Image right) => this.Call("relational", right, Enums.OperationRelational.Noteq) as Image; /// /// This operation compares two images on inequality. /// /// A double constant to compare. /// A new . public Image NotEqual(double right) => this.Call("relational_const", Enums.OperationRelational.Noteq, right) as Image; /// /// This operation compares two images on inequality. /// /// A double array to compare. /// A new . public Image NotEqual(double[] right) => this.Call("relational_const", Enums.OperationRelational.Noteq, right) as Image; /// /// This operation compares two images on inequality. /// /// A integer array to compare. /// A new . public Image NotEqual(int[] right) => this.Call("relational_const", Enums.OperationRelational.Noteq, right) as Image; /// /// Does this image have an alpha channel? /// /// /// Uses colour space interpretation with number of channels to guess /// this. /// /// if this image has an alpha channel; /// otherwise, . public bool HasAlpha() { // use `vips_image_hasalpha` on libvips >= 8.5. if (NetVips.AtLeastLibvips(8, 5)) { return VipsImage.HasAlpha(this); } return Bands == 2 || (Bands == 4 && Interpretation != Enums.Interpretation.Cmyk) || Bands > 4; } /// /// Append an alpha channel to an image. /// /// /// /// using rgba = rgb.AddAlpha(); /// /// /// A new . public Image AddAlpha() { // `vips_addalpha` was turned into a VipsOperation in 8.16. if (NetVips.AtLeastLibvips(8, 16)) { return this.Call("addalpha") as Image; } // use `vips_addalpha` on libvips >= 8.6. if (NetVips.AtLeastLibvips(8, 6)) { var result = VipsImage.AddAlpha(this, out var vi); if (result != 0) { throw new VipsException("unable to append an alpha channel"); } return new Image(vi); } var maxAlpha = Interpretation is Enums.Interpretation.Grey16 or Enums.Interpretation.Rgb16 ? 65535 : 255; return Bandjoin(maxAlpha); } /// /// If image has been killed (see ), set an error message, /// clear the `kill` flag and return . /// Otherwise return . /// /// /// Handy for loops which need to run sets of threads which can fail. /// At least libvips 8.8 is needed. If this version requirement is not met, /// it will always return . /// /// if image has been killed; /// otherwise, . public bool IsKilled() { if (!NetVips.AtLeastLibvips(8, 8)) { return false; } return VipsImage.IsKilled(this); } /// /// Set the `kill` flag on an image. Handy for stopping sets of threads. /// /// /// At least libvips 8.8 is needed. /// /// The kill state. public void SetKill(bool kill) { if (!NetVips.AtLeastLibvips(8, 8)) { return; } VipsImage.SetKill(this, kill); } /// /// Connects a callback function () to a signal on this image. /// /// /// The callback will be triggered every time this signal is issued on this image. /// /// A signal to be used on this image. See . /// The callback to connect. /// Data to pass to handler calls. /// The handler id. /// If it failed to connect the signal. public ulong SignalConnect(Enums.Signals signal, EvalDelegate callback, nint data = default) { void EvalMarshal(nint imagePtr, nint progressPtr, nint userDataPtr) { if (imagePtr == IntPtr.Zero || progressPtr == IntPtr.Zero) { return; } using var image = new Image(imagePtr); image.ObjectRef(); var progress = Marshal.PtrToStructure(progressPtr); callback.Invoke(image, progress); } return signal switch { Enums.Signals.PreEval => SignalConnect("preeval", EvalMarshal, data), Enums.Signals.Eval => SignalConnect("eval", EvalMarshal, data), Enums.Signals.PostEval => SignalConnect("posteval", EvalMarshal, data), _ => throw new ArgumentOutOfRangeException(nameof(signal), signal, $"The value of argument '{nameof(signal)}' ({signal}) is invalid for enum type '{nameof(Enums.Signals)}'.") }; } /// /// Drop caches on an image, and any downstream images. /// /// /// This method drops all pixel caches on an image and on all downstream /// images. Any operations which depend on this image, directly or /// indirectly, are also dropped from the libvips operation cache. /// /// This method can be useful if you wrap a libvips image around an array /// with /// and then change some bytes without libvips knowing. /// public void Invalidate() { VipsImage.InvalidateAll(this); } /// /// Enable progress reporting on an image. /// /// /// When progress reporting is enabled, evaluation of the most downstream /// image from this image will report progress using the , /// and signals. /// /// to enable progress reporting; /// otherwise, . public void SetProgress(bool progress) { VipsImage.SetProgress(this, progress); } /// /// Attach progress feedback, if required. /// /// /// You can use this function to update user-interfaces with /// progress feedback, for example: /// /// using var image = Image.NewFromFile("huge.jpg", access: Enums.Access.Sequential); /// /// var progress = new Progress<int>(percent => /// { /// Console.Write($"\r{percent}% complete"); /// }); /// image.SetProgress(progress); /// /// image.Dzsave("image-pyramid"); /// /// /// If a cancellation has been requested for this token (see ) /// it will block the evaluation of this image on libvips >= 8.8 (see ). /// If this version requirement is not met, it will only stop updating the progress. /// /// A provider for progress updates. /// Cancellation token to block evaluation on this image. public void SetProgress(IProgress progress, CancellationToken token = default) { SetProgress(progress != null); if (progress == null) { return; } var lastPercent = 0; var isKilled = false; void EvalCallback(Image image, VipsProgress progressStruct) { // Block evaluation on this image if a cancellation // has been requested for this token. if (token.IsCancellationRequested) { if (!isKilled) { image.SetKill(true); isKilled = true; } return; } if (progressStruct.Percent != lastPercent) { progress.Report(progressStruct.Percent); lastPercent = progressStruct.Percent; } } SignalConnect(Enums.Signals.Eval, EvalCallback); } #endregion #region handwritten properties /// /// Multi-page images can have a page height. /// If page-height is not set, it defaults to the image height. /// /// /// At least libvips 8.8 is needed. /// public int PageHeight => VipsImage.GetPageHeight(this); #endregion #region support with in the most trivial way /// /// Does band exist in image. /// /// The index to fetch. /// true if the index exists. public bool BandExists(int i) { return i >= 0 && i <= Bands - 1; } /// /// Overload `[]`. /// /// /// Use `[]` to pull out band elements from an image. For example: /// /// using var green = rgbImage[1]; /// /// Will make a new one-band image from band 1 (the middle band). /// /// The band element to pull out. /// A new . public Image this[int i] => BandExists(i) ? ExtractBand(i) : null; /// /// A synonym for . /// /// /// /// double[] outArray = in[x, y]; /// /// /// Point to read. /// Point to read. /// An array of doubles. public double[] this[int x, int y] => Getpoint(x, y); /// /// Split an n-band image into n separate images. /// /// An array of . public Image[] Bandsplit() { var images = new Image[Bands]; for (var i = 0; i < Bands; i++) { ref var image = ref images[i]; image = this[i]; } return images; } /// /// Compares the hashcode of two images. /// /// The to compare. /// if equal; otherwise, . public bool Equals(Image other) { return Equals(GetHashCode(), other.GetHashCode()); } /// /// Determines whether the specified object is equal to the current image. /// /// The object to compare with the current image. /// if the specified object is equal /// to the current image; otherwise, . public override bool Equals(object obj) { if (obj == null) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((Image)obj); } /// /// Serves as the default hash function. /// /// A hash code for the current image. public override int GetHashCode() { return ToString().GetHashCode(); } #endregion }