using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.InteropServices; using NetVips.Internal; namespace NetVips; /// /// Build introspection data for operations. /// /// /// Make an operation, introspect it, and build a structure representing /// everything we know about it. /// public class Introspect { /// /// A cache for introspection data. /// private static readonly ConcurrentDictionary IntrospectCache = new(); /// /// An object structure that encapsulates the metadata /// required to specify arguments. /// public struct Argument { /// /// Name of this argument. /// public string Name; /// /// Flags for this argument. /// public Enums.ArgumentFlags Flags; /// /// The GType for this argument. /// public nint Type; } /// /// The first required input image or . /// public Argument? MemberX; /// /// A bool indicating if this operation is mutable. /// public readonly bool Mutable; /// /// The required input for this operation. /// public readonly List RequiredInput = new(); /// /// The optional input for this operation. /// public readonly Dictionary OptionalInput = new(); /// /// The required output for this operation. /// public readonly List RequiredOutput = new(); /// /// The optional output for this operation. /// public readonly Dictionary OptionalOutput = new(); /// /// Build introspection data for a specified operation name. /// /// The operation name to introspect. private Introspect(string operationName) { using var op = Operation.NewFromName(operationName); var arguments = GetArgs(op); foreach (var entry in arguments) { var name = entry.Key; var flag = entry.Value; var gtype = op.GetTypeOf(name); var details = new Argument { Name = name, Flags = flag, Type = gtype }; if ((flag & Enums.ArgumentFlags.INPUT) != 0) { if ((flag & Enums.ArgumentFlags.REQUIRED) != 0 && (flag & Enums.ArgumentFlags.DEPRECATED) == 0) { // the first required input image arg will be self if (!MemberX.HasValue && gtype == GValue.ImageType) { MemberX = details; } else { RequiredInput.Add(details); } } else { // we allow deprecated optional args OptionalInput[name] = details; } // modified input arguments count as mutable. if ((flag & Enums.ArgumentFlags.MODIFY) != 0 && (flag & Enums.ArgumentFlags.REQUIRED) != 0 && (flag & Enums.ArgumentFlags.DEPRECATED) == 0) { Mutable = true; } } else if ((flag & Enums.ArgumentFlags.OUTPUT) != 0) { if ((flag & Enums.ArgumentFlags.REQUIRED) != 0 && (flag & Enums.ArgumentFlags.DEPRECATED) == 0) { RequiredOutput.Add(details); } else { // again, allow deprecated optional args OptionalOutput[name] = details; } } } } /// /// Get all arguments for an operation. /// /// /// Not quick! Try to call this infrequently. /// /// Operation to lookup. /// Arguments for the operation. private IEnumerable> GetArgs(Operation operation) { var args = new List>(); void AddArg(string name, Enums.ArgumentFlags flags) { // libvips uses '-' to separate parts of arg names, but we // need '_' for C# name = name.Replace("-", "_"); args.Add(new KeyValuePair(name, flags)); } // vips_object_get_args was added in 8.7 if (NetVips.AtLeastLibvips(8, 7)) { var result = Internal.VipsObject.GetArgs(operation, out var names, out var flags, out var nArgs); if (result != 0) { throw new VipsException("unable to get arguments from operation"); } for (var i = 0; i < nArgs; i++) { var flag = (Enums.ArgumentFlags)Marshal.PtrToStructure(flags + i * sizeof(int)); if ((flag & Enums.ArgumentFlags.CONSTRUCT) == 0) { continue; } var name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(names, i * IntPtr.Size)); AddArg(name, flag); } } else { nint AddConstruct(nint self, nint pspec, nint argumentClass, nint argumentInstance, nint a, nint b) { var flags = Marshal.PtrToStructure(argumentClass).Flags; if ((flags & Enums.ArgumentFlags.CONSTRUCT) == 0) { return IntPtr.Zero; } var name = Marshal.PtrToStringAnsi(Marshal.PtrToStructure(pspec).Name); AddArg(name, flags); return IntPtr.Zero; } Vips.ArgumentMap(operation, AddConstruct, IntPtr.Zero, IntPtr.Zero); } return args; } /// /// Get introspection data for a specified operation name. /// /// Operation name. /// Introspection data. public static Introspect Get(string operationName) { return IntrospectCache.GetOrAdd(operationName, name => new Introspect(name)); } }