using System; using System.Collections; using System.Runtime.InteropServices; using System.Text; using NetVips.Internal; namespace NetVips; /// /// Wrap in a C# class. /// /// /// This class wraps in a convenient interface. You can use /// instances of this class to get and set properties. /// /// On construction, is all zero (empty). You can pass it to /// a get function to have it filled by , or use init to /// set a type, set to set a value, then use it to set an object property. /// /// GValue lifetime is managed automatically. /// public class GValue : IDisposable { /// /// The specified struct to wrap around. /// internal Internal.GValue.Struct Struct; /// /// Track whether has been called. /// private bool _disposed; /// /// Shift value used in converting numbers to type IDs. /// private const int FundamentalShift = 2; // look up some common gtypes at init for speed /// /// The fundamental type corresponding to gboolean. /// public static readonly nint GBoolType = 5 << FundamentalShift; /// /// The fundamental type corresponding to gint. /// public static readonly nint GIntType = 6 << FundamentalShift; /// /// The fundamental type corresponding to guint64. /// public static readonly nint GUint64Type = 11 << FundamentalShift; /// /// The fundamental type from which all enumeration types are derived. /// public static readonly nint GEnumType = 12 << FundamentalShift; /// /// The fundamental type from which all flags types are derived. /// public static readonly nint GFlagsType = 13 << FundamentalShift; /// /// The fundamental type corresponding to gdouble. /// public static readonly nint GDoubleType = 15 << FundamentalShift; /// /// The fundamental type corresponding to null-terminated C strings. /// public static readonly nint GStrType = 16 << FundamentalShift; /// /// The fundamental type for GObject. /// public static readonly nint GObjectType = 20 << FundamentalShift; /// /// The fundamental type for VipsImage. /// public static readonly nint ImageType = NetVips.TypeFromName("VipsImage"); /// /// The fundamental type for VipsArrayInt. /// public static readonly nint ArrayIntType = NetVips.TypeFromName("VipsArrayInt"); /// /// The fundamental type for VipsArrayDouble. /// public static readonly nint ArrayDoubleType = NetVips.TypeFromName("VipsArrayDouble"); /// /// The fundamental type for VipsArrayImage. /// public static readonly nint ArrayImageType = NetVips.TypeFromName("VipsArrayImage"); /// /// The fundamental type for VipsRefString. /// public static readonly nint RefStrType = NetVips.TypeFromName("VipsRefString"); /// /// The fundamental type for VipsBlob. /// public static readonly nint BlobType = NetVips.TypeFromName("VipsBlob"); /// /// The fundamental type for VipsBlendMode. See . /// public static readonly nint BlendModeType; /// /// The fundamental type for VipsSource. See . /// public static readonly nint SourceType; /// /// The fundamental type for VipsTarget. See . /// public static readonly nint TargetType; /// /// Hint of how much native memory is actually occupied by the object. /// private long _memoryPressure; static GValue() { if (NetVips.AtLeastLibvips(8, 6)) { BlendModeType = Vips.BlendModeGetType(); } if (NetVips.AtLeastLibvips(8, 9)) { SourceType = NetVips.TypeFromName("VipsSource"); TargetType = NetVips.TypeFromName("VipsTarget"); } } /// /// Initializes a new instance of the class. /// public GValue() { Struct = new Internal.GValue.Struct(); } /// /// Initializes a new instance of the class /// with the specified struct to wrap around. /// /// The specified struct to wrap around. internal GValue(Internal.GValue.Struct value) { Struct = value; } /// /// Set the type of a GValue. /// /// /// GValues have a set type, fixed at creation time. Use SetType to set /// the type of a GValue before assigning to it. /// /// GTypes are 32 or 64-bit integers (depending on the platform). See /// TypeFind. /// /// Type the GValue should hold values of. public void SetType(nint gtype) { Internal.GValue.Init(ref Struct, gtype); } /// /// Ensure that the GC knows the true cost of the object during collection. /// /// /// If the object is actually bigger than the managed size reflects, it may /// be a candidate for quick(er) collection. /// /// The amount of unmanaged memory that has been allocated. private void AddMemoryPressure(long bytesAllocated) { if (bytesAllocated <= 0) { return; } GC.AddMemoryPressure(bytesAllocated); _memoryPressure += bytesAllocated; } /// /// Set a GValue. /// /// /// The value is converted to the type of the GValue, if possible, and /// assigned. /// /// Value to be set. public void Set(object value) { var gtype = GetTypeOf(); var fundamental = GType.Fundamental(gtype); if (gtype == GBoolType) { Internal.GValue.SetBoolean(ref Struct, Convert.ToBoolean(value)); } else if (gtype == GIntType) { Internal.GValue.SetInt(ref Struct, Convert.ToInt32(value)); } else if (gtype == GUint64Type) { Internal.GValue.SetUint64(ref Struct, Convert.ToUInt64(value)); } else if (gtype == GDoubleType) { Internal.GValue.SetDouble(ref Struct, Convert.ToDouble(value)); } else if (fundamental == GEnumType) { Internal.GValue.SetEnum(ref Struct, Convert.ToInt32(value)); } else if (fundamental == GFlagsType) { Internal.GValue.SetFlags(ref Struct, Convert.ToUInt32(value)); } else if (gtype == GStrType) { var bytes = Encoding.UTF8.GetBytes(Convert.ToString(value) + char.MinValue); // Ensure null-terminated string Internal.GValue.SetString(ref Struct, bytes); } else if (gtype == RefStrType) { var bytes = Encoding.UTF8.GetBytes(Convert.ToString(value) + char.MinValue); // Ensure null-terminated string VipsValue.SetRefString(ref Struct, bytes); } else if (fundamental == GObjectType && value is GObject gObject) { AddMemoryPressure(gObject.MemoryPressure); Internal.GValue.SetObject(ref Struct, gObject); } else if (gtype == ArrayIntType) { if (value is not IEnumerable) { value = new[] { value }; } var integers = value switch { int[] ints => ints, double[] doubles => Array.ConvertAll(doubles, Convert.ToInt32), object[] objects => Array.ConvertAll(objects, Convert.ToInt32), _ => throw new ArgumentException( $"unsupported value type {value.GetType()} for gtype {NetVips.TypeName(gtype)}") }; VipsValue.SetArrayInt(ref Struct, integers, integers.Length); } else if (gtype == ArrayDoubleType) { if (value is not IEnumerable) { value = new[] { value }; } var doubles = value switch { double[] dbls => dbls, int[] ints => Array.ConvertAll(ints, Convert.ToDouble), object[] objects => Array.ConvertAll(objects, Convert.ToDouble), _ => throw new ArgumentException( $"unsupported value type {value.GetType()} for gtype {NetVips.TypeName(gtype)}") }; VipsValue.SetArrayDouble(ref Struct, doubles, doubles.Length); } else if (gtype == ArrayImageType && value is Image[] images) { var size = images.Length; VipsValue.SetArrayImage(ref Struct, size); var ptrArr = VipsValue.GetArrayImage(in Struct, out _); for (var i = 0; i < size; i++) { ref var image = ref images[i]; // the gvalue needs a ref on each of the images Marshal.WriteIntPtr(ptrArr, i * IntPtr.Size, image.ObjectRef()); AddMemoryPressure(image.MemoryPressure); } } else if (gtype == BlobType && value is VipsBlob blob) { AddMemoryPressure((long)blob.Length); Internal.GValue.SetBoxed(ref Struct, blob); } else if (gtype == BlobType) { var memory = value switch { string strValue => Encoding.UTF8.GetBytes(strValue), char[] charArrValue => Encoding.UTF8.GetBytes(charArrValue), byte[] byteArrValue => byteArrValue, _ => throw new ArgumentException( $"unsupported value type {value.GetType()} for gtype {NetVips.TypeName(gtype)}") }; // We need to set the blob to a copy of the string that vips can own var ptr = GLib.GMalloc((ulong)memory.Length); Marshal.Copy(memory, 0, ptr, memory.Length); AddMemoryPressure(memory.Length); if (NetVips.AtLeastLibvips(8, 6)) { VipsValue.SetBlobFree(ref Struct, ptr, (ulong)memory.Length); } else { int FreeFn(nint a, nint b) { GLib.GFree(a); return 0; } VipsValue.SetBlob(ref Struct, FreeFn, ptr, (ulong)memory.Length); } } else { throw new ArgumentException( $"unsupported gtype for set {NetVips.TypeName(gtype)}, fundamental {NetVips.TypeName(fundamental)}, value type {value.GetType()}"); } } /// /// Get the contents of a GValue. /// /// /// The contents of the GValue are read out as a C# type. /// /// The contents of this GValue. public object Get() { var gtype = GetTypeOf(); var fundamental = GType.Fundamental(gtype); object result; if (gtype == GBoolType) { result = Internal.GValue.GetBoolean(in Struct); } else if (gtype == GIntType) { result = Internal.GValue.GetInt(in Struct); } else if (gtype == GUint64Type) { result = Internal.GValue.GetUint64(in Struct); } else if (gtype == GDoubleType) { result = Internal.GValue.GetDouble(in Struct); } else if (fundamental == GEnumType) { result = Internal.GValue.GetEnum(in Struct); } else if (fundamental == GFlagsType) { result = Internal.GValue.GetFlags(in Struct); } else if (gtype == GStrType) { result = Internal.GValue.GetString(in Struct).ToUtf8String(); } else if (gtype == RefStrType) { result = VipsValue.GetRefString(in Struct, out var size).ToUtf8String(size: (int)size); } else if (gtype == ImageType) { // g_value_get_object() will not add a ref ... that is // held by the gvalue var vi = Internal.GValue.GetObject(in Struct); // we want a ref that will last with the life of the vimage: // this ref is matched by the unref that's attached to finalize // by GObject var image = new Image(vi); image.ObjectRef(); result = image; } else if (gtype == ArrayIntType) { var intPtr = VipsValue.GetArrayInt(in Struct, out var size); var intArr = new int[size]; Marshal.Copy(intPtr, intArr, 0, size); result = intArr; } else if (gtype == ArrayDoubleType) { var intPtr = VipsValue.GetArrayDouble(in Struct, out var size); var doubleArr = new double[size]; Marshal.Copy(intPtr, doubleArr, 0, size); result = doubleArr; } else if (gtype == ArrayImageType) { var ptrArr = VipsValue.GetArrayImage(in Struct, out var size); var images = new Image[size]; for (var i = 0; i < size; i++) { var vi = Marshal.ReadIntPtr(ptrArr, i * IntPtr.Size); ref var image = ref images[i]; image = new Image(vi); image.ObjectRef(); } result = images; } else if (gtype == BlobType) { var array = VipsValue.GetBlob(in Struct, out var size); // Blob types are returned as an array of bytes. var byteArr = new byte[size]; Marshal.Copy(array, byteArr, 0, (int)size); result = byteArr; } else { throw new ArgumentException($"unsupported gtype for get {NetVips.TypeName(gtype)}"); } return result; } /// /// Get the GType of this GValue. /// /// The GType of this GValue. public nint GetTypeOf() { return Struct.GType; } /// /// Finalizes an instance of the class. /// /// /// Allows an object to try to free resources and perform other cleanup /// operations before it is reclaimed by garbage collection. /// ~GValue() { // Do not re-create Dispose clean-up code here. Dispose(false); } /// /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; /// to release only unmanaged resources. protected void Dispose(bool disposing) { // Check to see if Dispose has already been called. if (!_disposed) { // and tag it to be unset on GC as well Internal.GValue.Unset(ref Struct); if (_memoryPressure > 0) { GC.RemoveMemoryPressure(_memoryPressure); _memoryPressure = 0; } // Note disposing has been done. _disposed = true; } } /// /// Performs application-defined tasks associated with freeing, releasing, /// or resetting unmanaged resources. /// public void Dispose() { Dispose(true); // This object will be cleaned up by the Dispose method. GC.SuppressFinalize(this); } }