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
}