From 475477e7d95e8fe7bd7bcfdae6039809f2c4cc7d Mon Sep 17 00:00:00 2001 From: Lilith River Date: Tue, 6 Feb 2024 15:52:26 -0700 Subject: [PATCH 01/22] Don't use Assembly.Location on .NET core+ --- .../Bindings/NativeLibraryLoading.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Imageflow/Bindings/NativeLibraryLoading.cs b/src/Imageflow/Bindings/NativeLibraryLoading.cs index fa439ce..b04af52 100644 --- a/src/Imageflow/Bindings/NativeLibraryLoading.cs +++ b/src/Imageflow/Bindings/NativeLibraryLoading.cs @@ -245,14 +245,19 @@ private static IEnumerable> BaseFolders(IEnumerable? "runtimes", PlatformRuntimePrefix.Value + "-" + ArchitectureSubdir.Value, "native")); } - - #if NETSTANDARD2_0 - // Look in the folder that *this* assembly is located. - var assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - if (!string.IsNullOrEmpty(assemblyLocation)) - yield return Tuple.Create(true, assemblyLocation); - #endif + string? assemblyLocation = null; + #if !NETCOREAPP && !NET5_0_OR_GREATER && !NET8_0_OR_GREATER + try{ + // Look in the folder that *this* assembly is located. + assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + } catch (NotImplementedException){ + // ignored + } + #endif + if (!string.IsNullOrEmpty(assemblyLocation)) + yield return Tuple.Create(true, assemblyLocation!); } internal static IEnumerable SearchPossibilitiesForFile(string filename, IEnumerable? customSearchDirectories = null) From 92a67e21968be18ae7e70db4534d693c395d6f46 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Fri, 1 Mar 2024 00:55:58 -0700 Subject: [PATCH 02/22] Major refactor of API and codebase to prefer Span, Memory, and ValueTask; reduce copying of data significantly. Change memory management a lot: JsonResponseHandle will now prevent job disposal; SafeHandleMemoryManager will do the same, if given the safe handle of the job. UnmanagedMemoryManager can work with malloc/free if nothing else is allowed to free it. --- CHANGES.md | 3 + src/Imageflow.AllPlatforms/packages.lock.json | 20 +- src/Imageflow/Bindings/ImageflowMethods.cs | 8 + .../Bindings/ImageflowUnmanagedReadStream.cs | 31 +- src/Imageflow/Bindings/JobContext.cs | 559 +++++++++++++----- src/Imageflow/Bindings/JobContextHandle.cs | 5 +- src/Imageflow/Bindings/JsonResponse.cs | 150 ++++- src/Imageflow/Bindings/JsonResponseHandle.cs | 38 +- src/Imageflow/Fluent/BufferedStreamSource.cs | 126 ++++ src/Imageflow/Fluent/BuildJobResult.cs | 16 +- src/Imageflow/Fluent/BuildNode.cs | 25 +- src/Imageflow/Fluent/BytesSourceAdapter.cs | 27 + src/Imageflow/Fluent/FinishJobBuilder.cs | 2 +- src/Imageflow/Fluent/IAsyncMemorySource.cs | 27 + src/Imageflow/Fluent/IBytesSource.cs | 60 +- src/Imageflow/Fluent/IOutputDestination.cs | 136 ++++- src/Imageflow/Fluent/IOutputSink.cs | 25 + src/Imageflow/Fluent/ImageJob.cs | 222 +++++-- src/Imageflow/Fluent/InputWatermark.cs | 20 +- src/Imageflow/Fluent/MemoryLifetimePromise.cs | 18 + src/Imageflow/Fluent/MemorySource.cs | 105 ++++ .../Fluent/OutputDestinationWrapper.cs | 88 +++ .../Fluent/PerformanceDetailsNode.cs | 2 +- src/Imageflow/Imageflow.Net.csproj | 6 +- .../ArgumentNullThrowHelperPolyfill.cs | 40 ++ .../ArraySegmentExtensions.cs | 38 ++ .../IAssertReady.cs | 2 +- .../{Bindings => Internal.Helpers}/PinBox.cs | 2 +- .../SafeHandleMemoryManager.cs | 118 ++++ .../StreamMemoryExtensionsPolyfills.cs | 88 +++ src/Imageflow/Internal.Helpers/TextHelpers.cs | 63 ++ .../UnmanagedMemoryManager.cs | 59 ++ src/Imageflow/packages.lock.json | 121 +++- tests/Imageflow.Test/Imageflow.Test.csproj | 4 +- tests/Imageflow.Test/TestJson.cs | 2 +- tests/Imageflow.Test/TestStreams.cs | 54 ++ tests/Imageflow.Test/packages.lock.json | 170 +++--- .../packages.lock.json | 8 +- tests/Imageflow.TestWebAOT/packages.lock.json | 12 +- 39 files changed, 2103 insertions(+), 397 deletions(-) create mode 100644 src/Imageflow/Bindings/ImageflowMethods.cs create mode 100644 src/Imageflow/Fluent/BufferedStreamSource.cs create mode 100644 src/Imageflow/Fluent/BytesSourceAdapter.cs create mode 100644 src/Imageflow/Fluent/IAsyncMemorySource.cs create mode 100644 src/Imageflow/Fluent/IOutputSink.cs create mode 100644 src/Imageflow/Fluent/MemoryLifetimePromise.cs create mode 100644 src/Imageflow/Fluent/MemorySource.cs create mode 100644 src/Imageflow/Fluent/OutputDestinationWrapper.cs create mode 100644 src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs create mode 100644 src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs rename src/Imageflow/{Bindings => Internal.Helpers}/IAssertReady.cs (66%) rename src/Imageflow/{Bindings => Internal.Helpers}/PinBox.cs (95%) create mode 100644 src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs create mode 100644 src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs create mode 100644 src/Imageflow/Internal.Helpers/TextHelpers.cs create mode 100644 src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs create mode 100644 tests/Imageflow.Test/TestStreams.cs diff --git a/CHANGES.md b/CHANGES.md index 00236fd..cd4ffe6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ ## Changelog + +InputWatermark.Source is now IMemorySource instead of IBytesSource + ## v0.11 (2024-01-29) New features: diff --git a/src/Imageflow.AllPlatforms/packages.lock.json b/src/Imageflow.AllPlatforms/packages.lock.json index 4a8b792..af5c79d 100644 --- a/src/Imageflow.AllPlatforms/packages.lock.json +++ b/src/Imageflow.AllPlatforms/packages.lock.json @@ -45,8 +45,8 @@ }, "Microsoft.IO.RecyclableMemoryStream": { "type": "Transitive", - "resolved": "2.3.2", - "contentHash": "Oh1qXXFdJFcHozvb4H6XYLf2W0meZFuG0A+TfapFPj9z5fd4vxiARGEhAaLj/6XWQaMYIv4SH/9Q6H78Hw0E2Q==", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==", "dependencies": { "System.Memory": "4.5.5" } @@ -116,7 +116,9 @@ "imageflow.net": { "type": "Project", "dependencies": { - "Microsoft.IO.RecyclableMemoryStream": "[2.*, 4.0.0)", + "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", + "System.Buffers": "[4.*, )", + "System.Memory": "[4.*, )", "System.Text.Json": "[6.*, )" } } @@ -148,14 +150,14 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.1, )", - "resolved": "8.0.1", - "contentHash": "ADdJXuKNjwZDfBmybMnpvwd5CK3gp92WkWqqeQhW4W+q4MO3Qaa9QyW2DcFLAvCDMcCWxT5hRXqGdv13oon7nA==" + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "hKTrehpfVzOhAz0mreaTAZgbz0DrMEbWq4n3hAo8Ks6WdxdqQhNPvzOqn9VygKuWf1bmxPdraqzTaXriO/sn0A==" }, "Microsoft.IO.RecyclableMemoryStream": { "type": "Transitive", - "resolved": "2.3.2", - "contentHash": "Oh1qXXFdJFcHozvb4H6XYLf2W0meZFuG0A+TfapFPj9z5fd4vxiARGEhAaLj/6XWQaMYIv4SH/9Q6H78Hw0E2Q==" + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", @@ -182,7 +184,7 @@ "imageflow.net": { "type": "Project", "dependencies": { - "Microsoft.IO.RecyclableMemoryStream": "[2.*, 4.0.0)", + "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", "System.Text.Json": "[6.*, )" } } diff --git a/src/Imageflow/Bindings/ImageflowMethods.cs b/src/Imageflow/Bindings/ImageflowMethods.cs new file mode 100644 index 0000000..9ccaf2d --- /dev/null +++ b/src/Imageflow/Bindings/ImageflowMethods.cs @@ -0,0 +1,8 @@ +namespace Imageflow.Bindings; + +internal static class ImageflowMethods +{ + internal const string Execute = "v0.1/execute"; + internal const string GetVersionInfo = "v1/get_version_info"; + internal const string GetImageInfo = "v0.1/get_image_info"; +} \ No newline at end of file diff --git a/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs b/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs index 48dfc6a..067432a 100644 --- a/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs +++ b/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs @@ -1,17 +1,32 @@ -namespace Imageflow.Bindings +using System.Runtime.InteropServices; +using Imageflow.Internal.Helpers; + +namespace Imageflow.Bindings { /// /// An UnmanagedMemoryStream that checks that the underlying Imageflow context isn't in a disposed or errored state /// - /// + /// \ + /// + [Obsolete("This class will be removed in a future version; it has no benefit over Memory and IMemoryOwner")] public sealed class ImageflowUnmanagedReadStream : UnmanagedMemoryStream { private readonly IAssertReady _underlying; + private SafeHandle? _handle; + private int _handleReferenced; - internal unsafe ImageflowUnmanagedReadStream(IAssertReady underlying, IntPtr buffer, UIntPtr length) : base( (byte*)buffer.ToPointer(), (long)length.ToUInt64(), (long)length.ToUInt64(), FileAccess.Read) + internal unsafe ImageflowUnmanagedReadStream(IAssertReady underlying, SafeHandle handle, IntPtr buffer, UIntPtr length) : base( (byte*)buffer.ToPointer(), (long)length.ToUInt64(), (long)length.ToUInt64(), FileAccess.Read) { _underlying = underlying; + _handle = handle; + var addRefSucceeded = false; + _handle.DangerousAddRef(ref addRefSucceeded); + _handleReferenced = addRefSucceeded ? 1 : 0; + if (!addRefSucceeded) + { + throw new ArgumentException("SafeHandle.DangerousAddRef failed", nameof(handle)); + } } private void CheckSafe() @@ -53,5 +68,15 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio CheckSafe(); return base.CopyToAsync(destination, bufferSize, cancellationToken); } + + protected override void Dispose(bool disposing) + { + // Interlocked exchange to only release ref once + if (1 == Interlocked.Exchange(ref _handleReferenced, 0)) + { + _handle?.DangerousRelease(); + _handle = null; + } + } } } diff --git a/src/Imageflow/Bindings/JobContext.cs b/src/Imageflow/Bindings/JobContext.cs index 06ad135..52470a5 100644 --- a/src/Imageflow/Bindings/JobContext.cs +++ b/src/Imageflow/Bindings/JobContext.cs @@ -1,33 +1,39 @@ +using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; -using System.Text; using System.Text.Json; using System.Text.Json.Nodes; +using Imageflow.Fluent; +using Imageflow.Internal.Helpers; namespace Imageflow.Bindings { - public sealed class JobContext: CriticalFinalizerObject, IDisposable, IAssertReady + public sealed class JobContext : CriticalFinalizerObject, IDisposable, IAssertReady { private readonly JobContextHandle _handle; - private List? _pinned; + private List? _pinnedMemory; private List? _toDispose; private JobContextHandle Handle { get { - if (!_handle.IsValid) throw new ObjectDisposedException("Imageflow JobContext"); + if (!_handle.IsValid) throw new ObjectDisposedException("Imageflow JobContext"); return _handle; } } - private enum IoKind { InputBuffer, OutputBuffer} + + private enum IoKind + { + InputBuffer, + OutputBuffer + } internal bool IsInput(int ioId) => _ioSet.ContainsKey(ioId) && _ioSet[ioId] == IoKind.InputBuffer; internal bool IsOutput(int ioId) => _ioSet.ContainsKey(ioId) && _ioSet[ioId] == IoKind.OutputBuffer; internal int LargestIoId => _ioSet.Keys.DefaultIfEmpty().Max(); - + private readonly Dictionary _ioSet = new Dictionary(); public JobContext() @@ -35,27 +41,28 @@ public JobContext() _handle = new JobContextHandle(); } - private void AddPinnedData(GCHandle handle) + private void AddPinnedData(MemoryHandle handle) { - _pinned ??= new List(); - _pinned.Add(handle); + _pinnedMemory ??= []; + _pinnedMemory.Add(handle); } public bool HasError => NativeMethods.imageflow_context_has_error(Handle); - + [Obsolete("Use SerializeNode instead for AOT compatibility")] - + [RequiresUnreferencedCode("Use SerializeNode instead for AOT compatibility")] [RequiresDynamicCode("Use SerializeNode instead for AOT compatibility")] - internal static byte[] SerializeToJson(T obj){ + private static byte[] ObsoleteSerializeToJson(T obj) + { // Use System.Text.Json for serialization var options = new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, - #if NET8_0_OR_GREATER +#if NET8_0_OR_GREATER PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower - #endif +#endif }; var ms = new MemoryStream(); var utf8JsonWriter = new Utf8JsonWriter(ms, new JsonWriterOptions @@ -66,7 +73,20 @@ internal static byte[] SerializeToJson(T obj){ utf8JsonWriter.Flush(); return ms.ToArray(); } - internal static byte[] SerializeNode(JsonNode node, bool indented = true){ + + internal static void WriteSerializedNode(IBufferWriter bufferWriter, JsonNode node, bool indented = true) + { + // Use System.Text.Json for serialization + using var utf8JsonWriter = new Utf8JsonWriter(bufferWriter, new JsonWriterOptions + { + Indented = indented + }); + node.WriteTo(utf8JsonWriter); + // flushes on disposal + } + + internal static byte[] SerializeNode(JsonNode node, bool indented = true) + { // Use System.Text.Json for serialization var ms = new MemoryStream(); var utf8JsonWriter = new Utf8JsonWriter(ms, new JsonWriterOptions @@ -77,138 +97,238 @@ internal static byte[] SerializeNode(JsonNode node, bool indented = true){ utf8JsonWriter.Flush(); return ms.ToArray(); } - - + + [Obsolete("Use SendMessage(JsonNode) instead for AOT compatibility")] [RequiresUnreferencedCode("Use SendMessage(string method, JsonNode message) instead for AOT compatibility")] [RequiresDynamicCode("Use SendMessage(string method, JsonNode message) instead for AOT compatibility")] - public IJsonResponseProvider SendMessage(string method, T message){ - AssertReady(); - return SendJsonBytes(method, SerializeToJson(message)); - } - public IJsonResponseProvider SendMessage(string method, JsonNode message){ + public IJsonResponseProvider SendMessage(string method, T message) + { AssertReady(); - return SendJsonBytes(method, SerializeNode(message)); + return InvokeInternal(method, ObsoleteSerializeToJson(message)); } - + [Obsolete("Use ExecuteJsonNode instead for AOT compatibility")] [RequiresUnreferencedCode("Use ExecuteJsonNode instead for AOT compatibility")] [RequiresDynamicCode("Use ExecuteJsonNode instead for AOT compatibility")] - public IJsonResponseProvider Execute(T message){ + public IJsonResponseProvider Execute(T message) + { AssertReady(); - return SendJsonBytes("v0.1/execute", SerializeToJson(message)); + return InvokeInternal(ImageflowMethods.Execute, ObsoleteSerializeToJson(message)); } - public IJsonResponseProvider ExecuteJsonNode(JsonNode message){ + + [Obsolete("Use Invoke(string method, JsonNode message) instead.")] + public IJsonResponseProvider SendMessage(string method, JsonNode message){ AssertReady(); - return SendJsonBytes("v0.1/execute", SerializeNode(message)); + return InvokeInternal(method, message); } + - internal IJsonResponseProvider Execute(byte[] utf8Message){ + [Obsolete("Use .InvokeExecute(JsonNode message) instead")] + public IJsonResponseProvider ExecuteJsonNode(JsonNode message){ AssertReady(); - return SendJsonBytes("v0.1/execute", utf8Message); + return InvokeInternal(ImageflowMethods.Execute, message); } + + [Obsolete("Use .Invoke(string method, ReadOnlySpan utf8Json) instead")] + public IJsonResponseProvider SendJsonBytes(string method, byte[] utf8Json) + => InvokeInternal(method, utf8Json.AsSpan()); + public ImageInfo GetImageInfo(int ioId) { - AssertReady(); - using (var response = SendJsonBytes("v0.1/get_image_info", SerializeNode(new JsonObject(){ {"io_id", ioId} }))) + var node = InvokeAndParse(ImageflowMethods.GetImageInfo, new JsonObject() { { "io_id", ioId } }); + if (node == null) throw new ImageflowAssertionFailed("get_image_info response is null"); + var responseObj = node.AsObject(); + if (responseObj == null) throw new ImageflowAssertionFailed("get_image_info response is not an object"); + if (responseObj.TryGetPropertyValue("success", out var successValue)) { - var node = response.DeserializeJsonNode(); - if (node == null) throw new ImageflowAssertionFailed("get_image_info response is null"); - var responseObj = node.AsObject(); - if (responseObj == null) throw new ImageflowAssertionFailed("get_image_info response is not an object"); - if (responseObj.TryGetPropertyValue("success", out var successValue)) + if (successValue?.GetValue() != true) { - if (successValue?.GetValue() != true) - { - throw ImageflowException.FromContext(Handle); - } - var dataValue = responseObj.TryGetPropertyValue("data", out var dataValueObj) ? dataValueObj : null; - if (dataValue == null) throw new ImageflowAssertionFailed("get_image_info response does not have a data property"); - var imageInfoValue = (dataValue.AsObject().TryGetPropertyValue("image_info", out var imageInfoValueObj)) ? imageInfoValueObj : null; - - if (imageInfoValue == null) throw new ImageflowAssertionFailed("get_image_info response does not have an image_info property"); - return ImageInfo.FromDynamic(imageInfoValue); + throw ImageflowException.FromContext(Handle); } - else - { - throw new ImageflowAssertionFailed("get_image_info response does not have a success property"); - } - - // - // var responseDynamic = response.DeserializeDynamic(); - // if (responseDynamic?.success.Value == true) - // { - // return ImageInfo.FromDynamic(responseDynamic.data.image_info); - // } - // else - // { - // throw ImageflowException.FromContext(this.Handle); - // } + + var dataValue = responseObj.TryGetPropertyValue("data", out var dataValueObj) ? dataValueObj : null; + if (dataValue == null) + throw new ImageflowAssertionFailed("get_image_info response does not have a data property"); + var imageInfoValue = + (dataValue.AsObject().TryGetPropertyValue("image_info", out var imageInfoValueObj)) + ? imageInfoValueObj + : null; + + if (imageInfoValue == null) + throw new ImageflowAssertionFailed( + "get_image_info response does not have an image_info property"); + return ImageInfo.FromDynamic(imageInfoValue); + } + else + { + throw new ImageflowAssertionFailed("get_image_info response does not have a success property"); } } public VersionInfo GetVersionInfo() { AssertReady(); - using (var response = SendJsonBytes("v1/get_version_info", SerializeNode(new JsonObject()))) + + var node = InvokeAndParse(ImageflowMethods.GetVersionInfo); + if (node == null) throw new ImageflowAssertionFailed("get_version_info response is null"); + var responseObj = node.AsObject(); + if (responseObj == null) + throw new ImageflowAssertionFailed("get_version_info response is not an object"); + if (responseObj.TryGetPropertyValue("success", out var successValue)) { - var node = response.DeserializeJsonNode(); - if (node == null) throw new ImageflowAssertionFailed("get_version_info response is null"); - var responseObj = node.AsObject(); - if (responseObj == null) throw new ImageflowAssertionFailed("get_version_info response is not an object"); - if (responseObj.TryGetPropertyValue("success", out var successValue)) + if (successValue?.GetValue() != true) { - if (successValue?.GetValue() != true) - { - throw ImageflowException.FromContext(Handle); - } - var dataValue = responseObj.TryGetPropertyValue("data", out var dataValueObj) ? dataValueObj : null; - if (dataValue == null) throw new ImageflowAssertionFailed("get_version_info response does not have a data property"); - var versionInfoValue = (dataValue.AsObject().TryGetPropertyValue("version_info", out var versionInfoValueObj)) ? versionInfoValueObj : null; - - if (versionInfoValue == null) throw new ImageflowAssertionFailed("get_version_info response does not have an version_info property"); - return VersionInfo.FromNode(versionInfoValue); + throw ImageflowException.FromContext(Handle); } - else - { - throw new ImageflowAssertionFailed("get_version_info response does not have a success property"); - } - + + var dataValue = responseObj.TryGetPropertyValue("data", out var dataValueObj) ? dataValueObj : null; + if (dataValue == null) + throw new ImageflowAssertionFailed("get_version_info response does not have a data property"); + var versionInfoValue = + (dataValue.AsObject().TryGetPropertyValue("version_info", out var versionInfoValueObj)) + ? versionInfoValueObj + : null; + + if (versionInfoValue == null) + throw new ImageflowAssertionFailed( + "get_version_info response does not have an version_info property"); + return VersionInfo.FromNode(versionInfoValue); + } + else + { + throw new ImageflowAssertionFailed("get_version_info response does not have a success property"); } } + + + + public IJsonResponse Invoke(string method, ReadOnlySpan utf8Json) + { + return InvokeInternal(method, utf8Json); + } + public IJsonResponse Invoke(string method) + { + return InvokeInternal(method, "{}"u8); + } + + public JsonNode? InvokeAndParse(string method, JsonNode message) + { + using var response = InvokeInternal(method, message); + return response.Parse(); + } + public JsonNode? InvokeAndParse(string method) + { + using var response = InvokeInternal(method); + return response.Parse(); + } - public IJsonResponseProvider SendJsonBytes(string method, byte[] utf8Json) + + public IJsonResponse InvokeExecute(JsonNode message) { AssertReady(); - var pinnedJson = GCHandle.Alloc(utf8Json, GCHandleType.Pinned); - var methodPinned = GCHandle.Alloc(Encoding.ASCII.GetBytes($"{method}\0"), GCHandleType.Pinned); - try + return InvokeInternal(ImageflowMethods.Execute, message); + } + + private ImageflowJsonResponse InvokeInternal(string method) + { + AssertReady(); + return InvokeInternal(method, "{}"u8); + } + + + private ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTerminatedMethod, JsonNode message) + { + AssertReady(); +#if NETSTANDARD2_1_OR_GREATER + // TODO: Use ArrayPoolBufferWriter instead? Adds CommunityToolkit.HighPerformance dependency + var writer = new ArrayBufferWriter(4096); + var utf8JsonWriter = new Utf8JsonWriter(writer, new JsonWriterOptions { - AssertReady(); - var ptr = NativeMethods.imageflow_context_send_json(Handle, methodPinned.AddrOfPinnedObject(), pinnedJson.AddrOfPinnedObject(), - new UIntPtr((ulong) utf8Json.LongLength)); - // check HasError, throw exception with our input JSON too - if (HasError) throw ImageflowException.FromContext(Handle, 2048, "JSON:\n" + Encoding.UTF8.GetString(utf8Json)); - - AssertReady(); - return new JsonResponse(new JsonResponseHandle(_handle, ptr)); + Indented = true + }); + message.WriteTo(utf8JsonWriter); + utf8JsonWriter.Flush(); + return InvokeInternal(nullTerminatedMethod, writer.WrittenSpan); +#else + + // Use System.Text.Json for serialization + var ms = new MemoryStream(); + var utf8JsonWriter = new Utf8JsonWriter(ms, new JsonWriterOptions + { + Indented = true + }); + message.WriteTo(utf8JsonWriter); + utf8JsonWriter.Flush(); + return ms.TryGetBufferSliceAllWrittenData(out var buffer) ? + InvokeInternal(nullTerminatedMethod, buffer) : + InvokeInternal(nullTerminatedMethod, ms.ToArray()); + +#endif + } + + private ImageflowJsonResponse InvokeInternal(string method, JsonNode message) + { + AssertReady(); + var methodBuffer = method.Length < 128 ? stackalloc byte[method.Length + 1] : new byte[method.Length + 1]; + if (!TextHelpers.TryEncodeAsciiNullTerminated(method.AsSpan(), methodBuffer, out var nullTerminatedBytes)) + { + throw new ArgumentException("Method must only contain ASCII characters", nameof(method)); } - finally + return InvokeInternal(nullTerminatedBytes, message); + } + + private ImageflowJsonResponse InvokeInternal(string method, ReadOnlySpan utf8Json) + { + AssertReady(); + var methodBuffer = method.Length < 128 ? stackalloc byte[method.Length + 1] : new byte[method.Length + 1]; + if (!TextHelpers.TryEncodeAsciiNullTerminated(method.AsSpan(), methodBuffer, out var nullTerminatedBytes)) { - pinnedJson.Free(); - methodPinned.Free(); + throw new ArgumentException("Method must only contain ASCII characters", nameof(method)); } + return InvokeInternal(nullTerminatedBytes, utf8Json); } + private unsafe ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTerminatedMethod, ReadOnlySpan utf8Json) + { + if (utf8Json.Length < 0) throw new ArgumentException("utf8Json cannot be empty", nameof(utf8Json)); + if (nullTerminatedMethod.Length == 0) throw new ArgumentException("Method cannot be empty", nameof(nullTerminatedMethod)); + if (nullTerminatedMethod[^1] != 0) throw new ArgumentException("Method must be null terminated", nameof(nullTerminatedMethod)); + fixed (byte* methodPtr = nullTerminatedMethod) + { + fixed (byte* jsonPtr = utf8Json) + { + AssertReady(); + var ptr = NativeMethods.imageflow_context_send_json(Handle, new IntPtr(methodPtr), new IntPtr(jsonPtr), + new UIntPtr((ulong) utf8Json.Length)); + // check HasError, throw exception with our input JSON too + if (HasError) throw ImageflowException.FromContext(Handle, 2048, "JSON:\n" + TextHelpers.Utf8ToString(utf8Json)); + + AssertReady(); + return new ImageflowJsonResponse(new JsonResponseHandle(_handle, ptr)); + } + } + } + + + + public void AssertReady() { if (!_handle.IsValid) throw new ObjectDisposedException("Imageflow JobContext"); if (HasError) throw ImageflowException.FromContext(Handle); } - - public IJsonResponseProvider ExecuteImageResizer4CommandString( int inputId, int outputId, string commands) + + [Obsolete("Obsolete: use the Fluent API instead")] + public IJsonResponseProvider ExecuteImageResizer4CommandString(int inputId, int outputId, string commands) + { + AssertReady(); + return ExecuteImageResizer4CommandStringInternal(inputId, outputId, commands); + } + internal ImageflowJsonResponse ExecuteImageResizer4CommandStringInternal( int inputId, int outputId, string commands) { - // var message = new + + // var message = new // { // framewise = new // { @@ -247,83 +367,168 @@ public IJsonResponseProvider ExecuteImageResizer4CommandString( int inputId, int }} }; - return ExecuteJsonNode( message); - } - - - - internal void AddToDisposeQueue(IDisposable d) - { - _toDispose ??= new List(1); - _toDispose.Add(d); + return InvokeInternal(ImageflowMethods.Execute, message); } + /// + /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. + /// + /// + /// public void AddInputBytes(int ioId, byte[] buffer) { - AddInputBytes(ioId, buffer, 0, buffer.LongLength); + AddInputBytes(ioId, buffer.AsSpan()); } + /// + /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. + /// + /// + /// + /// public void AddInputBytes(int ioId, ArraySegment buffer) { if (buffer.Array == null) throw new ArgumentNullException(nameof(buffer), "Array cannot be null"); AddInputBytes(ioId, buffer.Array, buffer.Offset, buffer.Count); } + /// + /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. + /// + /// + /// + /// + /// + /// + /// public void AddInputBytes(int ioId, byte[] buffer, long offset, long count) { - AssertReady(); + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + if (offset > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(offset), "offset must be less than or equal to int.MaxValue"); + if (count > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(count), " count must be less than or equal to int.MaxValue"); if (offset < 0 || offset > buffer.LongLength - 1) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be within array bounds"); if (count < 0 || offset + count > buffer.LongLength) throw new ArgumentOutOfRangeException(nameof(count), count, "offset + count must be within array bounds. count cannot be negative"); + + AddInputBytes(ioId, buffer.AsSpan((int)offset, (int)count)); + } + /// + /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. + /// + /// + /// + /// + /// + public void AddInputBytes(int ioId, ReadOnlySpan data) + { + AssertReady(); if (ContainsIoId(ioId)) throw new ArgumentException($"ioId {ioId} already in use", nameof(ioId)); - var fixedBytes = GCHandle.Alloc(buffer, GCHandleType.Pinned); - try + var length = (ulong) data.Length; + unsafe { - var addr = new IntPtr(fixedBytes.AddrOfPinnedObject().ToInt64() + offset); - - if (!NativeMethods.imageflow_context_add_input_buffer(Handle, ioId, addr, new UIntPtr((ulong) count), - NativeMethods.Lifetime.OutlivesFunctionCall)) + fixed (byte* ptr = data) { - AssertReady(); - throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); + // OutlivesFunctionCall tells imageflow to copy the data. + if (!NativeMethods.imageflow_context_add_input_buffer(Handle, ioId, new IntPtr(ptr), + new UIntPtr(length), + NativeMethods.Lifetime.OutlivesFunctionCall)) + { + AssertReady(); + throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); + } + + _ioSet.Add(ioId, IoKind.InputBuffer); } - _ioSet.Add(ioId, IoKind.InputBuffer); - } finally{ - fixedBytes.Free(); } } - + /// + /// Pins the given data in managed memory and gives Imageflow a pointer to it. The data must not be modified until after the job is disposed. + /// + /// + /// + /// public void AddInputBytesPinned(int ioId, byte[] buffer) { - AddInputBytesPinned(ioId, buffer, 0, buffer.LongLength); + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + AddInputBytesPinned(ioId, new ReadOnlyMemory(buffer), MemoryLifetimePromise.MemoryIsOwnedByRuntime); } + /// + /// Pins the given data in managed memory and gives Imageflow a pointer to it. The data must not be modified until after the job is disposed. + /// + /// + /// + /// public void AddInputBytesPinned(int ioId, ArraySegment buffer) { - if (buffer.Array == null) throw new ArgumentNullException(nameof(buffer), "Array cannot be null"); + if (buffer.Array == null) throw new ArgumentNullException(nameof(buffer)); AddInputBytesPinned(ioId, buffer.Array, buffer.Offset, buffer.Count); } + /// + /// Pins the given data in managed memory and gives Imageflow a pointer to it. The data must not be modified until after the job is disposed. + /// + /// + /// + /// + /// + /// + /// public void AddInputBytesPinned(int ioId, byte[] buffer, long offset, long count) { - AssertReady(); + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + if (offset > int.MaxValue) + throw new ArgumentOutOfRangeException(nameof(offset), "offset must be less than or equal to int.MaxValue"); + if (count > int.MaxValue) + throw new ArgumentOutOfRangeException(nameof(count), " count must be less than or equal to int.MaxValue"); + if (offset < 0 || offset > buffer.LongLength - 1) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be within array bounds"); if (count < 0 || offset + count > buffer.LongLength) throw new ArgumentOutOfRangeException(nameof(count), count, "offset + count must be within array bounds. count cannot be negative"); + + + var rom = new ReadOnlyMemory(buffer, (int)offset, (int)count); + AddInputBytesPinned(ioId, rom, MemoryLifetimePromise.MemoryIsOwnedByRuntime); + } + + /// + /// Pines the given Memory and gives Imageflow a pointer to it. You must promise that the + /// memory will remain valid until after the JobContext is disposed. + /// + /// + /// + /// + /// + /// + public unsafe void AddInputBytesPinned(int ioId, ReadOnlyMemory data, MemoryLifetimePromise callerPromise) + { + if (callerPromise == MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource) + throw new ArgumentException("callerPromise cannot be MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource", nameof(callerPromise)); + AssertReady(); if (ContainsIoId(ioId)) throw new ArgumentException($"ioId {ioId} already in use", nameof(ioId)); + + var pinned = data.Pin(); + try + { + var length = (ulong)data.Length; + AddPinnedData(pinned); - var fixedBytes = GCHandle.Alloc(buffer, GCHandleType.Pinned); - AddPinnedData(fixedBytes); + var addr = new IntPtr(pinned.Pointer); + if (!NativeMethods.imageflow_context_add_input_buffer(Handle, ioId, addr, new UIntPtr(length), + NativeMethods.Lifetime.OutlivesContext)) + { + AssertReady(); + throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); + } - var addr = new IntPtr(fixedBytes.AddrOfPinnedObject().ToInt64() + offset); - if (!NativeMethods.imageflow_context_add_input_buffer(Handle, ioId, addr, new UIntPtr((ulong) count), - NativeMethods.Lifetime.OutlivesContext)) + _ioSet.Add(ioId, IoKind.InputBuffer); + }catch { - AssertReady(); - throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); + _pinnedMemory?.Remove(pinned); + pinned.Dispose(); + throw; } - _ioSet.Add(ioId, IoKind.InputBuffer); } - + public void AddOutputBuffer(int ioId) { @@ -344,6 +549,7 @@ public void AddOutputBuffer(int ioId) /// Stream is not valid after the JobContext is disposed /// /// + [Obsolete("Use a higher-level wrapper like the Fluent API instead; they can use faster code paths")] public Stream GetOutputBuffer(int ioId) { if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) @@ -357,14 +563,74 @@ public Stream GetOutputBuffer(int ioId) AssertReady(); throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); } - return new ImageflowUnmanagedReadStream(this, buffer, bufferSize); + return new ImageflowUnmanagedReadStream(this, this._handle, buffer, bufferSize); + } + + /// + /// The memory remains valid only until the JobContext is disposed. + /// + /// + /// + /// + /// + internal unsafe Span BorrowOutputBuffer(int ioId){ + if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) + { + throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); + } + AssertReady(); + if (!NativeMethods.imageflow_context_get_output_buffer_by_id(Handle, ioId, out var buffer, + out var bufferSize)) + { + AssertReady(); + throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); + } + return new Span((void*)buffer, (int)bufferSize); + } + + /// + /// Returns an IMemoryOwner<byte> that will keep the Imageflow Job in memory until both it and the JobContext are disposed. + /// The memory should be treated as read-only. + /// + /// + /// + /// + /// + internal IMemoryOwner BorrowOutputBufferMemoryAndAddReference(int ioId){ + if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) + { + throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); + } + AssertReady(); + if (!NativeMethods.imageflow_context_get_output_buffer_by_id(Handle, ioId, out var buffer, + out var bufferSize)) + { + AssertReady(); + throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); + } + return SafeHandleMemoryManager.BorrowFromHandle(_handle, buffer, (uint)bufferSize); + } + private int _refCount; + internal void AddRef() + { + Interlocked.Increment(ref _refCount); + } + + internal void RemoveRef() + { + Interlocked.Decrement(ref _refCount); } public bool IsDisposed => !_handle.IsValid; public void Dispose() { if (IsDisposed) throw new ObjectDisposedException("Imageflow JobContext"); + + if (Interlocked.Exchange(ref _refCount, 0) > 0) + { + throw new InvalidOperationException("Cannot dispose a JobContext that is still in use. "); + } // Do not allocate or throw exceptions unless (disposing) Exception? e = null; @@ -387,22 +653,25 @@ public void Dispose() if (e != null) throw e; } } - + private void UnpinAll() { - //Unpin GCHandles - if (_pinned == null) return; - - foreach (var active in _pinned) + //Unpin + if (_pinnedMemory != null) { - if (active.IsAllocated) active.Free(); + var toDispose = _pinnedMemory; + _pinnedMemory = null; + foreach (var active in toDispose) + { + active.Dispose(); + } } - _pinned = null; } ~JobContext() { //Don't dispose managed objects; they have their own finalizers + // _handle specifically handles it's own disposal and finalizer UnpinAll(); } diff --git a/src/Imageflow/Bindings/JobContextHandle.cs b/src/Imageflow/Bindings/JobContextHandle.cs index c3215cc..66da70a 100644 --- a/src/Imageflow/Bindings/JobContextHandle.cs +++ b/src/Imageflow/Bindings/JobContextHandle.cs @@ -1,11 +1,14 @@ using System.Runtime.ConstrainedExecution; +using Imageflow.Internal.Helpers; using Microsoft.Win32.SafeHandles; namespace Imageflow.Bindings { /// - /// The handle is ready even if there is an error condition stored in the context + /// The handle is ready even if there is an error condition stored in the context. + /// + /// AddRef and Release should be called. /// internal sealed class JobContextHandle : SafeHandleZeroOrMinusOneIsInvalid, IAssertReady { diff --git a/src/Imageflow/Bindings/JsonResponse.cs b/src/Imageflow/Bindings/JsonResponse.cs index c08cf4b..a655075 100644 --- a/src/Imageflow/Bindings/JsonResponse.cs +++ b/src/Imageflow/Bindings/JsonResponse.cs @@ -1,19 +1,82 @@ -using System.Text; +using System.Buffers; +using System.Text; using System.Text.Json.Nodes; +using Imageflow.Internal.Helpers; using Utf8JsonReader = System.Text.Json.Utf8JsonReader; namespace Imageflow.Bindings { + [Obsolete("Use Imageflow.Bindings.IJsonResponse instead")] public interface IJsonResponseProvider : IDisposable { + [Obsolete("Do not read the JSON directly; utilize helper methods instead")] Stream GetStream(); } + + public interface IJsonResponse: IDisposable + { + public int ImageflowErrorCode { get; } + public string CopyString(); + public JsonNode? Parse(); + public byte[] CopyBytes(); + } + + internal interface IJsonResponseSpanProvider : IDisposable + { + /// + /// This data is only valid until the JsonResponse or JobContext is disposed. + /// + /// + ReadOnlySpan BorrowBytes(); + } + + internal sealed class MemoryJsonResponse : IJsonResponse, IJsonResponseSpanProvider + { + internal MemoryJsonResponse(int statusCode, ReadOnlyMemory memory) + { + ImageflowErrorCode = statusCode; + _memory = memory; + } + + private readonly ReadOnlyMemory _memory; + + public int ImageflowErrorCode { get; } + + public string CopyString() + { + return _memory.Span.Utf8ToString(); + } + + public JsonNode? Parse() + { + return _memory.Span.ParseJsonNode(); + } + + public byte[] CopyBytes() + { + return _memory.ToArray(); + } + + public ReadOnlySpan BorrowBytes() + { + return _memory.Span; + } + + public void Dispose() + { + // no-op + } + } + /// /// Readable even if the JobContext is in an error state. /// - internal sealed class JsonResponse : IJsonResponseProvider, IAssertReady +#pragma warning disable CS0618 // Type or member is obsolete + internal sealed class ImageflowJsonResponse : IJsonResponseProvider, IAssertReady, IJsonResponseSpanProvider, IJsonResponse +#pragma warning restore CS0618 // Type or member is obsolete { private readonly JsonResponseHandle _handle; + private int? _statusCode; private JsonResponseHandle Handle { @@ -24,9 +87,8 @@ private JsonResponseHandle Handle } } - internal JsonResponse(JsonResponseHandle ptr) + internal ImageflowJsonResponse(JsonResponseHandle ptr) { - ptr.ParentContext.AssertReady(); ptr.AssertReady(); _handle = ptr; @@ -46,16 +108,37 @@ private void Read(out int statusCode, out IntPtr utf8Buffer, out UIntPtr bufferS AssertReady(); } - public int GetStatusCode() + + + /// + /// The stream will become invalid if the JsonResponse or JobContext is disposed. + /// + /// + [Obsolete("Do not read the JSON directly; utilize helper methods instead")] + public Stream GetStream() { - Read(out var statusCode, out var _, out var _); - return statusCode; + Read(out var _, out var utf8Buffer, out var bufferSize); + return new ImageflowUnmanagedReadStream(this, _handle, utf8Buffer, bufferSize); } + + public unsafe ReadOnlySpan BorrowBytes() + { + unsafe + { + Read(out var _, out var utf8Buffer, out var bufferSize); + if (utf8Buffer == IntPtr.Zero) return ReadOnlySpan.Empty; + if (bufferSize == UIntPtr.Zero) return ReadOnlySpan.Empty; + if (bufferSize.ToUInt64() > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(bufferSize)); - public Stream GetStream() + return new ReadOnlySpan((void*)utf8Buffer, (int)bufferSize); + } + } + + public MemoryManager BorrowMemory() { Read(out var _, out var utf8Buffer, out var bufferSize); - return new ImageflowUnmanagedReadStream(this, utf8Buffer, bufferSize); + if (bufferSize.ToUInt64() > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(bufferSize)); + return SafeHandleMemoryManager.BorrowFromHandle(_handle, utf8Buffer, (uint)bufferSize); } @@ -63,16 +146,43 @@ public Stream GetStream() public void Dispose() { - var e = _handle.DisposeAllowingException(); - if (e != null) throw e; + _handle.Dispose(); + } + + + public int GetStatusCode() + { + Read(out var statusCode, out var _, out var _); + return statusCode; + } + + public int ImageflowErrorCode => _statusCode ??= GetStatusCode(); + public string CopyString() + { + return BorrowBytes().Utf8ToString(); } + public JsonNode? Parse() + { + return BorrowBytes().ParseJsonNode(); + } + + public byte[] CopyBytes() + { + return BorrowBytes().ToArray(); + } } // ReSharper disable once InconsistentNaming public static class IJsonResponseProviderExtensions { - + internal static JsonNode? ParseJsonNode(this ReadOnlySpan buffer) + { + + var reader3 = new Utf8JsonReader(buffer); + return JsonNode.Parse(ref reader3); + } + // [Obsolete("Use DeserializeJsonNode() instead")] // public static T? Deserialize(this IJsonResponseProvider p) where T : class @@ -96,21 +206,33 @@ public static class IJsonResponseProviderExtensions // //return JsonSerializer.Create().Deserialize(new JsonTextReader(reader)); // } + [Obsolete("Use IJsonResponse.Parse()? instead")] public static JsonNode? DeserializeJsonNode(this IJsonResponseProvider p) { + if (p is IJsonResponseSpanProvider j) + { + var reader3 = new Utf8JsonReader(j.BorrowBytes()); + return JsonNode.Parse(ref reader3); + } + +#pragma warning disable CS0618 // Type or member is obsolete using var reader = new StreamReader(p.GetStream(), Encoding.UTF8); +#pragma warning restore CS0618 // Type or member is obsolete var json = reader.ReadToEnd(); var reader2 = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); return JsonNode.Parse(ref reader2); } - + [Obsolete("IJsonResponseProvider is deprecated; use IJsonResponse instead")] public static string GetString(this IJsonResponseProvider p) { + if (p is IJsonResponseSpanProvider j) + { + return j.BorrowBytes().Utf8ToString(); + } using var s = new StreamReader(p.GetStream(), Encoding.UTF8); return s.ReadToEnd(); } - } } diff --git a/src/Imageflow/Bindings/JsonResponseHandle.cs b/src/Imageflow/Bindings/JsonResponseHandle.cs index f99478a..4d12e91 100644 --- a/src/Imageflow/Bindings/JsonResponseHandle.cs +++ b/src/Imageflow/Bindings/JsonResponseHandle.cs @@ -1,8 +1,13 @@ using System.Runtime.ConstrainedExecution; +using Imageflow.Internal.Helpers; using Microsoft.Win32.SafeHandles; namespace Imageflow.Bindings { + /// + /// A child SafeHandle that increments the reference count on JobContextHandle when created and decrements it when disposed. + /// + /// internal sealed class JsonResponseHandle : SafeHandleZeroOrMinusOneIsInvalid, IAssertReady { public JsonResponseHandle(JobContextHandle parent, IntPtr ptr) @@ -10,9 +15,18 @@ public JsonResponseHandle(JobContextHandle parent, IntPtr ptr) { ParentContext = parent ?? throw new ArgumentNullException(nameof(parent)); SetHandle(ptr); + + var addRefSucceeded = false; + parent.DangerousAddRef(ref addRefSucceeded); + if (!addRefSucceeded) + { + throw new ArgumentException("SafeHandle.DangerousAddRef failed", nameof(parent)); + } + _handleReferenced = addRefSucceeded ? 1 : 0; } + private int _handleReferenced = 0; public JobContextHandle ParentContext { get; } public bool IsValid => !IsInvalid && !IsClosed && ParentContext.IsValid; @@ -23,23 +37,6 @@ public void AssertReady() if (!IsValid) throw new ObjectDisposedException("Imageflow JsonResponseHandle"); } - public ImageflowException? DisposeAllowingException() - { - if (!IsValid) return null; - - try - { - if (!NativeMethods.imageflow_json_response_destroy(ParentContext, DangerousGetHandle())) - { - return ImageflowException.FromContext(ParentContext); - } - } - finally - { - Dispose(); - } - return null; - } #pragma warning disable SYSLIB0004 @@ -47,7 +44,12 @@ public void AssertReady() #pragma warning restore SYSLIB0004 protected override bool ReleaseHandle() { - return !ParentContext.IsValid || NativeMethods.imageflow_json_response_destroy(ParentContext, handle); + // The base class, the caller, handles interlocked / sync and preventing multiple calls. + // We check ParentContext just in case someone went wild with DangerousRelease elsewhere. + // It's a process-ending error if ParentContext is invalid. + if (ParentContext.IsValid) NativeMethods.imageflow_json_response_destroy(ParentContext, handle); + ParentContext.DangerousRelease(); + return true; } } } \ No newline at end of file diff --git a/src/Imageflow/Fluent/BufferedStreamSource.cs b/src/Imageflow/Fluent/BufferedStreamSource.cs new file mode 100644 index 0000000..15debb3 --- /dev/null +++ b/src/Imageflow/Fluent/BufferedStreamSource.cs @@ -0,0 +1,126 @@ +using Imageflow.Internal.Helpers; +using Microsoft.IO; + +namespace Imageflow.Fluent; + +public sealed class BufferedStreamSource : IAsyncMemorySource, IMemorySource +{ + private BufferedStreamSource(Stream stream, bool disposeUnderlying, bool seekToStart = true) + { + if (stream.Position != 0 && !stream.CanSeek && seekToStart) + { + throw new ArgumentException("Stream must be seekable if seekToStart is true"); + } + var length = stream.CanSeek ? stream.Length : 0; + if (length >= int.MaxValue) throw new OverflowException("Streams cannot exceed 2GB"); + + _underlying = stream; + _disposeUnderlying = disposeUnderlying; + _seekToStart = seekToStart; + } + + private readonly bool _seekToStart; + private readonly Stream _underlying; + private readonly bool _disposeUnderlying; + + private static readonly RecyclableMemoryStreamManager Mgr + = new(); + + private RecyclableMemoryStream? _copy; + + public void Dispose() + { + if (_disposeUnderlying) + { + _underlying?.Dispose(); + } + + _copy?.Dispose(); + } + + private bool TryGetWrittenMemory( + out ReadOnlyMemory memory) + { + var memStream = _underlying as MemoryStream ?? _copy; + if (memStream != null) + { + // If it's a recyclable memory stream, it will cache the buffer no matter how many times we call it. + if (_seekToStart ? memStream.TryGetBufferSliceAllWrittenData(out var segment) + : memStream.TryGetBufferSliceAfterPosition(out segment)) + { + memory = segment; + return true; + } + throw new OverflowException("Streams cannot exceed 2GB"); + } + memory = default; + return false; + } + + /// + /// Note that bytes will not be valid after BufferedStreamSource is disposed + /// + /// + /// + /// + public async ValueTask> BorrowReadOnlyMemoryAsync(CancellationToken cancellationToken) + { + if (TryGetWrittenMemory(out var segment)) + { + return segment; + } + if (_seekToStart) + { + _underlying.Seek(0, SeekOrigin.Begin); + } + _copy = new RecyclableMemoryStream(Mgr, "BufferedStreamSource: IMemorySource", _underlying.CanSeek ? _underlying.Length : 0); + await _underlying.CopyToAsync(_copy, 81920, cancellationToken); + _copy.Seek(0, SeekOrigin.Begin); + if (!TryGetWrittenMemory(out segment)) + { + throw new InvalidOperationException("Could not get RecyclableMemoryStream buffer; please report this bug to support@imazen.io"); + } + return segment; + } + + public ReadOnlyMemory BorrowReadOnlyMemory() + { + if (TryGetWrittenMemory(out var segment)) + { + return segment; + } + if (_seekToStart) + { + _underlying.Seek(0, SeekOrigin.Begin); + } + + _copy = new RecyclableMemoryStream(Mgr, "BufferedStreamSource: IMemorySource", _underlying.CanSeek ? _underlying.Length : 0); + _underlying.CopyTo(_copy); + _copy.Seek(0, SeekOrigin.Begin); + if (!TryGetWrittenMemory(out segment)) + { + throw new InvalidOperationException("Could not get RecyclableMemoryStream buffer; please report this bug to support@imazen.io"); + } + return segment; + } + + public bool AsyncPreferred => _underlying is not MemoryStream && _underlying is not UnmanagedMemoryStream; + + + public static IAsyncMemorySource BorrowEntireStream(Stream stream) + { + return new BufferedStreamSource(stream, false, true); + } + public static IAsyncMemorySource BorrowStreamRemainder(Stream stream) + { + return new BufferedStreamSource(stream, false, false); + } + public static IAsyncMemorySource UseEntireStreamAndDispose(Stream stream) + { + return new BufferedStreamSource(stream, true, true); + } + public static IAsyncMemorySource UseStreamRemainderAndDispose(Stream stream) + { + return new BufferedStreamSource(stream, true, false); + } +} \ No newline at end of file diff --git a/src/Imageflow/Fluent/BuildJobResult.cs b/src/Imageflow/Fluent/BuildJobResult.cs index a09419f..5e9e09a 100644 --- a/src/Imageflow/Fluent/BuildJobResult.cs +++ b/src/Imageflow/Fluent/BuildJobResult.cs @@ -49,25 +49,25 @@ private BuildJobResult(IReadOnlyCollection decodeResults, /// public BuildEncodeResult? TryGet(int ioId) => _encodeResults.TryGetValue(ioId, out var result) ? result : null; - internal static BuildJobResult From(IJsonResponseProvider response, Dictionary outputs) + internal static BuildJobResult From(IJsonResponse response, Dictionary outputs) { - var v = response.DeserializeJsonNode(); - if (v == null) throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response " + response.GetString()); + var v = response.Parse(); + if (v == null) throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response " + response.CopyString()); bool? success = v.AsObject().TryGetPropertyValue("success", out var successValue) ? successValue?.GetValue() : null; switch (success) { case false: - throw new ImageflowAssertionFailed("BuildJobResult.From cannot convert a failure: " + response.GetString()); + throw new ImageflowAssertionFailed("BuildJobResult.From cannot convert a failure: " + response.CopyString()); case null: - throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response " + response.GetString()); + throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response " + response.CopyString()); } var data = v.AsObject().TryGetPropertyValue("data", out var dataValue) ? dataValue : null; - if (data == null) throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response ('data' missing) " + response.GetString()); + if (data == null) throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response ('data' missing) " + response.CopyString()); // IEnumerable encodes = (v.data.job_result ?? v.data.build_result).encodes; @@ -98,7 +98,7 @@ internal static BuildJobResult From(IJsonResponseProvider response, Dictionary(); if (!jobResult.AsObject().TryGetPropertyValue("encodes", out var encodeArray)) @@ -111,7 +111,7 @@ internal static BuildJobResult From(IJsonResponseProvider response, Dictionary /// /// + [Obsolete("Use Watermark(IMemorySource source, ..) instead. BufferedStreamSource and MemorySource are preferred over BytesSource and StreamSource.")] public BuildNode Watermark(IBytesSource source, WatermarkOptions watermark) => - Watermark(source, null, watermark); + Watermark(source.ToMemorySource(), null, watermark); + /// + /// Draw a watermark from the given BufferedStreamSource or MemorySource + /// + /// + /// + /// + public BuildNode Watermark(IAsyncMemorySource source, WatermarkOptions watermark) => + Watermark(source, null, watermark); + /// /// Draw a watermark from the given BytesSource or StreamSource /// @@ -676,7 +686,20 @@ public BuildNode Watermark(IBytesSource source, WatermarkOptions watermark) => /// /// /// + [Obsolete("Use Watermark(IMemorySource source, ..) instead. BufferedStreamSource and MemorySource are preferred over BytesSource and StreamSource.")] public BuildNode Watermark(IBytesSource source, int? ioId, WatermarkOptions watermark) + { + return Watermark(source.ToMemorySource(), ioId, watermark); + } + + /// + /// Draw a watermark from the given BufferedStreamSource or MemorySource + /// + /// + /// + /// + /// + public BuildNode Watermark(IAsyncMemorySource source, int? ioId, WatermarkOptions watermark) { ioId ??= Builder.GenerateIoId(); Builder.AddInput(ioId.Value, source); diff --git a/src/Imageflow/Fluent/BytesSourceAdapter.cs b/src/Imageflow/Fluent/BytesSourceAdapter.cs new file mode 100644 index 0000000..d8f1b32 --- /dev/null +++ b/src/Imageflow/Fluent/BytesSourceAdapter.cs @@ -0,0 +1,27 @@ +namespace Imageflow.Fluent; + +#pragma warning disable CS0618 // Type or member is obsolete +public sealed class BytesSourceAdapter(IBytesSource source) : IAsyncMemorySource, IMemorySource +#pragma warning restore CS0618 // Type or member is obsolete +{ + public void Dispose() + { + source.Dispose(); + } + + public async ValueTask> BorrowReadOnlyMemoryAsync(CancellationToken cancellationToken) + { + var bytes = await source.GetBytesAsync(cancellationToken).ConfigureAwait(false); + return new ReadOnlyMemory(bytes.Array, bytes.Offset, bytes.Count); + } + + public ReadOnlyMemory BorrowReadOnlyMemory() + { + var bytes = source.GetBytesAsync(default).Result; + return new ReadOnlyMemory(bytes.Array, bytes.Offset, bytes.Count); + } + +#pragma warning disable CS0618 // Type or member is obsolete + public bool AsyncPreferred => source is not (BytesSource or StreamSource { AsyncPreferred: true }); +#pragma warning restore CS0618 // Type or member is obsolete +} \ No newline at end of file diff --git a/src/Imageflow/Fluent/FinishJobBuilder.cs b/src/Imageflow/Fluent/FinishJobBuilder.cs index cf6e883..49f4428 100644 --- a/src/Imageflow/Fluent/FinishJobBuilder.cs +++ b/src/Imageflow/Fluent/FinishJobBuilder.cs @@ -82,7 +82,7 @@ public Task InSubprocessAsync(string? imageflowToolPath = null, public Task WriteJsonJobAndInputs(bool deleteFilesOnDispose) => _builder.WriteJsonJobAndInputs(_token, _security, deleteFilesOnDispose); - internal string ToJson(SecurityOptions? securityOptions = null) => _builder.ToJson(securityOptions); + internal string ToJsonDebug(SecurityOptions? securityOptions = null) => _builder.ToJsonDebug(securityOptions); public async Task InProcessAndDisposeAsync() { diff --git a/src/Imageflow/Fluent/IAsyncMemorySource.cs b/src/Imageflow/Fluent/IAsyncMemorySource.cs new file mode 100644 index 0000000..89225bc --- /dev/null +++ b/src/Imageflow/Fluent/IAsyncMemorySource.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; +using Imageflow.Bindings; + +namespace Imageflow.Fluent; + +public interface IAsyncMemorySource: IDisposable +{ + /// + /// Implementations should return a reference to a byte array that (at least until the IMemorySource implementation is disposed) will remain valid, immutable, and pinnable. + /// + /// + ValueTask> BorrowReadOnlyMemoryAsync(CancellationToken cancellationToken); +} + +public interface IMemorySource: IDisposable +{ + /// + /// Implementations should return a reference to a byte array that (at least until the IMemorySource implementation is disposed) will remain valid, immutable, and pinnable. + /// + /// + ReadOnlyMemory BorrowReadOnlyMemory(); + + /// + /// If true, implementations should prefer to use async methods over sync methods. + /// + bool AsyncPreferred { get; } +} \ No newline at end of file diff --git a/src/Imageflow/Fluent/IBytesSource.cs b/src/Imageflow/Fluent/IBytesSource.cs index 7886240..db73e47 100644 --- a/src/Imageflow/Fluent/IBytesSource.cs +++ b/src/Imageflow/Fluent/IBytesSource.cs @@ -1,7 +1,10 @@ -using Microsoft.IO; +using System.Buffers; +using Imageflow.Internal.Helpers; +using Microsoft.IO; namespace Imageflow.Fluent { + [Obsolete("Use IMemorySource instead")] public interface IBytesSource: IDisposable { /// @@ -10,10 +13,11 @@ public interface IBytesSource: IDisposable /// Task> GetBytesAsync(CancellationToken cancellationToken); } - + /// - /// Represents a source backed by an ArraySegment or byte[] array. + /// Represents a source backed by an ArraySegment or byte[] array. Use MemorySource for ReadOnlyMemory backed memory instead. /// + [Obsolete("Use MemorySource instead")] public readonly struct BytesSource : IBytesSource { public BytesSource(byte[] bytes) @@ -34,16 +38,25 @@ public BytesSource(ArraySegment bytes) public void Dispose() { } - public Task> GetBytesAsync(CancellationToken cancellationToken) => Task.FromResult(_bytes); + + public Task> GetBytesAsync(CancellationToken cancellationToken) + { + return Task.FromResult(_bytes); + } + public static implicit operator MemorySource(BytesSource source) + { + return new MemorySource(source._bytes); + } } /// /// Represents a image source that is backed by a Stream. /// + [Obsolete("Use BufferedStreamSource instead")] public class StreamSource(Stream underlying, bool disposeUnderlying) : IBytesSource { - private static readonly RecyclableMemoryStreamManager Mgr - = new RecyclableMemoryStreamManager(); + private static readonly RecyclableMemoryStreamManager Mgr + = new(); private RecyclableMemoryStream? _copy; public void Dispose() @@ -70,17 +83,11 @@ public async Task> GetBytesAsync(CancellationToken cancellati } var length = underlying.CanSeek ? underlying.Length : 0; if (length >= int.MaxValue) throw new OverflowException("Streams cannot exceed 2GB"); - switch (underlying) + + if (underlying is MemoryStream underlyingMemoryStream && + underlyingMemoryStream.TryGetBufferSliceAllWrittenData(out var underlyingBuffer)) { - case RecyclableMemoryStream underlyingRecMemoryStream: - return new ArraySegment(underlyingRecMemoryStream.GetBuffer(), 0, - (int) length); - case MemoryStream underlyingMemoryStream: - if (underlyingMemoryStream.TryGetBuffer(out var underlyingBuffer)) - { - return underlyingBuffer; - } - break; + return underlyingBuffer; } if (_copy == null) @@ -92,5 +99,24 @@ public async Task> GetBytesAsync(CancellationToken cancellati return new ArraySegment(_copy.GetBuffer(), 0, (int) _copy.Length); } + + internal bool AsyncPreferred => _copy != null && underlying is not MemoryStream && underlying is not UnmanagedMemoryStream; + + public static implicit operator BytesSourceAdapter(StreamSource source) + { + return new BytesSourceAdapter(source); + } } -} \ No newline at end of file + + public static class BytesSourceExtensions + { +#pragma warning disable CS0618 // Type or member is obsolete + public static IAsyncMemorySource ToMemorySource(this IBytesSource source) +#pragma warning restore CS0618 // Type or member is obsolete + { + return new BytesSourceAdapter(source); + } + } + +} + diff --git a/src/Imageflow/Fluent/IOutputDestination.cs b/src/Imageflow/Fluent/IOutputDestination.cs index 3c18835..289b318 100644 --- a/src/Imageflow/Fluent/IOutputDestination.cs +++ b/src/Imageflow/Fluent/IOutputDestination.cs @@ -1,4 +1,7 @@ -using Imageflow.Bindings; +using System.Buffers; +using System.Runtime.InteropServices; +using Imageflow.Bindings; +using Imageflow.Internal.Helpers; namespace Imageflow.Fluent { @@ -9,9 +12,92 @@ public interface IOutputDestination : IDisposable Task FlushAsync(CancellationToken cancellationToken); } + + // ReSharper disable once InconsistentNaming public static class IOutputDestinationExtensions { + internal static void AdaptiveWriteAll(this IOutputDestination dest, ReadOnlyMemory data) + { + if (dest is IOutputSink syncSink) + { + syncSink.RequestCapacity(data.Length); + syncSink.Write(data.Span); + syncSink.Flush(); + } + else + { + dest.RequestCapacityAsync(data.Length).Wait(); + dest.AdaptedWrite(data.Span); + dest.FlushAsync(default).Wait(); + } + } + + internal static async ValueTask AdaptiveWriteAllAsync(this IOutputDestination dest, ReadOnlyMemory data, CancellationToken cancellationToken) + { + if (dest is IAsyncOutputSink sink) + { + await sink.FastRequestCapacityAsync(data.Length); + await sink.FastWriteAsync(data, cancellationToken); + await sink.FastFlushAsync(cancellationToken); + return; + } + await dest.RequestCapacityAsync(data.Length); + await dest.AdaptedWriteAsync(data, cancellationToken); + await dest.FlushAsync(cancellationToken); + } + + + internal static async ValueTask AdaptedWriteAsync(this IOutputDestination dest, ReadOnlyMemory data, CancellationToken cancellationToken) + { + if (MemoryMarshal.TryGetArray(data, out ArraySegment segment)) + { + await dest.WriteAsync(segment, cancellationToken).ConfigureAwait(false); + return; + } + + var rent = ArrayPool.Shared.Rent(Math.Min(81920,data.Length)); + try + { + for (int i = 0; i < data.Length; i += rent.Length) + { + int len = Math.Min(rent.Length, data.Length - i); + data.Span.Slice(i, len).CopyTo(rent); + await dest.WriteAsync(new ArraySegment(rent, 0, len), cancellationToken).ConfigureAwait(false); + } + } + finally + { + ArrayPool.Shared.Return(rent); + } + + } + internal static void AdaptedWrite(this IOutputDestination dest, ReadOnlySpan data) + { + + var rent = ArrayPool.Shared.Rent(Math.Min(81920,data.Length)); + try + { + for (int i = 0; i < data.Length; i += rent.Length) + { + int len = Math.Min(rent.Length, data.Length - i); + data.Slice(i, len).CopyTo(rent); + dest.WriteAsync(new ArraySegment(rent, 0, len), default).Wait(); + } + } + finally + { + ArrayPool.Shared.Return(rent); + } + } + + + // internal static IAsyncOutputSink ToAsyncOutputSink(this IOutputDestination dest, bool disposeUnderlying = true) + // { + // if (dest is IAsyncOutputSink sink) return sink; + // return new OutputDestinationToSinkAdapter(dest, disposeUnderlying); + // } + public static async Task CopyFromStreamAsync(this IOutputDestination dest, Stream stream, CancellationToken cancellationToken) { @@ -35,7 +121,7 @@ await dest.WriteAsync(new ArraySegment(buffer, 0, bytesRead), cancellation } } - public class BytesDestination : IOutputDestination + public class BytesDestination : IOutputDestination, IOutputSink, IAsyncOutputSink { private MemoryStream? _m; public void Dispose() @@ -44,9 +130,7 @@ public void Dispose() public Task RequestCapacityAsync(int bytes) { - _m ??= new MemoryStream(bytes); - - if (_m.Capacity < bytes) _m.Capacity = bytes; + RequestCapacity(bytes); return Task.CompletedTask; } @@ -72,6 +156,38 @@ public ArraySegment GetBytes() } return bytes; } + + public void RequestCapacity(int bytes) + { + _m ??= new MemoryStream(bytes); + if (_m.Capacity < bytes) _m.Capacity = bytes; + } + + public void Write(ReadOnlySpan data) + { + if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.Write called before RequestCapacity"); + _m.WriteSpan(data); + } + public ValueTask FastWriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken) + { + if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.FastWriteAsync called before RequestCapacityAsync"); + return _m.WriteMemoryAsync(data, cancellationToken); + } + public void Flush() + { + _m?.Flush(); // Redundant for MemoryStream. + } + + public ValueTask FastRequestCapacityAsync(int bytes) + { + RequestCapacity(bytes); + return new ValueTask(); + } + public ValueTask FastFlushAsync(CancellationToken cancellationToken) + { + Flush(); + return new ValueTask(); + } } public class StreamDestination(Stream underlying, bool disposeUnderlying) : IOutputDestination @@ -92,6 +208,16 @@ public Task WriteAsync(ArraySegment bytes, CancellationToken cancellationT if (bytes.Array == null) throw new ImageflowAssertionFailed("StreamDestination.WriteAsync called with null array"); return underlying.WriteAsync(bytes.Array, bytes.Offset, bytes.Count, cancellationToken); } + + public ValueTask WriteAsync(ReadOnlyMemory bytes, CancellationToken cancellationToken) + { + return underlying.WriteMemoryAsync(bytes, cancellationToken); + } + + public void Write(ReadOnlySpan bytes) + { + underlying.WriteSpan(bytes); + } public Task FlushAsync(CancellationToken cancellationToken) { diff --git a/src/Imageflow/Fluent/IOutputSink.cs b/src/Imageflow/Fluent/IOutputSink.cs new file mode 100644 index 0000000..6b5bf18 --- /dev/null +++ b/src/Imageflow/Fluent/IOutputSink.cs @@ -0,0 +1,25 @@ +namespace Imageflow.Fluent; + +internal interface IOutputSink : IDisposable +{ + void RequestCapacity(int bytes); + void Write(ReadOnlySpan data); + void Flush(); +} + +internal interface IAsyncOutputSink : IDisposable +{ + ValueTask FastRequestCapacityAsync(int bytes); + ValueTask FastWriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken); + ValueTask FastFlushAsync(CancellationToken cancellationToken); +} + +internal static class OutputSinkExtensions +{ + public static async ValueTask WriteAllAsync(this IAsyncOutputSink sink, ReadOnlyMemory data, CancellationToken cancellationToken) + { + await sink.FastRequestCapacityAsync(data.Length); + await sink.FastWriteAsync(data, cancellationToken); + await sink.FastFlushAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/src/Imageflow/Fluent/ImageJob.cs b/src/Imageflow/Fluent/ImageJob.cs index 6be1d72..fad44c4 100644 --- a/src/Imageflow/Fluent/ImageJob.cs +++ b/src/Imageflow/Fluent/ImageJob.cs @@ -1,9 +1,11 @@ -using System.Collections.ObjectModel; +using System.Buffers; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using Imageflow.Bindings; +using Imageflow.Internal.Helpers; using Imageflow.IO; namespace Imageflow.Fluent @@ -14,11 +16,11 @@ public class FluentBuildJob : ImageJob; public class ImageJob: IDisposable { private bool _disposed; - private readonly Dictionary _inputs = new Dictionary(2); + private readonly Dictionary _inputs = new Dictionary(2); private readonly Dictionary _outputs = new Dictionary(2); - internal void AddInput(int ioId, IBytesSource source) + internal void AddInput(int ioId, IAsyncMemorySource source) { if (_inputs.ContainsKey(ioId) || _outputs.ContainsKey(ioId)) throw new ArgumentException("ioId", $"ioId {ioId} has already been assigned"); @@ -31,15 +33,56 @@ internal void AddOutput(int ioId, IOutputDestination destination) _outputs.Add(ioId, destination); } - /// - /// Decode an image into a frame - /// - /// Use BytesSource or StreamSource - /// Commands to the decoder, such as JPEG or WebP block-wise downscaling for performance, or to discard the color profile or ignore color profile errors - /// + [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] public BuildNode Decode(IBytesSource source, DecodeCommands commands) => - Decode(source, GenerateIoId(), commands); + Decode(source.ToMemorySource(), GenerateIoId(), commands); + + [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] + public BuildNode Decode(IBytesSource source, int ioId) => Decode(source.ToMemorySource(), ioId, null); + + [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] + public BuildNode Decode(IBytesSource source) => Decode( source, GenerateIoId()); + + + [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] public BuildNode Decode(IBytesSource source, int ioId, DecodeCommands? commands) + { + return Decode(source.ToMemorySource(), ioId, commands); + } + + public BuildNode Decode(Stream source, bool disposeStream) => Decode( source, disposeStream, GenerateIoId()); + + public BuildNode Decode(Stream source, bool disposeStream, int ioId) => + Decode( disposeStream ? BufferedStreamSource.UseEntireStreamAndDispose(source) + : BufferedStreamSource.BorrowEntireStream(source), ioId); + + + [Obsolete("Use Decode(MemorySource.Borrow(arraySegment, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed)) instead")] + public BuildNode Decode(ArraySegment source) => Decode( MemorySource.Borrow(source, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), GenerateIoId()); + + [Obsolete("Use Decode(MemorySource.Borrow(arraySegment, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), ioId) instead")] + public BuildNode Decode(ArraySegment source, int ioId) => Decode( MemorySource.Borrow(source, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), ioId); + + public BuildNode Decode(byte[] source) => Decode( new MemorySource(source), GenerateIoId()); + public BuildNode Decode(byte[] source, int ioId) => Decode( new MemorySource(source), ioId); + + public BuildNode Decode(T source) where T : IAsyncMemorySource + { + return Decode(source, GenerateIoId(), null); + } + public BuildNode Decode(T source, DecodeCommands commands) where T:IAsyncMemorySource => + Decode(source, GenerateIoId(), commands); + + public BuildNode Decode(T source, int ioId) where T : IAsyncMemorySource + { + return Decode(source, ioId, null); + } + + /// + /// Commands to the decoder, such as JPEG or WebP block-wise downscaling for performance, or to discard the color profile or ignore color profile errors + /// + /// + public BuildNode Decode(T source, int ioId, DecodeCommands? commands) where T:IAsyncMemorySource { AddInput(ioId, source); if (commands == null) @@ -66,22 +109,7 @@ public BuildNode Decode(IBytesSource source, int ioId, DecodeCommands? commands) new JsonObject() {{"decode", new JsonObject() {{"io_id", ioId}, {"commands", commands.ToJsonNode()}}}}); } - public BuildNode Decode(IBytesSource source, int ioId) => Decode(source, ioId, null); - public BuildNode Decode(IBytesSource source) => Decode( source, GenerateIoId()); - public BuildNode Decode(ArraySegment source) => Decode( new BytesSource(source), GenerateIoId()); - public BuildNode Decode(byte[] source) => Decode( new BytesSource(source), GenerateIoId()); - /// - /// Decode an image into a frame - /// - /// - public BuildNode Decode(Stream source, bool disposeStream) => Decode( new StreamSource(source, disposeStream), GenerateIoId()); - [Obsolete("Use Decode(IBytesSource source, int ioId)")] - public BuildNode Decode(ArraySegment source, int ioId) => Decode( new BytesSource(source), ioId); - [Obsolete("Use Decode(IBytesSource source, int ioId)")] - public BuildNode Decode(byte[] source, int ioId) => Decode( new BytesSource(source), ioId); - public BuildNode Decode(Stream source, bool disposeStream, int ioId) => Decode( new StreamSource(source, disposeStream), ioId); - public BuildNode CreateCanvasBgra32(uint w, uint h, AnyColor color) => CreateCanvas(w, h, color, PixelFormat.Bgra_32); @@ -99,29 +127,50 @@ private BuildNode CreateCanvas(uint w, uint h, AnyColor color, PixelFormat forma - public BuildEndpoint BuildCommandString(byte[] source, IOutputDestination dest, string commandString) => BuildCommandString(new BytesSource(source), dest, commandString); - + [Obsolete("Use overloads that take a MemorySource")] public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString) => BuildCommandString(source, null, dest, null, commandString); - - + [Obsolete("Use overloads that take a MemorySource")] public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, int? destIoId, string commandString) => BuildCommandString(source, sourceIoId, dest, destIoId, commandString, null); + + [Obsolete("Use overloads that take a MemorySource")] + public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString, + ICollection? watermarks) + => BuildCommandString(source, null, dest, null, commandString, watermarks); + [Obsolete("Use overloads that take a MemorySource")] + public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, + int? destIoId, string commandString, ICollection? watermarks) + { + return BuildCommandString(source.ToMemorySource(), sourceIoId, dest, destIoId, commandString, watermarks); + } + + public BuildEndpoint BuildCommandString(byte[] source, IOutputDestination dest, string commandString) => BuildCommandString(new MemorySource(source), dest, commandString); + /// /// Modify the input image (source) with the given command string and watermarks and encode to the (dest) /// /// /// /// - /// /// - public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString, + /// + public BuildEndpoint BuildCommandString(IAsyncMemorySource source, IOutputDestination dest, string commandString) => BuildCommandString(source, null, dest, null, commandString); + + public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIoId, IOutputDestination dest, + int? destIoId, string commandString) + => BuildCommandString(source, sourceIoId, dest, destIoId, commandString, null); + + public BuildEndpoint BuildCommandString(IAsyncMemorySource source, IOutputDestination dest, string commandString, ICollection? watermarks) => BuildCommandString(source, null, dest, null, commandString, watermarks); - public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, int? destIoId, string commandString, ICollection? watermarks) + + + + public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIoId, IOutputDestination dest, int? destIoId, string commandString, ICollection? watermarks) { sourceIoId = sourceIoId ?? GenerateIoId(); AddInput(sourceIoId.Value, source); @@ -193,33 +242,33 @@ public Task FinishAndDisposeAsync(CancellationToken cancellation => Finish().SetCancellationToken(cancellationToken).InProcessAndDisposeAsync(); - internal byte[] ToJsonUtf8(SecurityOptions? securityOptions) + internal JsonObject CreateJsonNodeForFramewiseWithSecurityOptions(SecurityOptions? securityOptions) { // var message = new // { // security = securityOptions?.ToImageflowDynamic(), // framewise = ToFramewise() // }; - var message = new JsonObject() + return new JsonObject() { ["framewise"] = ToFramewise(), ["security"] = securityOptions?.ToJsonNode() }; - return JobContext.SerializeNode(message); } - internal string ToJson(SecurityOptions? securityOptions = default) + internal string ToJsonDebug(SecurityOptions? securityOptions = default) { - return Encoding.UTF8.GetString(ToJsonUtf8(securityOptions)); + return CreateJsonNodeForFramewiseWithSecurityOptions(securityOptions).ToJsonString(); } internal async Task FinishAsync(JobExecutionOptions executionOptions, SecurityOptions? securityOptions, CancellationToken cancellationToken) { - var inputByteArrays = await Task.WhenAll(_inputs.Select( async pair => new KeyValuePair>(pair.Key, await pair.Value.GetBytesAsync(cancellationToken)))); + var inputByteArrays = await Task.WhenAll( + _inputs.Select( async pair => new KeyValuePair>(pair.Key, await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken)))); using (var ctx = new JobContext()) { foreach (var pair in inputByteArrays) - ctx.AddInputBytesPinned(pair.Key, pair.Value); + ctx.AddInputBytesPinned(pair.Key, pair.Value, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); foreach (var outId in _outputs.Keys) { @@ -227,33 +276,59 @@ internal async Task FinishAsync(JobExecutionOptions executionOpt } //TODO: Use a Semaphore to limit concurrency - var message = ToJsonUtf8(securityOptions); + var message = CreateJsonNodeForFramewiseWithSecurityOptions(securityOptions); var response = executionOptions.OffloadCpuToThreadPool - ? await Task.Run(() => ctx.Execute(message), cancellationToken) - : ctx.Execute(message); + ? await Task.Run(() => ctx.InvokeExecute(message), cancellationToken) + : ctx.InvokeExecute(message); + // TODO: Should we handle failure before copying out the buffers?? using (response) { - + foreach (var pair in _outputs) { - using (var stream = ctx.GetOutputBuffer(pair.Key)) - { - await pair.Value.CopyFromStreamAsync(stream, cancellationToken); - } + using var memOwner = ctx.BorrowOutputBufferMemoryAndAddReference(pair.Key); + await pair.Value.AdaptiveWriteAllAsync(memOwner.Memory, cancellationToken); } return BuildJobResult.From(response, _outputs); } } } - private class MemoryStreamJsonProvider : IJsonResponseProvider +#pragma warning disable CS0618 // Type or member is obsolete + private class StreamJsonSpanProvider : IJsonResponseProvider, IJsonResponseSpanProvider, IJsonResponse +#pragma warning restore CS0618 // Type or member is obsolete { private readonly MemoryStream _ms; - public MemoryStreamJsonProvider(MemoryStream ms) => _ms = ms; + + public StreamJsonSpanProvider(int statusCode, MemoryStream ms) + { + ImageflowErrorCode = statusCode; + _ms = ms; + } public void Dispose() => _ms.Dispose(); public Stream GetStream() => _ms; + public ReadOnlySpan BorrowBytes() + { + return _ms.TryGetBufferSliceAllWrittenData(out var slice) ? slice : _ms.ToArray(); + } + + public int ImageflowErrorCode { get; } + public string CopyString() + { + return BorrowBytes().Utf8ToString(); + } + + public JsonNode? Parse() + { + return BorrowBytes().ParseJsonNode(); + } + + public byte[] CopyBytes() + { + return BorrowBytes().ToArray(); + } } @@ -324,13 +399,13 @@ private async Task PrepareForSubprocessAsync(Cancellati var inputFiles = (await Task.WhenAll(_inputs.Select(async pair => { - var bytes = await pair.Value.GetBytesAsync(cancellationToken); + var bytes = await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken); - var file = job.Provider.Create(cleanupFiles, bytes.Count); + var file = job.Provider.Create(cleanupFiles, bytes.Length); job.Cleanup.Add(file); return (io_id: pair.Key, direction: "in", io: new JsonObject {{"file", file.Path}}, - bytes, Length: bytes.Count, File: file); + bytes, Length: bytes.Length, File: file); }))).ToArray(); @@ -347,11 +422,8 @@ private async Task PrepareForSubprocessAsync(Cancellati foreach (var f in inputFiles) { - using (var accessor = f.File.WriteFromBeginning()) - { - if (f.bytes.Array == null) throw new ImageflowAssertionFailed("Bytes.Array is null"); - await accessor.WriteAsync(f.bytes.Array, f.bytes.Offset, f.bytes.Count, cancellationToken); - } + using var accessor = f.File.WriteFromBeginning(); + await accessor.WriteMemoryAsync(f.bytes, cancellationToken); } // job.JobMessage = new @@ -469,9 +541,8 @@ internal async Task FinishInSubprocessAsync(SecurityOptions? sec await job.CopyOutputsToDestinations(cancellationToken); - using (var jsonProvider = new MemoryStreamJsonProvider(output)) { - return BuildJobResult.From(jsonProvider, _outputs); - } + var outputMemory = output.GetWrittenMemory(); + return BuildJobResult.From(new MemoryJsonResponse(results.ExitCode, outputMemory), _outputs); } } @@ -618,6 +689,7 @@ protected virtual void Dispose(bool disposing) /// /// /// + [Obsolete("Use GetImageInfoAsync(IMemorySource ...) instead; this method disposes the source and will be removed in a future version")] public static Task GetImageInfo(IBytesSource image) => GetImageInfo(image, CancellationToken.None); @@ -628,6 +700,7 @@ public static Task GetImageInfo(IBytesSource image) /// /// /// + [Obsolete("Use GetImageInfoAsync(IMemorySource ..) instead; this method disposes the source and will be removed in a future version")] public static async Task GetImageInfo(IBytesSource image, CancellationToken cancellationToken) { try @@ -645,6 +718,35 @@ public static async Task GetImageInfo(IBytesSource image, Cancellatio } } + /// + /// Returns dimensions and format of the provided image stream or byte array. + /// Does NOT dispose the IMemorySource. + /// + /// + /// + public static ImageInfo GetImageInfo(IMemorySource image) + { + var inputMemory = image.BorrowReadOnlyMemory(); + using var ctx = new JobContext(); + ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); + return ctx.GetImageInfo(0); + } + + /// + /// Returns dimensions and format of the provided image stream or memory. + /// Does not offload processing to a thread pool; will be CPU bound unless IMemorySource is not yet in memory. + /// Does not dispose the IMemorySource. + /// + /// + /// + /// + public static async ValueTask GetImageInfoAsync(IAsyncMemorySource image, CancellationToken cancellationToken = default) + { + var inputMemory = await image.BorrowReadOnlyMemoryAsync(cancellationToken); + using var ctx = new JobContext(); + ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); + return ctx.GetImageInfo(0); + } /// /// Returns true if it is likely that Imageflow can decode the given image based on the first 12 bytes of the file. /// diff --git a/src/Imageflow/Fluent/InputWatermark.cs b/src/Imageflow/Fluent/InputWatermark.cs index ea1b146..f06d1fd 100644 --- a/src/Imageflow/Fluent/InputWatermark.cs +++ b/src/Imageflow/Fluent/InputWatermark.cs @@ -4,20 +4,34 @@ namespace Imageflow.Fluent { public class InputWatermark { + [Obsolete("Use InputWatermark(IMemorySource source, WatermarkOptions watermark) instead")] public InputWatermark(IBytesSource source, int ioId, WatermarkOptions watermark) { - Source = source; + Source = source.ToMemorySource(); IoId = ioId; Watermark = watermark; } - + [Obsolete("Use InputWatermark(IMemorySource source, WatermarkOptions watermark) instead")] public InputWatermark(IBytesSource source, WatermarkOptions watermark) + { + Source = source.ToMemorySource(); + Watermark = watermark; + } + + public InputWatermark(IAsyncMemorySource source, WatermarkOptions watermark) { Source = source; Watermark = watermark; } - public IBytesSource Source { get; set; } + public InputWatermark(IAsyncMemorySource source, int ioId, WatermarkOptions watermark) + { + Source = source; + IoId = ioId; + Watermark = watermark; + } + + public IAsyncMemorySource Source { get; set; } public int? IoId { get; set; } public WatermarkOptions Watermark { get; set; } diff --git a/src/Imageflow/Fluent/MemoryLifetimePromise.cs b/src/Imageflow/Fluent/MemoryLifetimePromise.cs new file mode 100644 index 0000000..3d1d479 --- /dev/null +++ b/src/Imageflow/Fluent/MemoryLifetimePromise.cs @@ -0,0 +1,18 @@ +namespace Imageflow.Fluent; + +public enum MemoryLifetimePromise:byte +{ + /// + /// The caller guarantees that the provided ReadOnlyMemory<byte> will remain valid until after the job is disposed, across + /// any async boundaries that occur. + /// + MemoryValidUntilAfterJobDisposed, + /// + /// The caller guarantees that it has eliminated all other references to the IMemoryOwner, and that the IMemoryOwner will be disposed exclusively by Imageflow. + /// + MemoryOwnerDisposedByMemorySource, + /// + /// The caller guarantees that the provided ReadOnlyMemory is "owner-less" or owned by the garbage collector; no IMemoryOwner reference exists + /// + MemoryIsOwnedByRuntime +} \ No newline at end of file diff --git a/src/Imageflow/Fluent/MemorySource.cs b/src/Imageflow/Fluent/MemorySource.cs new file mode 100644 index 0000000..b70d47d --- /dev/null +++ b/src/Imageflow/Fluent/MemorySource.cs @@ -0,0 +1,105 @@ +using System.Buffers; +using Imageflow.Internal.Helpers; + +namespace Imageflow.Fluent; + +public sealed class MemorySource : IAsyncMemorySource, IMemorySource +{ + private readonly ReadOnlyMemory? _borrowedMemory; + private readonly IMemoryOwner? _ownedMemory; + + public static IAsyncMemorySource TakeOwnership(IMemoryOwner ownedMemory, MemoryLifetimePromise promise) + { + ArgumentNullThrowHelper.ThrowIfNull(ownedMemory); + if (promise != MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource) + { + throw new ArgumentException( + "MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource is required for TakeOwnership"); + } + + return new MemorySource(null, ownedMemory, promise); + } + + + private MemorySource(ReadOnlyMemory? borrowedMemory, IMemoryOwner? ownedMemory, + MemoryLifetimePromise promise) + { + if (promise == MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource) + { + ArgumentNullThrowHelper.ThrowIfNull(ownedMemory); + if (borrowedMemory.HasValue) + { + throw new ArgumentException( + "MemoryLifetimePromise.MemoryNowOwnedByMemorySource is not valid for BorrowMemory"); + } + } + + if (!borrowedMemory.HasValue) + { + throw new ArgumentNullException(nameof(borrowedMemory)); + } + + ArgumentNullThrowHelper.ThrowIfNull(borrowedMemory); + _borrowedMemory = borrowedMemory; + _ownedMemory = ownedMemory; + } + + /// + /// Prefer MemorySource.Borrow() instead for clarity and more overloads + /// + /// + public MemorySource(byte[] bytes) + { + _borrowedMemory = new ReadOnlyMemory(bytes); + } + + internal MemorySource(ArraySegment bytes) + { + _borrowedMemory = new ReadOnlyMemory(bytes.Array, bytes.Offset, bytes.Count); + } + + + public static IAsyncMemorySource Borrow(ReadOnlyMemory borrowedMemory, MemoryLifetimePromise promise) + { + if (promise == MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource) + { + throw new ArgumentException( + "MemoryLifetimePromise.MemoryNowOwnedByMemorySource is not valid for Borrow"); + } + + return new MemorySource(borrowedMemory, null, promise); + } + + public static IAsyncMemorySource Borrow(byte[] borrowedMemory, MemoryLifetimePromise promise) + => Borrow(borrowedMemory.AsMemory(), promise); + + public static IAsyncMemorySource Borrow(byte[] borrowedMemory) + => Borrow(borrowedMemory.AsMemory(), MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); + + public static IAsyncMemorySource Borrow(ArraySegment borrowedMemory, MemoryLifetimePromise promise) + { + return new MemorySource(borrowedMemory, null, promise); + } + public static IAsyncMemorySource Borrow(byte[] borrowedMemory, int offset, int length, MemoryLifetimePromise promise) + { + var memory = new ReadOnlyMemory(borrowedMemory, offset, length); + return Borrow(memory, promise); + } + + public void Dispose() + { + _ownedMemory?.Dispose(); + } + + public ValueTask> BorrowReadOnlyMemoryAsync(CancellationToken cancellationToken) + { + return new ValueTask>(_borrowedMemory ?? _ownedMemory!.Memory); + } + + public ReadOnlyMemory BorrowReadOnlyMemory() + { + return _borrowedMemory ?? _ownedMemory!.Memory; + } + + public bool AsyncPreferred => false; +} \ No newline at end of file diff --git a/src/Imageflow/Fluent/OutputDestinationWrapper.cs b/src/Imageflow/Fluent/OutputDestinationWrapper.cs new file mode 100644 index 0000000..d437ad0 --- /dev/null +++ b/src/Imageflow/Fluent/OutputDestinationWrapper.cs @@ -0,0 +1,88 @@ +// using System.Buffers; +// using System.Runtime.InteropServices; +// +// namespace Imageflow.Fluent; +// +// +// internal class OutputDestinationToSinkAdapter: IOutputSink, IAsyncOutputSink +// { +// private readonly IOutputDestination _dest; +// private readonly bool _disposeUnderlying; +// +// +// public OutputDestinationToSinkAdapter(IOutputDestination dest, bool disposeUnderlying) +// { +// _dest = dest; +// _disposeUnderlying = disposeUnderlying; +// +// } +// public async ValueTask FastRequestCapacityAsync(int bytes) +// { +// await _dest.RequestCapacityAsync(bytes); +// } +// +// public async ValueTask FastWriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken) +// { +// if (MemoryMarshal.TryGetArray(data, out ArraySegment segment)) +// { +// await _dest.WriteAsync(segment, cancellationToken).ConfigureAwait(false); +// return; +// } +// +// var rent = ArrayPool.Shared.Rent(Math.Min(81920,data.Length)); +// try +// { +// for (int i = 0; i < data.Length; i += rent.Length) +// { +// int len = Math.Min(rent.Length, data.Length - i); +// data.Span.Slice(i, len).CopyTo(rent); +// await _dest.WriteAsync(new ArraySegment(rent, 0, len), cancellationToken).ConfigureAwait(false); +// } +// } +// finally +// { +// ArrayPool.Shared.Return(rent); +// } +// +// } +// public void Write(ReadOnlySpan data) +// { +// +// var rent = ArrayPool.Shared.Rent(Math.Min(81920,data.Length)); +// try +// { +// for (int i = 0; i < data.Length; i += rent.Length) +// { +// int len = Math.Min(rent.Length, data.Length - i); +// data.Slice(i, len).CopyTo(rent); +// _dest.WriteAsync(new ArraySegment(rent, 0, len), default).Wait(); +// } +// } +// finally +// { +// ArrayPool.Shared.Return(rent); +// } +// } +// +// public async ValueTask FastFlushAsync(CancellationToken cancellationToken) +// { +// await _dest.FlushAsync(cancellationToken); +// } +// +// public void RequestCapacity(int bytes) +// { +// _dest.RequestCapacityAsync(bytes).Wait(); +// } +// +// +// public void Flush() +// { +// _dest.FlushAsync(default).Wait(); +// } +// +// public void Dispose() +// { +// if (_disposeUnderlying) _dest?.Dispose(); +// } +// } +// diff --git a/src/Imageflow/Fluent/PerformanceDetailsNode.cs b/src/Imageflow/Fluent/PerformanceDetailsNode.cs index caca628..75967cc 100644 --- a/src/Imageflow/Fluent/PerformanceDetailsNode.cs +++ b/src/Imageflow/Fluent/PerformanceDetailsNode.cs @@ -1,6 +1,6 @@ namespace Imageflow.Fluent { - public struct PerformanceDetailsNode + public record struct PerformanceDetailsNode { public string Name { get; internal init; } diff --git a/src/Imageflow/Imageflow.Net.csproj b/src/Imageflow/Imageflow.Net.csproj index 10ab3ba..5d51f87 100644 --- a/src/Imageflow/Imageflow.Net.csproj +++ b/src/Imageflow/Imageflow.Net.csproj @@ -3,7 +3,7 @@ Imageflow.Net Bindings for the Imageflow image processing library. Also remember to install Imageflow.NativeRuntime.[your platform] or use Imageflow.AllPlatforms instead. - net8.0;netstandard2.0 + net8.0;netstandard2.0;netstandard2.1 true true true @@ -18,7 +18,9 @@ - + + + diff --git a/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs b/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs new file mode 100644 index 0000000..db4f636 --- /dev/null +++ b/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Imageflow.Internal.Helpers; + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// Imazen licenses any changes to th + +#nullable enable + +internal static partial class ArgumentNullThrowHelper +{ + /// Throws an if is null. + /// The reference type argument to validate as non-null. + /// The name of the parameter with which corresponds. + public static void ThrowIfNull( +#if INTERNAL_NULLABLE_ATTRIBUTES || NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + [NotNull] +#endif + object? argument, [CallerArgumentExpression("argument")] string? paramName = null) + { +#if !NET7_0_OR_GREATER || NETSTANDARD || NETFRAMEWORK + if (argument is null) + { + Throw(paramName); + } +#else + ArgumentNullException.ThrowIfNull(argument, paramName); +#endif + } + +#if !NET7_0_OR_GREATER || NETSTANDARD || NETFRAMEWORK +#if INTERNAL_NULLABLE_ATTRIBUTES || NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + [DoesNotReturn] +#endif + internal static void Throw(string? paramName) => + throw new ArgumentNullException(paramName); +#endif +} \ No newline at end of file diff --git a/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs b/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs new file mode 100644 index 0000000..2c7f849 --- /dev/null +++ b/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs @@ -0,0 +1,38 @@ +namespace Imageflow.Internal.Helpers; + +internal static class ArraySegmentExtensions +{ + internal static bool TryGetBufferSliceAfterPosition(this MemoryStream stream, out ArraySegment segment) + { + if (stream.TryGetBuffer(out var wholeStream) && wholeStream is { Count: > 0, Array: not null }) + { + var remainingStreamLength = stream.Length - (int)stream.Position; + if (remainingStreamLength > int.MaxValue || stream.Position > int.MaxValue) + throw new OverflowException("Streams cannot exceed 2GB"); + if (stream.Position < 0) throw new InvalidOperationException("Stream position cannot be negative"); + segment = new ArraySegment(wholeStream.Array, (int)stream.Position, (int)remainingStreamLength); + return true; + } + + segment = default; + return false; + } + + internal static bool TryGetBufferSliceAllWrittenData(this MemoryStream stream, out ArraySegment segment) + { + if (stream.TryGetBuffer(out var wholeStream) && wholeStream is { Count: > 0, Array: not null }) + { + if (stream.Length > int.MaxValue) throw new OverflowException("Streams cannot exceed 2GB"); + segment = new ArraySegment(wholeStream.Array, 0, (int)stream.Length); + return true; + } + + segment = default; + return false; + } + + internal static ReadOnlyMemory GetWrittenMemory(this MemoryStream stream) + { + return stream.TryGetBufferSliceAllWrittenData(out var segment) ? new ReadOnlyMemory(segment.Array, segment.Offset, segment.Count) : stream.ToArray(); + } +} \ No newline at end of file diff --git a/src/Imageflow/Bindings/IAssertReady.cs b/src/Imageflow/Internal.Helpers/IAssertReady.cs similarity index 66% rename from src/Imageflow/Bindings/IAssertReady.cs rename to src/Imageflow/Internal.Helpers/IAssertReady.cs index 10b0398..dcc1f40 100644 --- a/src/Imageflow/Bindings/IAssertReady.cs +++ b/src/Imageflow/Internal.Helpers/IAssertReady.cs @@ -1,4 +1,4 @@ -namespace Imageflow.Bindings +namespace Imageflow.Internal.Helpers { internal interface IAssertReady { diff --git a/src/Imageflow/Bindings/PinBox.cs b/src/Imageflow/Internal.Helpers/PinBox.cs similarity index 95% rename from src/Imageflow/Bindings/PinBox.cs rename to src/Imageflow/Internal.Helpers/PinBox.cs index bb11add..180ee57 100644 --- a/src/Imageflow/Bindings/PinBox.cs +++ b/src/Imageflow/Internal.Helpers/PinBox.cs @@ -1,7 +1,7 @@ using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; -namespace Imageflow.Bindings +namespace Imageflow.Internal.Helpers { internal class PinBox : CriticalFinalizerObject, IDisposable { diff --git a/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs b/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs new file mode 100644 index 0000000..098db8b --- /dev/null +++ b/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs @@ -0,0 +1,118 @@ +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Imageflow.Internal.Helpers; + +/// +/// Allows safely borrowing unmanaged memory that is exclusively owned and freed by a SafeHandle. +/// +internal sealed unsafe class SafeHandleMemoryManager : MemoryManager +{ + private readonly uint _length; + private IntPtr _pointer; + private readonly SafeHandle _outerHandle; + private readonly SafeHandle? _innerHandle; + + /// + /// Use this to create a MemoryManager that keeps a handle forced open until the MemoryManager is disposed. + /// + /// + /// + /// + /// + internal static MemoryManager BorrowFromHandle(SafeHandle handle, IntPtr pointer, uint length) + { + return new SafeHandleMemoryManager(handle,null, pointer, length, true); + } + /// + /// Use this to create a MemoryManager that keeps two handles forced open until the MemoryManager is disposed. + /// The ref count is increased first on outerHandle, then on innerHandle. + /// On disposal, the ref count is decreased first on innerHandle, then on outerHandle. + /// + /// + /// + /// + /// + /// + internal static MemoryManager BorrowFromHandles(SafeHandle outerHandle, SafeHandle innerHandle, IntPtr pointer, uint length) + { + return new SafeHandleMemoryManager(outerHandle,innerHandle, pointer, length, true); + } + private SafeHandleMemoryManager(SafeHandle outerHandle, SafeHandle? innerHandle, IntPtr pointer, uint length, bool addGcPressure) + { + if (outerHandle.IsInvalid) + { + throw new ArgumentException("Handle is invalid", nameof(outerHandle)); + } + if (pointer == IntPtr.Zero || pointer == new IntPtr(-1)) + { + throw new ArgumentException("Pointer is invalid", nameof(pointer)); + } + var addRefSucceeded = false; + outerHandle.DangerousAddRef(ref addRefSucceeded); + if (!addRefSucceeded) + { + throw new ArgumentException("SafeHandle.DangerousAddRef failed", nameof(outerHandle)); + } + var addRefSucceeded2 = false; + innerHandle?.DangerousAddRef(ref addRefSucceeded2); + if (innerHandle != null && !addRefSucceeded2) + { + outerHandle.DangerousRelease(); + throw new ArgumentException("SafeHandle.DangerousAddRef failed for 2nd handle", nameof(innerHandle)); + } + _outerHandle = outerHandle; + _innerHandle = innerHandle; + _pointer = pointer; + _length = length; + + if (length != 0 && addGcPressure) + { + GC.AddMemoryPressure(length); + } + } + + public IntPtr Pointer => _pointer; + + public override Span GetSpan() + { + if (_length == 0) + { + return Span.Empty; + } + + return new Span((void*)_pointer, (int)_length); + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + if ((uint)elementIndex > _length) + { + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + + return new MemoryHandle(Unsafe.Add((void*)_pointer, elementIndex), default, this); + } + + public override void Unpin() + { + } + + protected override void Dispose(bool disposing) + { + var pointer = Interlocked.Exchange(ref _pointer, IntPtr.Zero); + if (pointer != IntPtr.Zero) + { + // Now release the handle(s) + _innerHandle?.DangerousRelease(); + _outerHandle.DangerousRelease(); + + if (_length != 0){ + GC.RemoveMemoryPressure(_length); + } + } + } + + +} diff --git a/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs b/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs new file mode 100644 index 0000000..c2c54b6 --- /dev/null +++ b/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs @@ -0,0 +1,88 @@ +using System.Buffers; +using System.Runtime.InteropServices; + +namespace Imageflow.Internal.Helpers; + +internal static class StreamMemoryExtensionsPolyfills +{ + internal static ValueTask WriteMemoryAsync(this Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken) + { +#if NETSTANDARD2_1_OR_GREATER + return stream.WriteAsync(buffer, cancellationToken); +#else + if (cancellationToken.IsCancellationRequested) + { + return new(Task.FromCanceled(cancellationToken)); + } + + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + return new(stream.WriteAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken)); + } + + // Local function, same idea as above + static async Task WriteAsyncFallback(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + byte[] rent = ArrayPool.Shared.Rent(buffer.Length); + + try + { + buffer.Span.CopyTo(rent); + + await stream.WriteAsync(rent, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(rent); + } + } + + return new(WriteAsyncFallback(stream, buffer, cancellationToken)); +#endif + } + + + internal static void WriteMemory(this Stream stream, ReadOnlyMemory buffer) + { +#if NETSTANDARD2_1_OR_GREATER + stream.Write(buffer.Span); +#else + + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + stream.Write(segment.Array!, segment.Offset, segment.Count); + return; + } + + byte[] rent = ArrayPool.Shared.Rent(buffer.Length); + + try + { + buffer.Span.CopyTo(rent); + stream.Write(rent, 0, buffer.Length); + } + finally + { + ArrayPool.Shared.Return(rent); + } +#endif + } + + internal static void WriteSpan(this Stream stream, ReadOnlySpan buffer) + { +#if NETSTANDARD2_1_OR_GREATER + stream.Write(buffer); +#else + var rent = ArrayPool.Shared.Rent(buffer.Length); + try + { + buffer.CopyTo(rent); + stream.Write(rent, 0, buffer.Length); + } + finally + { + ArrayPool.Shared.Return(rent); + } +#endif + } +} \ No newline at end of file diff --git a/src/Imageflow/Internal.Helpers/TextHelpers.cs b/src/Imageflow/Internal.Helpers/TextHelpers.cs new file mode 100644 index 0000000..d7e9613 --- /dev/null +++ b/src/Imageflow/Internal.Helpers/TextHelpers.cs @@ -0,0 +1,63 @@ +using System.Text; + +namespace Imageflow.Internal.Helpers; + +internal static class TextHelpers +{ + internal static string Utf8ToString(this ReadOnlySpan utf8) + { +#if NETSTANDARD2_1_OR_GREATER + return Encoding.UTF8.GetString(utf8); +#else + unsafe + { + fixed (byte* ptr = utf8) + { + return Encoding.UTF8.GetString(ptr, utf8.Length); + } + } +#endif + } + + /// + /// Returns false if the text contains non-ASCII characters or nulls, or if the buffer is too small (it should be greater than the number of chars). + /// + /// + /// + /// + /// + internal static bool TryEncodeAsciiNullTerminated(ReadOnlySpan text, Span buffer, out ReadOnlySpan resultingBufferSlice) + { + if (TryEncodeAsciiNullTerminatedIntoBuffer(text, buffer, out var bytesUsed)) + { + resultingBufferSlice = buffer.Slice(0, bytesUsed); + return true; + } + resultingBufferSlice = ReadOnlySpan.Empty; + return false; + } + + internal static bool TryEncodeAsciiNullTerminatedIntoBuffer(ReadOnlySpan text, Span buffer, out int bytesUsed) + { + if (buffer.Length < text.Length + 1) + { + bytesUsed = 0; + return false; + } + for (var i = 0; i < text.Length; i++) + { + if (text[i] > 127 || text[i] <= 0) + { + bytesUsed = 0; + return false; + } + buffer[i] = (byte) text[i]; + } + buffer[^1] = 0; + bytesUsed = text.Length + 1; + return true; + } + + + +} \ No newline at end of file diff --git a/src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs b/src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs new file mode 100644 index 0000000..aa40c46 --- /dev/null +++ b/src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs @@ -0,0 +1,59 @@ +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace Imageflow.Internal.Helpers; + +internal unsafe class UnmanagedMemoryManager : MemoryManager +{ + private readonly uint _length; + private IntPtr _pointer; + private readonly Action _onDispose; + + public UnmanagedMemoryManager(IntPtr pointer, uint length, Action onDispose) + { + _onDispose = onDispose; + _pointer = pointer; + _length = length; + + if (length != 0) + { + GC.AddMemoryPressure(length); + } + } + + public IntPtr Pointer => _pointer; + + public override Span GetSpan() + { + if (_length == 0) + { + return Span.Empty; + } + + return new Span((void*)_pointer, (int)_length); + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + if ((uint)elementIndex > _length) + { + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + + return new MemoryHandle(Unsafe.Add((void*)_pointer, elementIndex), default, this); + } + + public override void Unpin() + { + } + + protected override void Dispose(bool disposing) + { + var pointer = Interlocked.Exchange(ref _pointer, IntPtr.Zero); + if (pointer != IntPtr.Zero && _length != 0) + { + _onDispose(pointer, _length); + GC.RemoveMemoryPressure(_length); + } + } +} diff --git a/src/Imageflow/packages.lock.json b/src/Imageflow/packages.lock.json index 08d8e03..71fbd43 100644 --- a/src/Imageflow/packages.lock.json +++ b/src/Imageflow/packages.lock.json @@ -4,9 +4,9 @@ ".NETStandard,Version=v2.0": { "Microsoft.IO.RecyclableMemoryStream": { "type": "Direct", - "requested": "[2.*, 4.0.0)", - "resolved": "2.3.2", - "contentHash": "Oh1qXXFdJFcHozvb4H6XYLf2W0meZFuG0A+TfapFPj9z5fd4vxiARGEhAaLj/6XWQaMYIv4SH/9Q6H78Hw0E2Q==", + "requested": "[3.*, 4.0.0)", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==", "dependencies": { "System.Memory": "4.5.5" } @@ -36,6 +36,23 @@ "resolved": "1.14.1", "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" }, + "System.Buffers": { + "type": "Direct", + "requested": "[4.*, )", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Memory": { + "type": "Direct", + "requested": "[4.*, )", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.4.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, "System.Text.Json": { "type": "Direct", "requested": "[6.*, )", @@ -74,6 +91,88 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + } + }, + ".NETStandard,Version=v2.1": { + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Direct", + "requested": "[3.*, 4.0.0)", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.*, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "PolySharp": { + "type": "Direct", + "requested": "[1.*, )", + "resolved": "1.14.1", + "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + }, + "System.Text.Json": { + "type": "Direct", + "requested": "[6.*, )", + "resolved": "6.0.9", + "contentHash": "2j16oUgtIzl7Xtk7demG0i/v5aU/ZvULcAnJvPb63U3ZhXJ494UYcxuEj5Fs49i3XDrk5kU/8I+6l9zRCw3cJw==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0", + "System.Buffers": "4.5.1", + "System.Memory": "4.5.4", + "System.Numerics.Vectors": "4.5.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "6.0.0", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, "System.Buffers": { "type": "Transitive", "resolved": "4.5.1", @@ -81,8 +180,8 @@ }, "System.Memory": { "type": "Transitive", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "resolved": "4.5.4", + "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==", "dependencies": { "System.Buffers": "4.5.1", "System.Numerics.Vectors": "4.4.0", @@ -121,15 +220,15 @@ "net8.0": { "Microsoft.IO.RecyclableMemoryStream": { "type": "Direct", - "requested": "[2.*, 4.0.0)", - "resolved": "2.3.2", - "contentHash": "Oh1qXXFdJFcHozvb4H6XYLf2W0meZFuG0A+TfapFPj9z5fd4vxiARGEhAaLj/6XWQaMYIv4SH/9Q6H78Hw0E2Q==" + "requested": "[3.*, 4.0.0)", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.1, )", - "resolved": "8.0.1", - "contentHash": "ADdJXuKNjwZDfBmybMnpvwd5CK3gp92WkWqqeQhW4W+q4MO3Qaa9QyW2DcFLAvCDMcCWxT5hRXqGdv13oon7nA==" + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "hKTrehpfVzOhAz0mreaTAZgbz0DrMEbWq4n3hAo8Ks6WdxdqQhNPvzOqn9VygKuWf1bmxPdraqzTaXriO/sn0A==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/tests/Imageflow.Test/Imageflow.Test.csproj b/tests/Imageflow.Test/Imageflow.Test.csproj index c9b4b09..4d20f3b 100644 --- a/tests/Imageflow.Test/Imageflow.Test.csproj +++ b/tests/Imageflow.Test/Imageflow.Test.csproj @@ -27,8 +27,8 @@ - - + + diff --git a/tests/Imageflow.Test/TestJson.cs b/tests/Imageflow.Test/TestJson.cs index f6a8f0a..34a33b5 100644 --- a/tests/Imageflow.Test/TestJson.cs +++ b/tests/Imageflow.Test/TestJson.cs @@ -59,7 +59,7 @@ public void TestAllJob() .SetHints(new ResampleHints().SetSharpen(15f, SharpenWhen.Always)) .SetMinCanvasSize(1, 1)) .EncodeToBytes(new MozJpegEncoder(80, true)) - .Finish().ToJson(); + .Finish().ToJsonDebug(); var expected = """{"security":null,"framewise":{"graph":{"edges":[{"from":0,"to":1,"kind":"input"},{"from":1,"to":2,"kind":"input"},{"from":2,"to":3,"kind":"input"},{"from":3,"to":4,"kind":"input"},{"from":4,"to":5,"kind":"input"},{"from":5,"to":6,"kind":"input"},{"from":6,"to":7,"kind":"input"},{"from":7,"to":8,"kind":"input"},{"from":8,"to":9,"kind":"input"},{"from":9,"to":10,"kind":"input"},{"from":10,"to":11,"kind":"input"},{"from":11,"to":12,"kind":"input"},{"from":12,"to":13,"kind":"input"},{"from":13,"to":14,"kind":"input"},{"from":14,"to":15,"kind":"input"},{"from":15,"to":16,"kind":"input"},{"from":16,"to":17,"kind":"input"},{"from":17,"to":18,"kind":"input"},{"from":18,"to":19,"kind":"input"},{"from":19,"to":20,"kind":"input"},{"from":20,"to":21,"kind":"input"},{"from":21,"to":22,"kind":"input"},{"from":22,"to":23,"kind":"input"},{"from":23,"to":24,"kind":"input"},{"from":24,"to":25,"kind":"input"},{"from":25,"to":26,"kind":"input"},{"from":26,"to":27,"kind":"input"},{"from":27,"to":28,"kind":"input"},{"from":28,"to":29,"kind":"input"}],"nodes":{"0":{"decode":{"io_id":0}},"1":{"flip_v":null},"2":{"flip_h":null},"3":{"rotate_90":null},"4":{"rotate_180":null},"5":{"rotate_270":null},"6":{"transpose":null},"7":{"crop_whitespace":{"threshold":80,"percent_padding":0.5}},"8":{"resample_2d":{"w":30,"h":20,"hints":null}},"9":{"crop":{"x1":0,"y1":0,"x2":10,"y2":10}},"10":{"region":{"x1":-5,"y1":-5,"x2":10,"y2":10,"background_color":{"black":null}}},"11":{"region_percent":{"x1":-10.0,"y1":-10.0,"x2":110.0,"y2":110.0,"background_color":{"transparent":null}}},"12":{"color_filter_srgb":{"brightness":-1.0}},"13":{"color_filter_srgb":{"contrast":1.0}},"14":{"color_filter_srgb":{"saturation":1.0}},"15":{"white_balance_histogram_area_threshold_srgb":{"threshold":80}},"16":{"color_filter_srgb":"invert"},"17":{"color_filter_srgb":"sepia"},"18":{"color_filter_srgb":"grayscale_bt709"},"19":{"color_filter_srgb":"grayscale_flat"},"20":{"color_filter_srgb":"grayscale_ntsc"},"21":{"color_filter_srgb":"grayscale_ry"},"22":{"expand_canvas":{"left":5,"top":5,"right":5,"bottom":5,"color":{"srgb":{"hex":"ffeecc"}}}},"23":{"fill_rect":{"x1":2,"y1":2,"x2":8,"y2":8,"color":{"black":null}}},"24":{"command_string":{"kind":"ir4","value":"width=10&height=10&mode=crop"}},"25":{"round_image_corners":{"radius":{"percentage":100.0},"background_color":{"black":null}}},"26":{"round_image_corners":{"radius":{"pixels":1},"background_color":{"transparent":null}}},"27":{"constrain":{"mode":"within","w":5,"h":5}},"28":{"watermark":{"io_id":1,"gravity":{"percentage":{"x":90.0,"y":90.0}},"fit_box":{"image_margins":{"left":1,"top":1,"right":1,"bottom":1}},"fit_mode":"within","min_canvas_width":1,"min_canvas_height":1,"opacity":0.5,"hints":{"sharpen_percent":15.0,"down_filter":null,"up_filter":null,"scaling_colorspace":null,"resample_when":null,"sharpen_when":"always"}}},"29":{"encode":{"io_id":2,"preset":{"mozjpeg":{"quality":80,"progressive":true,"matte":null}}}}}}}}"""; // parse and reformat both before comparing diff --git a/tests/Imageflow.Test/TestStreams.cs b/tests/Imageflow.Test/TestStreams.cs new file mode 100644 index 0000000..d771f60 --- /dev/null +++ b/tests/Imageflow.Test/TestStreams.cs @@ -0,0 +1,54 @@ +namespace Imageflow.Test; +using System; +using System.IO; +using Microsoft.IO; +using Xunit; + +public class MemoryStreamTests +{ + [Theory] + [InlineData(typeof(MemoryStream))] + [InlineData(typeof(RecyclableMemoryStream))] + public void TryGetBuffer_ReturnsDataFromPositionZero(Type streamType) + { + // Arrange + var manager = new RecyclableMemoryStreamManager(); + using var stream = streamType == typeof(MemoryStream) ? new MemoryStream() : manager.GetStream(); + byte[] data = { 1, 2, 3, 4, 5 }; + stream.Write(data, 0, data.Length); + stream.Position = 2; // Set a positive position in the stream + + // Act + bool canGetBuffer = stream.TryGetBuffer(out ArraySegment buffer); + + // Assert + Assert.True(canGetBuffer); + Assert.Equal(0, buffer.Offset); // Data starts from position 0 + Assert.Equal(data.Length, buffer.Count); // Buffer length equals data written + for (int i = 0; i < data.Length; i++) + { + Assert.Equal(data[i], buffer.Array![i + buffer.Offset]); // Data integrity check + } + } + + [Theory] + [InlineData(typeof(MemoryStream))] + [InlineData(typeof(RecyclableMemoryStream))] + public void TryGetBuffer_DoesNotReturnMoreBytesThanWritten(Type streamType) + { + // Arrange + var manager = new RecyclableMemoryStreamManager(); + using var stream = streamType == typeof(MemoryStream) ? new MemoryStream() : manager.GetStream(); + byte[] data = { 1, 2, 3, 4, 5 }; + stream.Write(data, 0, data.Length); + stream.Position = data.Length; // Move to the end to mimic more writes that don't happen + + // Act + bool canGetBuffer = stream.TryGetBuffer(out ArraySegment buffer); + + // Assert + Assert.True(canGetBuffer); + Assert.Equal(data.Length, buffer.Count); // Ensures no more bytes are reported than actually written + } +} + diff --git a/tests/Imageflow.Test/packages.lock.json b/tests/Imageflow.Test/packages.lock.json index f9b52b9..73d0486 100644 --- a/tests/Imageflow.Test/packages.lock.json +++ b/tests/Imageflow.Test/packages.lock.json @@ -63,28 +63,28 @@ "xunit": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.6.6", - "contentHash": "MAbOOMtZIKyn2lrAmMlvhX0BhDOX/smyrTB+8WTXnSKkrmTGBS2fm8g1PZtHBPj91Dc5DJA7fY+/81TJ/yUFZw==", + "resolved": "2.7.0", + "contentHash": "KcCI5zxh8zbUfQTeErc4oT7YokViVND2V0p4vDJ2VD4lhF9V5qCYMMDNixme7FdwYy3SwPHF+2xC2Dq4Z9GSlA==", "dependencies": { - "xunit.analyzers": "1.10.0", - "xunit.assert": "2.6.6", - "xunit.core": "[2.6.6]" + "xunit.analyzers": "1.11.0", + "xunit.assert": "2.7.0", + "xunit.core": "[2.7.0]" } }, "xunit.extensibility.execution": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.6.6", - "contentHash": "UDjIVGj2TepVKN3n32/qXIdb3U6STwTb9L6YEwoQO2A8OxiJS5QAVv2l1aT6tDwwv/9WBmm8Khh/LyHALipcng==", + "resolved": "2.7.0", + "contentHash": "bjY+crT1jOyxKagFjCMdEVzoenO2v66ru8+CK/0UaXvyG4U9Q3UTieJkbQXbi7/1yZIK1sGh01l5/jh2CwLJtQ==", "dependencies": { - "xunit.extensibility.core": "[2.6.6]" + "xunit.extensibility.core": "[2.7.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.5.6", - "contentHash": "CW6uhMXNaQQNMSG1IWhHkBT+V5eqHqn7MP0zfNMhU9wS/sgKX7FGL3rzoaUgt26wkY3bpf7pDVw3IjXhwfiP4w==", + "resolved": "2.5.7", + "contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "17.8.0" } @@ -204,27 +204,27 @@ }, "xunit.analyzers": { "type": "Transitive", - "resolved": "1.10.0", - "contentHash": "Lw8CiDy5NaAWcO6keqD7iZHYUTIuCOcoFrUHw5Sv84ITZ9gFeDybdkVdH0Y2maSlP9fUjtENyiykT44zwFQIHA==" + "resolved": "1.11.0", + "contentHash": "SCv+Ihxv+fCqotGeM8sVwLhw8nzAJ2aFRN5lcoKn9QtGdbVJ79JqDc+4u8/Ddnp2udxtmv+xYFWkHNlb/sk01w==" }, "xunit.assert": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "74Cm9lAZOk5TKCz2MvCBCByKsS23yryOKDIMxH3XRDHXmfGM02jKZWzRA7g4mGB41GnBnv/pcWP3vUYkrCtEcg==" + "resolved": "2.7.0", + "contentHash": "CCTs3bUhmIS4tDwK6Cn/IiabG3RhYzdf65eIkO7u9/grKoN9MrN780LzVED3E8v+vwmmj7b5TW3/GFuZHPAzWA==" }, "xunit.core": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "tqi7RfaNBqM7t8zx6QHryuBPzmotsZXKGaWnopQG2Ez5UV7JoWuyoNdT6gLpDIcKdGYey6YTXJdSr9IXDMKwjg==", + "resolved": "2.7.0", + "contentHash": "98tzqYAbtc/p/2Ba455XTNbD12Qoo8kPehjC4oDT46CAsLli5JOCU9hFF2MV3HHWMw/Y3yFUV2Vcukplbs6kuA==", "dependencies": { - "xunit.extensibility.core": "[2.6.6]", - "xunit.extensibility.execution": "[2.6.6]" + "xunit.extensibility.core": "[2.7.0]", + "xunit.extensibility.execution": "[2.7.0]" } }, "xunit.extensibility.core": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "ty6VKByzbx4Toj4/VGJLEnlmOawqZiMv0in/tLju+ftA+lbWuAWDERM+E52Jfhj4ZYHrAYVa14KHK5T+dq0XxA==", + "resolved": "2.7.0", + "contentHash": "JLnx4PI0vn1Xr1Ust6ydrp2t/ktm2dyGPAVoDJV5gQuvBMSbd2K7WGzODa2ttiz030CeQ8nbsXl05+cvf7QNyA==", "dependencies": { "xunit.abstractions": "2.0.3" } @@ -242,7 +242,9 @@ "imageflow.net": { "type": "Project", "dependencies": { - "Microsoft.IO.RecyclableMemoryStream": "[2.*, 4.0.0)", + "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", + "System.Buffers": "[4.*, )", + "System.Memory": "[4.*, )", "System.Text.Json": "[6.*, )" } } @@ -300,28 +302,28 @@ "xunit": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.6.6", - "contentHash": "MAbOOMtZIKyn2lrAmMlvhX0BhDOX/smyrTB+8WTXnSKkrmTGBS2fm8g1PZtHBPj91Dc5DJA7fY+/81TJ/yUFZw==", + "resolved": "2.7.0", + "contentHash": "KcCI5zxh8zbUfQTeErc4oT7YokViVND2V0p4vDJ2VD4lhF9V5qCYMMDNixme7FdwYy3SwPHF+2xC2Dq4Z9GSlA==", "dependencies": { - "xunit.analyzers": "1.10.0", - "xunit.assert": "2.6.6", - "xunit.core": "[2.6.6]" + "xunit.analyzers": "1.11.0", + "xunit.assert": "2.7.0", + "xunit.core": "[2.7.0]" } }, "xunit.extensibility.execution": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.6.6", - "contentHash": "UDjIVGj2TepVKN3n32/qXIdb3U6STwTb9L6YEwoQO2A8OxiJS5QAVv2l1aT6tDwwv/9WBmm8Khh/LyHALipcng==", + "resolved": "2.7.0", + "contentHash": "bjY+crT1jOyxKagFjCMdEVzoenO2v66ru8+CK/0UaXvyG4U9Q3UTieJkbQXbi7/1yZIK1sGh01l5/jh2CwLJtQ==", "dependencies": { - "xunit.extensibility.core": "[2.6.6]" + "xunit.extensibility.core": "[2.7.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.5.6", - "contentHash": "CW6uhMXNaQQNMSG1IWhHkBT+V5eqHqn7MP0zfNMhU9wS/sgKX7FGL3rzoaUgt26wkY3bpf7pDVw3IjXhwfiP4w==" + "resolved": "2.5.7", + "contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w==" }, "Imageflow.NativeRuntime.osx-x86_64": { "type": "Transitive", @@ -1680,27 +1682,27 @@ }, "xunit.analyzers": { "type": "Transitive", - "resolved": "1.10.0", - "contentHash": "Lw8CiDy5NaAWcO6keqD7iZHYUTIuCOcoFrUHw5Sv84ITZ9gFeDybdkVdH0Y2maSlP9fUjtENyiykT44zwFQIHA==" + "resolved": "1.11.0", + "contentHash": "SCv+Ihxv+fCqotGeM8sVwLhw8nzAJ2aFRN5lcoKn9QtGdbVJ79JqDc+4u8/Ddnp2udxtmv+xYFWkHNlb/sk01w==" }, "xunit.assert": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "74Cm9lAZOk5TKCz2MvCBCByKsS23yryOKDIMxH3XRDHXmfGM02jKZWzRA7g4mGB41GnBnv/pcWP3vUYkrCtEcg==" + "resolved": "2.7.0", + "contentHash": "CCTs3bUhmIS4tDwK6Cn/IiabG3RhYzdf65eIkO7u9/grKoN9MrN780LzVED3E8v+vwmmj7b5TW3/GFuZHPAzWA==" }, "xunit.core": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "tqi7RfaNBqM7t8zx6QHryuBPzmotsZXKGaWnopQG2Ez5UV7JoWuyoNdT6gLpDIcKdGYey6YTXJdSr9IXDMKwjg==", + "resolved": "2.7.0", + "contentHash": "98tzqYAbtc/p/2Ba455XTNbD12Qoo8kPehjC4oDT46CAsLli5JOCU9hFF2MV3HHWMw/Y3yFUV2Vcukplbs6kuA==", "dependencies": { - "xunit.extensibility.core": "[2.6.6]", - "xunit.extensibility.execution": "[2.6.6]" + "xunit.extensibility.core": "[2.7.0]", + "xunit.extensibility.execution": "[2.7.0]" } }, "xunit.extensibility.core": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "ty6VKByzbx4Toj4/VGJLEnlmOawqZiMv0in/tLju+ftA+lbWuAWDERM+E52Jfhj4ZYHrAYVa14KHK5T+dq0XxA==", + "resolved": "2.7.0", + "contentHash": "JLnx4PI0vn1Xr1Ust6ydrp2t/ktm2dyGPAVoDJV5gQuvBMSbd2K7WGzODa2ttiz030CeQ8nbsXl05+cvf7QNyA==", "dependencies": { "xunit.abstractions": "2.0.3" } @@ -1718,7 +1720,7 @@ "imageflow.net": { "type": "Project", "dependencies": { - "Microsoft.IO.RecyclableMemoryStream": "[2.*, 4.0.0)", + "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", "System.Text.Json": "[6.*, )" } } @@ -1776,28 +1778,28 @@ "xunit": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.6.6", - "contentHash": "MAbOOMtZIKyn2lrAmMlvhX0BhDOX/smyrTB+8WTXnSKkrmTGBS2fm8g1PZtHBPj91Dc5DJA7fY+/81TJ/yUFZw==", + "resolved": "2.7.0", + "contentHash": "KcCI5zxh8zbUfQTeErc4oT7YokViVND2V0p4vDJ2VD4lhF9V5qCYMMDNixme7FdwYy3SwPHF+2xC2Dq4Z9GSlA==", "dependencies": { - "xunit.analyzers": "1.10.0", - "xunit.assert": "2.6.6", - "xunit.core": "[2.6.6]" + "xunit.analyzers": "1.11.0", + "xunit.assert": "2.7.0", + "xunit.core": "[2.7.0]" } }, "xunit.extensibility.execution": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.6.6", - "contentHash": "UDjIVGj2TepVKN3n32/qXIdb3U6STwTb9L6YEwoQO2A8OxiJS5QAVv2l1aT6tDwwv/9WBmm8Khh/LyHALipcng==", + "resolved": "2.7.0", + "contentHash": "bjY+crT1jOyxKagFjCMdEVzoenO2v66ru8+CK/0UaXvyG4U9Q3UTieJkbQXbi7/1yZIK1sGh01l5/jh2CwLJtQ==", "dependencies": { - "xunit.extensibility.core": "[2.6.6]" + "xunit.extensibility.core": "[2.7.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.5.6", - "contentHash": "CW6uhMXNaQQNMSG1IWhHkBT+V5eqHqn7MP0zfNMhU9wS/sgKX7FGL3rzoaUgt26wkY3bpf7pDVw3IjXhwfiP4w==" + "resolved": "2.5.7", + "contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w==" }, "Imageflow.NativeRuntime.osx-x86_64": { "type": "Transitive", @@ -3156,27 +3158,27 @@ }, "xunit.analyzers": { "type": "Transitive", - "resolved": "1.10.0", - "contentHash": "Lw8CiDy5NaAWcO6keqD7iZHYUTIuCOcoFrUHw5Sv84ITZ9gFeDybdkVdH0Y2maSlP9fUjtENyiykT44zwFQIHA==" + "resolved": "1.11.0", + "contentHash": "SCv+Ihxv+fCqotGeM8sVwLhw8nzAJ2aFRN5lcoKn9QtGdbVJ79JqDc+4u8/Ddnp2udxtmv+xYFWkHNlb/sk01w==" }, "xunit.assert": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "74Cm9lAZOk5TKCz2MvCBCByKsS23yryOKDIMxH3XRDHXmfGM02jKZWzRA7g4mGB41GnBnv/pcWP3vUYkrCtEcg==" + "resolved": "2.7.0", + "contentHash": "CCTs3bUhmIS4tDwK6Cn/IiabG3RhYzdf65eIkO7u9/grKoN9MrN780LzVED3E8v+vwmmj7b5TW3/GFuZHPAzWA==" }, "xunit.core": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "tqi7RfaNBqM7t8zx6QHryuBPzmotsZXKGaWnopQG2Ez5UV7JoWuyoNdT6gLpDIcKdGYey6YTXJdSr9IXDMKwjg==", + "resolved": "2.7.0", + "contentHash": "98tzqYAbtc/p/2Ba455XTNbD12Qoo8kPehjC4oDT46CAsLli5JOCU9hFF2MV3HHWMw/Y3yFUV2Vcukplbs6kuA==", "dependencies": { - "xunit.extensibility.core": "[2.6.6]", - "xunit.extensibility.execution": "[2.6.6]" + "xunit.extensibility.core": "[2.7.0]", + "xunit.extensibility.execution": "[2.7.0]" } }, "xunit.extensibility.core": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "ty6VKByzbx4Toj4/VGJLEnlmOawqZiMv0in/tLju+ftA+lbWuAWDERM+E52Jfhj4ZYHrAYVa14KHK5T+dq0XxA==", + "resolved": "2.7.0", + "contentHash": "JLnx4PI0vn1Xr1Ust6ydrp2t/ktm2dyGPAVoDJV5gQuvBMSbd2K7WGzODa2ttiz030CeQ8nbsXl05+cvf7QNyA==", "dependencies": { "xunit.abstractions": "2.0.3" } @@ -3194,7 +3196,7 @@ "imageflow.net": { "type": "Project", "dependencies": { - "Microsoft.IO.RecyclableMemoryStream": "[2.*, 4.0.0)", + "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", "System.Text.Json": "[6.*, )" } } @@ -3252,28 +3254,28 @@ "xunit": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.6.6", - "contentHash": "MAbOOMtZIKyn2lrAmMlvhX0BhDOX/smyrTB+8WTXnSKkrmTGBS2fm8g1PZtHBPj91Dc5DJA7fY+/81TJ/yUFZw==", + "resolved": "2.7.0", + "contentHash": "KcCI5zxh8zbUfQTeErc4oT7YokViVND2V0p4vDJ2VD4lhF9V5qCYMMDNixme7FdwYy3SwPHF+2xC2Dq4Z9GSlA==", "dependencies": { - "xunit.analyzers": "1.10.0", - "xunit.assert": "2.6.6", - "xunit.core": "[2.6.6]" + "xunit.analyzers": "1.11.0", + "xunit.assert": "2.7.0", + "xunit.core": "[2.7.0]" } }, "xunit.extensibility.execution": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.6.6", - "contentHash": "UDjIVGj2TepVKN3n32/qXIdb3U6STwTb9L6YEwoQO2A8OxiJS5QAVv2l1aT6tDwwv/9WBmm8Khh/LyHALipcng==", + "resolved": "2.7.0", + "contentHash": "bjY+crT1jOyxKagFjCMdEVzoenO2v66ru8+CK/0UaXvyG4U9Q3UTieJkbQXbi7/1yZIK1sGh01l5/jh2CwLJtQ==", "dependencies": { - "xunit.extensibility.core": "[2.6.6]" + "xunit.extensibility.core": "[2.7.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.*, )", - "resolved": "2.5.6", - "contentHash": "CW6uhMXNaQQNMSG1IWhHkBT+V5eqHqn7MP0zfNMhU9wS/sgKX7FGL3rzoaUgt26wkY3bpf7pDVw3IjXhwfiP4w==" + "resolved": "2.5.7", + "contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w==" }, "Imageflow.NativeRuntime.osx-x86_64": { "type": "Transitive", @@ -4632,27 +4634,27 @@ }, "xunit.analyzers": { "type": "Transitive", - "resolved": "1.10.0", - "contentHash": "Lw8CiDy5NaAWcO6keqD7iZHYUTIuCOcoFrUHw5Sv84ITZ9gFeDybdkVdH0Y2maSlP9fUjtENyiykT44zwFQIHA==" + "resolved": "1.11.0", + "contentHash": "SCv+Ihxv+fCqotGeM8sVwLhw8nzAJ2aFRN5lcoKn9QtGdbVJ79JqDc+4u8/Ddnp2udxtmv+xYFWkHNlb/sk01w==" }, "xunit.assert": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "74Cm9lAZOk5TKCz2MvCBCByKsS23yryOKDIMxH3XRDHXmfGM02jKZWzRA7g4mGB41GnBnv/pcWP3vUYkrCtEcg==" + "resolved": "2.7.0", + "contentHash": "CCTs3bUhmIS4tDwK6Cn/IiabG3RhYzdf65eIkO7u9/grKoN9MrN780LzVED3E8v+vwmmj7b5TW3/GFuZHPAzWA==" }, "xunit.core": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "tqi7RfaNBqM7t8zx6QHryuBPzmotsZXKGaWnopQG2Ez5UV7JoWuyoNdT6gLpDIcKdGYey6YTXJdSr9IXDMKwjg==", + "resolved": "2.7.0", + "contentHash": "98tzqYAbtc/p/2Ba455XTNbD12Qoo8kPehjC4oDT46CAsLli5JOCU9hFF2MV3HHWMw/Y3yFUV2Vcukplbs6kuA==", "dependencies": { - "xunit.extensibility.core": "[2.6.6]", - "xunit.extensibility.execution": "[2.6.6]" + "xunit.extensibility.core": "[2.7.0]", + "xunit.extensibility.execution": "[2.7.0]" } }, "xunit.extensibility.core": { "type": "Transitive", - "resolved": "2.6.6", - "contentHash": "ty6VKByzbx4Toj4/VGJLEnlmOawqZiMv0in/tLju+ftA+lbWuAWDERM+E52Jfhj4ZYHrAYVa14KHK5T+dq0XxA==", + "resolved": "2.7.0", + "contentHash": "JLnx4PI0vn1Xr1Ust6ydrp2t/ktm2dyGPAVoDJV5gQuvBMSbd2K7WGzODa2ttiz030CeQ8nbsXl05+cvf7QNyA==", "dependencies": { "xunit.abstractions": "2.0.3" } @@ -4670,7 +4672,7 @@ "imageflow.net": { "type": "Project", "dependencies": { - "Microsoft.IO.RecyclableMemoryStream": "[2.*, 4.0.0)", + "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", "System.Text.Json": "[6.*, )" } } diff --git a/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json b/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json index 5f6908c..78b81df 100644 --- a/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json +++ b/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json @@ -44,8 +44,8 @@ }, "Microsoft.IO.RecyclableMemoryStream": { "type": "Transitive", - "resolved": "2.3.2", - "contentHash": "Oh1qXXFdJFcHozvb4H6XYLf2W0meZFuG0A+TfapFPj9z5fd4vxiARGEhAaLj/6XWQaMYIv4SH/9Q6H78Hw0E2Q==", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==", "dependencies": { "System.Memory": "4.5.5" } @@ -126,7 +126,9 @@ "imageflow.net": { "type": "Project", "dependencies": { - "Microsoft.IO.RecyclableMemoryStream": "[2.*, 4.0.0)", + "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", + "System.Buffers": "[4.*, )", + "System.Memory": "[4.*, )", "System.Text.Json": "[6.*, )" } } diff --git a/tests/Imageflow.TestWebAOT/packages.lock.json b/tests/Imageflow.TestWebAOT/packages.lock.json index ac159aa..afa3c4a 100644 --- a/tests/Imageflow.TestWebAOT/packages.lock.json +++ b/tests/Imageflow.TestWebAOT/packages.lock.json @@ -10,9 +10,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.1, )", - "resolved": "8.0.1", - "contentHash": "ADdJXuKNjwZDfBmybMnpvwd5CK3gp92WkWqqeQhW4W+q4MO3Qaa9QyW2DcFLAvCDMcCWxT5hRXqGdv13oon7nA==" + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "hKTrehpfVzOhAz0mreaTAZgbz0DrMEbWq4n3hAo8Ks6WdxdqQhNPvzOqn9VygKuWf1bmxPdraqzTaXriO/sn0A==" }, "Imageflow.NativeRuntime.osx-x86_64": { "type": "Transitive", @@ -36,8 +36,8 @@ }, "Microsoft.IO.RecyclableMemoryStream": { "type": "Transitive", - "resolved": "2.3.2", - "contentHash": "Oh1qXXFdJFcHozvb4H6XYLf2W0meZFuG0A+TfapFPj9z5fd4vxiARGEhAaLj/6XWQaMYIv4SH/9Q6H78Hw0E2Q==" + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", @@ -74,7 +74,7 @@ "imageflow.net": { "type": "Project", "dependencies": { - "Microsoft.IO.RecyclableMemoryStream": "[2.*, 4.0.0)", + "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", "System.Text.Json": "[6.*, )" } } From e994e989d2ac730be2f1be79aabbf27fa96ac6eb Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 15:17:14 -0700 Subject: [PATCH 03/22] Finish achieving API compatibility and public API changes for source providers. --- CHANGES.md | 15 ++- .../.idea.Imageflow.both/.idea/.gitignore | 2 + src/Imageflow/Bindings/JobContext.cs | 2 +- src/Imageflow/Bindings/JsonResponse.cs | 13 +-- src/Imageflow/Bindings/JsonResponseHandle.cs | 2 - .../Bindings/NativeLibraryLoading.cs | 2 +- src/Imageflow/CompatibilitySuppressions.xml | 74 +++++++++++++++ src/Imageflow/Fluent/BufferedStreamSource.cs | 43 ++++++++- src/Imageflow/Fluent/BuildEncodeResult.cs | 4 +- src/Imageflow/Fluent/BuildJobResult.cs | 8 ++ src/Imageflow/Fluent/BuildNode.cs | 2 +- src/Imageflow/Fluent/IAsyncMemorySource.cs | 5 +- src/Imageflow/Fluent/IBytesSource.cs | 68 +------------ src/Imageflow/Fluent/IOutputDestination.cs | 1 + src/Imageflow/Fluent/ImageJob.cs | 63 ++++++++---- src/Imageflow/Fluent/InputWatermark.cs | 4 +- src/Imageflow/Fluent/SourceLifetime.cs | 17 ++++ src/Imageflow/Fluent/StreamSource.cs | 63 ++++++++++++ src/Imageflow/IO/ProcessEx.cs | 2 +- src/Imageflow/Imageflow.Net.csproj | 3 + .../ArgumentNullThrowHelperPolyfill.cs | 20 +++- tests/Imageflow.Test/TestApi.cs | 95 +++++++++++++++++-- tests/Imageflow.Test/TestJson.cs | 5 +- .../packages.lock.json | 88 ----------------- tests/Imageflow.TestWebAOT/packages.lock.json | 6 +- 25 files changed, 391 insertions(+), 216 deletions(-) create mode 100644 src/Imageflow/CompatibilitySuppressions.xml create mode 100644 src/Imageflow/Fluent/SourceLifetime.cs create mode 100644 src/Imageflow/Fluent/StreamSource.cs diff --git a/CHANGES.md b/CHANGES.md index cd4ffe6..615e000 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,16 @@ ## Changelog +## v0.13 -InputWatermark.Source is now IMemorySource instead of IBytesSource +This release makes user-facing changes: + + + +InputWatermark.Source is now IMemorySource instead of IBytesSource\ + +## v0.12 (2024-02-06) + +* Fix compatibility with RecyclableMemoryStream 3.x, drop compatibility with 1.x ## v0.11 (2024-01-29) @@ -24,6 +33,10 @@ public static dynamic? DeserializeDynamic(this IJsonResponseProvider p) public static T? Deserialize(this IJsonResponseProvider p) where T : class ``` +To accommodate the shift to `System.Text.Json`, interface members `ToJsonNode()` have been added to `IEncoderPreset` and `IWatermarkConstraintBox`. Nobody should be implementing these anyway, other than the Imageflow library itself. + +The `object` parameter in `BuildItemBase` protected constructor has been changed to `System.Text.Json.Nodes.JsonNode`. + Deprecated lots of APIs, including the following: ``` * All ToImageflowDynamic() methods on objects. Use ToJsonNode() instead. diff --git a/src/.idea/.idea.Imageflow.both/.idea/.gitignore b/src/.idea/.idea.Imageflow.both/.idea/.gitignore index a52674f..14d35ea 100644 --- a/src/.idea/.idea.Imageflow.both/.idea/.gitignore +++ b/src/.idea/.idea.Imageflow.both/.idea/.gitignore @@ -11,3 +11,5 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/src/Imageflow/Bindings/JobContext.cs b/src/Imageflow/Bindings/JobContext.cs index 52470a5..d966e11 100644 --- a/src/Imageflow/Bindings/JobContext.cs +++ b/src/Imageflow/Bindings/JobContext.cs @@ -241,7 +241,7 @@ private ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTerminatedMe { AssertReady(); #if NETSTANDARD2_1_OR_GREATER - // TODO: Use ArrayPoolBufferWriter instead? Adds CommunityToolkit.HighPerformance dependency + // MAYBE: Use ArrayPoolBufferWriter instead? Adds CommunityToolkit.HighPerformance dependency var writer = new ArrayBufferWriter(4096); var utf8JsonWriter = new Utf8JsonWriter(writer, new JsonWriterOptions { diff --git a/src/Imageflow/Bindings/JsonResponse.cs b/src/Imageflow/Bindings/JsonResponse.cs index a655075..ce357fd 100644 --- a/src/Imageflow/Bindings/JsonResponse.cs +++ b/src/Imageflow/Bindings/JsonResponse.cs @@ -123,15 +123,12 @@ public Stream GetStream() public unsafe ReadOnlySpan BorrowBytes() { - unsafe - { - Read(out var _, out var utf8Buffer, out var bufferSize); - if (utf8Buffer == IntPtr.Zero) return ReadOnlySpan.Empty; - if (bufferSize == UIntPtr.Zero) return ReadOnlySpan.Empty; - if (bufferSize.ToUInt64() > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(bufferSize)); + Read(out var _, out var utf8Buffer, out var bufferSize); + if (utf8Buffer == IntPtr.Zero) return ReadOnlySpan.Empty; + if (bufferSize == UIntPtr.Zero) return ReadOnlySpan.Empty; + if (bufferSize.ToUInt64() > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(bufferSize)); - return new ReadOnlySpan((void*)utf8Buffer, (int)bufferSize); - } + return new ReadOnlySpan((void*)utf8Buffer, (int)bufferSize); } public MemoryManager BorrowMemory() diff --git a/src/Imageflow/Bindings/JsonResponseHandle.cs b/src/Imageflow/Bindings/JsonResponseHandle.cs index 4d12e91..58fbb5b 100644 --- a/src/Imageflow/Bindings/JsonResponseHandle.cs +++ b/src/Imageflow/Bindings/JsonResponseHandle.cs @@ -22,11 +22,9 @@ public JsonResponseHandle(JobContextHandle parent, IntPtr ptr) { throw new ArgumentException("SafeHandle.DangerousAddRef failed", nameof(parent)); } - _handleReferenced = addRefSucceeded ? 1 : 0; } - private int _handleReferenced = 0; public JobContextHandle ParentContext { get; } public bool IsValid => !IsInvalid && !IsClosed && ParentContext.IsValid; diff --git a/src/Imageflow/Bindings/NativeLibraryLoading.cs b/src/Imageflow/Bindings/NativeLibraryLoading.cs index b04af52..41144da 100644 --- a/src/Imageflow/Bindings/NativeLibraryLoading.cs +++ b/src/Imageflow/Bindings/NativeLibraryLoading.cs @@ -533,7 +533,7 @@ public static IntPtr Execute(string fileName) [SecurityCritical] internal static class UnixLoadLibrary { - // TODO: unsure if this works on Mac OS X; it might be libc instead + // TODO: unsure if this works on Mac OS X; it might be libc instead. dncore works, but mono is untested [DllImport("libdl.so", SetLastError = true, CharSet = CharSet.Ansi)] private static extern IntPtr dlopen(string fileName, int flags); diff --git a/src/Imageflow/CompatibilitySuppressions.xml b/src/Imageflow/CompatibilitySuppressions.xml new file mode 100644 index 0000000..bc81cde --- /dev/null +++ b/src/Imageflow/CompatibilitySuppressions.xml @@ -0,0 +1,74 @@ + + + + + CP0002 + M:Imageflow.Bindings.IJsonResponseProviderExtensions.Deserialize``1(Imageflow.Bindings.IJsonResponseProvider) + lib/netstandard2.0/Imageflow.Net.dll + lib/netstandard2.0/Imageflow.Net.dll + true + + + CP0002 + M:Imageflow.Bindings.IJsonResponseProviderExtensions.DeserializeDynamic(Imageflow.Bindings.IJsonResponseProvider) + lib/netstandard2.0/Imageflow.Net.dll + lib/netstandard2.0/Imageflow.Net.dll + true + + + CP0002 + M:Imageflow.Fluent.BuildEncodeResult.#ctor + lib/netstandard2.0/Imageflow.Net.dll + lib/netstandard2.0/Imageflow.Net.dll + true + + + CP0002 + M:Imageflow.Fluent.BuildItemBase.#ctor(Imageflow.Fluent.ImageJob,System.Object,Imageflow.Fluent.BuildNode,Imageflow.Fluent.BuildNode) + lib/netstandard2.0/Imageflow.Net.dll + lib/netstandard2.0/Imageflow.Net.dll + true + + + CP0002 + M:Imageflow.Fluent.BuildJobResult.#ctor + lib/netstandard2.0/Imageflow.Net.dll + lib/netstandard2.0/Imageflow.Net.dll + true + + + CP0002 + M:Imageflow.Fluent.InputWatermark.get_Source + lib/netstandard2.0/Imageflow.Net.dll + lib/netstandard2.0/Imageflow.Net.dll + true + + + CP0006 + M:Imageflow.Fluent.IEncoderPreset.ToJsonNode + lib/netstandard2.0/Imageflow.Net.dll + lib/netstandard2.0/Imageflow.Net.dll + true + + + CP0006 + M:Imageflow.Fluent.IWatermarkConstraintBox.ToJsonNode + lib/netstandard2.0/Imageflow.Net.dll + lib/netstandard2.0/Imageflow.Net.dll + true + + + CP0009 + T:Imageflow.Fluent.BuildEncodeResult + lib/netstandard2.0/Imageflow.Net.dll + lib/netstandard2.0/Imageflow.Net.dll + true + + + CP0009 + T:Imageflow.Fluent.BuildJobResult + lib/netstandard2.0/Imageflow.Net.dll + lib/netstandard2.0/Imageflow.Net.dll + true + + \ No newline at end of file diff --git a/src/Imageflow/Fluent/BufferedStreamSource.cs b/src/Imageflow/Fluent/BufferedStreamSource.cs index 15debb3..6e5f589 100644 --- a/src/Imageflow/Fluent/BufferedStreamSource.cs +++ b/src/Imageflow/Fluent/BufferedStreamSource.cs @@ -20,7 +20,7 @@ private BufferedStreamSource(Stream stream, bool disposeUnderlying, bool seekToS } private readonly bool _seekToStart; - private readonly Stream _underlying; + private Stream? _underlying; private readonly bool _disposeUnderlying; private static readonly RecyclableMemoryStreamManager Mgr @@ -33,14 +33,18 @@ public void Dispose() if (_disposeUnderlying) { _underlying?.Dispose(); + _underlying = null; } _copy?.Dispose(); + _copy = null; + } private bool TryGetWrittenMemory( out ReadOnlyMemory memory) { + ObjectDisposedHelper.ThrowIf(_underlying == null, this); var memStream = _underlying as MemoryStream ?? _copy; if (memStream != null) { @@ -65,6 +69,7 @@ private bool TryGetWrittenMemory( /// public async ValueTask> BorrowReadOnlyMemoryAsync(CancellationToken cancellationToken) { + ObjectDisposedHelper.ThrowIf(_underlying == null, this); if (TryGetWrittenMemory(out var segment)) { return segment; @@ -85,6 +90,7 @@ public async ValueTask> BorrowReadOnlyMemoryAsync(Cancellat public ReadOnlyMemory BorrowReadOnlyMemory() { + ObjectDisposedHelper.ThrowIf(_underlying == null, this); if (TryGetWrittenMemory(out var segment)) { return segment; @@ -107,19 +113,50 @@ public ReadOnlyMemory BorrowReadOnlyMemory() public bool AsyncPreferred => _underlying is not MemoryStream && _underlying is not UnmanagedMemoryStream; + /// + /// Seeks to the beginning of the stream before reading. + /// You swear not to close, dispose, or reuse the stream or its underlying memory/stream until after this wrapper and the job are disposed. + /// You remain responsible for disposing and cleaning up the stream after the job is disposed. + /// + /// + /// public static IAsyncMemorySource BorrowEntireStream(Stream stream) { return new BufferedStreamSource(stream, false, true); } + /// + /// You remain responsible for disposing and cleaning up the stream after the job is disposed. + /// Only reads from the current position to the end of the image file. + /// You swear not to close, dispose, or reuse the stream or its underlying memory/stream until after this wrapper and the job are disposed. + /// + /// + /// public static IAsyncMemorySource BorrowStreamRemainder(Stream stream) { return new BufferedStreamSource(stream, false, false); } - public static IAsyncMemorySource UseEntireStreamAndDispose(Stream stream) + + /// + /// The stream will be closed and disposed with the BufferedStreamSource. You must not close, dispose, or reuse the stream or its underlying streams/buffers until after the job and the owning objects are disposed. + /// You must not close, dispose, or reuse the stream or its underlying streams/buffers until after the job and the owning objects are disposed.
+ /// + /// The BufferedStreamSource will still need to be disposed after the job, either with a using declaration or by transferring ownership of it to the job (which should be in a using declaration).
+ ///
+ /// + /// + public static IAsyncMemorySource UseEntireStreamAndDisposeWithSource(Stream stream) { return new BufferedStreamSource(stream, true, true); } - public static IAsyncMemorySource UseStreamRemainderAndDispose(Stream stream) + + /// + /// The stream will be closed and disposed with the BufferedStreamSource. + /// You must not close, dispose, or reuse the stream or its underlying streams/buffers until after the job and the owning objects are disposed. + /// strong>The BufferedStreamSource will still need to be disposed after the job, either with a using declaration or by transferring ownership of it to the job (which should be in a using declaration). + /// + /// + /// + public static IAsyncMemorySource UseStreamRemainderAndDisposeWithSource(Stream stream) { return new BufferedStreamSource(stream, true, false); } diff --git a/src/Imageflow/Fluent/BuildEncodeResult.cs b/src/Imageflow/Fluent/BuildEncodeResult.cs index 001e1d3..1d460db 100644 --- a/src/Imageflow/Fluent/BuildEncodeResult.cs +++ b/src/Imageflow/Fluent/BuildEncodeResult.cs @@ -1,6 +1,6 @@ namespace Imageflow.Fluent { - public class BuildEncodeResult + public record class BuildEncodeResult { // internal BuildEncodeResult(string preferredMimeType, // string preferredExtension, int ioId, int width, int height, IOutputDestination destination) @@ -14,7 +14,7 @@ public class BuildEncodeResult // Destination = destination; // } - internal BuildEncodeResult() + public BuildEncodeResult() { } // maps to "preferred_mime_type" in json diff --git a/src/Imageflow/Fluent/BuildJobResult.cs b/src/Imageflow/Fluent/BuildJobResult.cs index 5e9e09a..890efcb 100644 --- a/src/Imageflow/Fluent/BuildJobResult.cs +++ b/src/Imageflow/Fluent/BuildJobResult.cs @@ -7,6 +7,14 @@ namespace Imageflow.Fluent public class BuildJobResult { + + [Obsolete("Use ImageJob.FinishAsync() to get a result; you should never create a BuildJobResult directly.")] + public BuildJobResult(){ + _encodeResults = new Dictionary(); + DecodeResults = new List(); + EncodeResults = new List(); + PerformanceDetails = new PerformanceDetails(null); + } private BuildJobResult(IReadOnlyCollection decodeResults, IReadOnlyCollection encodeResults, diff --git a/src/Imageflow/Fluent/BuildNode.cs b/src/Imageflow/Fluent/BuildNode.cs index 900c182..13175f5 100644 --- a/src/Imageflow/Fluent/BuildNode.cs +++ b/src/Imageflow/Fluent/BuildNode.cs @@ -387,7 +387,7 @@ public BuildNode CopyRectTo(BuildNode canvas, Rectangle area, Point to) /// /// /// - public BuildNode DrawImageExactTo(BuildNode canvas, Rectangle to, ResampleHints hints, CompositingMode? blend) + public BuildNode DrawImageExactTo(BuildNode canvas, Rectangle to, ResampleHints? hints, CompositingMode? blend) // => NodeWithCanvas(canvas, new // { // draw_image_exact = new diff --git a/src/Imageflow/Fluent/IAsyncMemorySource.cs b/src/Imageflow/Fluent/IAsyncMemorySource.cs index 89225bc..c355243 100644 --- a/src/Imageflow/Fluent/IAsyncMemorySource.cs +++ b/src/Imageflow/Fluent/IAsyncMemorySource.cs @@ -1,7 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using Imageflow.Bindings; - -namespace Imageflow.Fluent; +namespace Imageflow.Fluent; public interface IAsyncMemorySource: IDisposable { diff --git a/src/Imageflow/Fluent/IBytesSource.cs b/src/Imageflow/Fluent/IBytesSource.cs index db73e47..06160af 100644 --- a/src/Imageflow/Fluent/IBytesSource.cs +++ b/src/Imageflow/Fluent/IBytesSource.cs @@ -1,8 +1,4 @@ -using System.Buffers; -using Imageflow.Internal.Helpers; -using Microsoft.IO; - -namespace Imageflow.Fluent +namespace Imageflow.Fluent { [Obsolete("Use IMemorySource instead")] public interface IBytesSource: IDisposable @@ -15,9 +11,9 @@ public interface IBytesSource: IDisposable } /// - /// Represents a source backed by an ArraySegment or byte[] array. Use MemorySource for ReadOnlyMemory backed memory instead. + /// Represents a source backed by an ArraySegment or byte[] array. Use MemorySource for ReadOnlyMemory<byte> backed memory instead. /// - [Obsolete("Use MemorySource instead")] + [Obsolete("Use MemorySource.Borrow(bytes, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed) instead")] public readonly struct BytesSource : IBytesSource { public BytesSource(byte[] bytes) @@ -49,65 +45,7 @@ public static implicit operator MemorySource(BytesSource source) return new MemorySource(source._bytes); } } - /// - /// Represents a image source that is backed by a Stream. - /// - [Obsolete("Use BufferedStreamSource instead")] - public class StreamSource(Stream underlying, bool disposeUnderlying) : IBytesSource - { - private static readonly RecyclableMemoryStreamManager Mgr - = new(); - private RecyclableMemoryStream? _copy; - - public void Dispose() - { - if (disposeUnderlying) - { - underlying?.Dispose(); - } - _copy?.Dispose(); - } - /// - /// Note that bytes will not be valid after StreamSource is disposed - /// - /// - /// - /// - public async Task> GetBytesAsync(CancellationToken cancellationToken) - { - if (_copy != null) - { - return new ArraySegment(_copy.GetBuffer(), 0, - (int) _copy.Length); - } - var length = underlying.CanSeek ? underlying.Length : 0; - if (length >= int.MaxValue) throw new OverflowException("Streams cannot exceed 2GB"); - - if (underlying is MemoryStream underlyingMemoryStream && - underlyingMemoryStream.TryGetBufferSliceAllWrittenData(out var underlyingBuffer)) - { - return underlyingBuffer; - } - - if (_copy == null) - { - _copy = new RecyclableMemoryStream(Mgr,"StreamSource: IBytesSource", length); - await underlying.CopyToAsync(_copy,81920, cancellationToken); - } - - return new ArraySegment(_copy.GetBuffer(), 0, - (int) _copy.Length); - } - - internal bool AsyncPreferred => _copy != null && underlying is not MemoryStream && underlying is not UnmanagedMemoryStream; - - public static implicit operator BytesSourceAdapter(StreamSource source) - { - return new BytesSourceAdapter(source); - } - } - public static class BytesSourceExtensions { #pragma warning disable CS0618 // Type or member is obsolete diff --git a/src/Imageflow/Fluent/IOutputDestination.cs b/src/Imageflow/Fluent/IOutputDestination.cs index 289b318..9812fa6 100644 --- a/src/Imageflow/Fluent/IOutputDestination.cs +++ b/src/Imageflow/Fluent/IOutputDestination.cs @@ -126,6 +126,7 @@ public class BytesDestination : IOutputDestination, IOutputSink, IAsyncOutputSin private MemoryStream? _m; public void Dispose() { + } public Task RequestCapacityAsync(int bytes) diff --git a/src/Imageflow/Fluent/ImageJob.cs b/src/Imageflow/Fluent/ImageJob.cs index fad44c4..79f7c14 100644 --- a/src/Imageflow/Fluent/ImageJob.cs +++ b/src/Imageflow/Fluent/ImageJob.cs @@ -1,5 +1,4 @@ -using System.Buffers; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Text; using System.Text.Json; @@ -53,7 +52,7 @@ public BuildNode Decode(IBytesSource source, int ioId, DecodeCommands? commands) public BuildNode Decode(Stream source, bool disposeStream) => Decode( source, disposeStream, GenerateIoId()); public BuildNode Decode(Stream source, bool disposeStream, int ioId) => - Decode( disposeStream ? BufferedStreamSource.UseEntireStreamAndDispose(source) + Decode( disposeStream ? BufferedStreamSource.UseEntireStreamAndDisposeWithSource(source) : BufferedStreamSource.BorrowEntireStream(source), ioId); @@ -127,21 +126,21 @@ private BuildNode CreateCanvas(uint w, uint h, AnyColor color, PixelFormat forma - [Obsolete("Use overloads that take a MemorySource")] + [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString) => BuildCommandString(source, null, dest, null, commandString); - [Obsolete("Use overloads that take a MemorySource")] + [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, int? destIoId, string commandString) => BuildCommandString(source, sourceIoId, dest, destIoId, commandString, null); - [Obsolete("Use overloads that take a MemorySource")] + [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString, ICollection? watermarks) => BuildCommandString(source, null, dest, null, commandString, watermarks); - [Obsolete("Use overloads that take a MemorySource")] + [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, int? destIoId, string commandString, ICollection? watermarks) { @@ -405,7 +404,7 @@ private async Task PrepareForSubprocessAsync(Cancellati job.Cleanup.Add(file); return (io_id: pair.Key, direction: "in", io: new JsonObject {{"file", file.Path}}, - bytes, Length: bytes.Length, File: file); + bytes, bytes.Length, File: file); }))).ToArray(); @@ -689,7 +688,7 @@ protected virtual void Dispose(bool disposing) /// /// /// - [Obsolete("Use GetImageInfoAsync(IMemorySource ...) instead; this method disposes the source and will be removed in a future version")] + [Obsolete("Use GetImageInfoAsync(IMemorySource, DataLifetime) instead; this method is less efficient and lacks clarity on disposing the source.")] public static Task GetImageInfo(IBytesSource image) => GetImageInfo(image, CancellationToken.None); @@ -700,7 +699,7 @@ public static Task GetImageInfo(IBytesSource image) /// /// /// - [Obsolete("Use GetImageInfoAsync(IMemorySource ..) instead; this method disposes the source and will be removed in a future version")] + [Obsolete("Use GetImageInfoAsync(IMemorySource, DataLifetime) instead; this method is less efficient and lacks clarity on disposing the source.")] public static async Task GetImageInfo(IBytesSource image, CancellationToken cancellationToken) { try @@ -723,29 +722,51 @@ public static async Task GetImageInfo(IBytesSource image, Cancellatio /// Does NOT dispose the IMemorySource. /// /// + /// /// - public static ImageInfo GetImageInfo(IMemorySource image) + public static ImageInfo GetImageInfo(IMemorySource image, SourceLifetime disposeSource) { - var inputMemory = image.BorrowReadOnlyMemory(); - using var ctx = new JobContext(); - ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); - return ctx.GetImageInfo(0); + try + { + var inputMemory = image.BorrowReadOnlyMemory(); + using var ctx = new JobContext(); + ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); + return ctx.GetImageInfo(0); + }finally + { + if (disposeSource != SourceLifetime.Borrowed) + { + image.Dispose(); + } + } } - + /// /// Returns dimensions and format of the provided image stream or memory. /// Does not offload processing to a thread pool; will be CPU bound unless IMemorySource is not yet in memory. /// Does not dispose the IMemorySource. /// /// + /// /// /// - public static async ValueTask GetImageInfoAsync(IAsyncMemorySource image, CancellationToken cancellationToken = default) + public static async ValueTask GetImageInfoAsync(IAsyncMemorySource image, SourceLifetime disposeSource, CancellationToken cancellationToken = default) { - var inputMemory = await image.BorrowReadOnlyMemoryAsync(cancellationToken); - using var ctx = new JobContext(); - ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); - return ctx.GetImageInfo(0); + try + { + var inputMemory = await image.BorrowReadOnlyMemoryAsync(cancellationToken); + if (inputMemory.Length == 0) throw new ArgumentException("Input image is empty"); + if (cancellationToken.IsCancellationRequested) throw new TaskCanceledException(); + using var ctx = new JobContext(); + ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); + return ctx.GetImageInfo(0); + }finally + { + if (disposeSource != SourceLifetime.Borrowed) + { + image.Dispose(); + } + } } /// /// Returns true if it is likely that Imageflow can decode the given image based on the first 12 bytes of the file. diff --git a/src/Imageflow/Fluent/InputWatermark.cs b/src/Imageflow/Fluent/InputWatermark.cs index f06d1fd..7bd792a 100644 --- a/src/Imageflow/Fluent/InputWatermark.cs +++ b/src/Imageflow/Fluent/InputWatermark.cs @@ -4,14 +4,14 @@ namespace Imageflow.Fluent { public class InputWatermark { - [Obsolete("Use InputWatermark(IMemorySource source, WatermarkOptions watermark) instead")] + [Obsolete("Call .ToMemorySource() on the source to use InputWatermark(IMemorySource source, WatermarkOptions watermark instead. For improved performance, use the new BufferedStreamSource and MemorySource classes directly.")] public InputWatermark(IBytesSource source, int ioId, WatermarkOptions watermark) { Source = source.ToMemorySource(); IoId = ioId; Watermark = watermark; } - [Obsolete("Use InputWatermark(IMemorySource source, WatermarkOptions watermark) instead")] + [Obsolete("Call .ToMemorySource() on the source to use InputWatermark(IMemorySource source, WatermarkOptions watermark instead. For improved performance, use the new BufferedStreamSource and MemorySource classes directly.")] public InputWatermark(IBytesSource source, WatermarkOptions watermark) { Source = source.ToMemorySource(); diff --git a/src/Imageflow/Fluent/SourceLifetime.cs b/src/Imageflow/Fluent/SourceLifetime.cs new file mode 100644 index 0000000..c6ff4b2 --- /dev/null +++ b/src/Imageflow/Fluent/SourceLifetime.cs @@ -0,0 +1,17 @@ +namespace Imageflow.Fluent; + +public enum SourceLifetime +{ + /// + /// The function you are invoking will dispose the source when the task is complete (or cancelled/failed). The source and underlying memory/stream must remain valid until the task is complete. + /// + NowOwnedAndDisposedByTask, + /// + /// By using this, you solemnly swear to not close, dispose, or reuse the data source object or its underlying memory/stream until after the job is disposed. The job will dispose the source when the job is disposed. + /// + TransferOwnership, + /// + /// You swear not to close, dispose, or reuse the data source object or its underlying memory/stream until after the job is disposed. You remain responsible for disposing and cleaning up the source after the job is disposed. + /// + Borrowed +} \ No newline at end of file diff --git a/src/Imageflow/Fluent/StreamSource.cs b/src/Imageflow/Fluent/StreamSource.cs new file mode 100644 index 0000000..2d5c625 --- /dev/null +++ b/src/Imageflow/Fluent/StreamSource.cs @@ -0,0 +1,63 @@ +using Imageflow.Internal.Helpers; +using Microsoft.IO; + +namespace Imageflow.Fluent; + +/// +/// Represents a image source that is backed by a Stream. +/// +[Obsolete("Use BufferedStreamSource.UseStreamRemainderAndDispose() or BufferedStreamSource.BorrowStreamRemainder() instead")] +public class StreamSource(Stream underlying, bool disposeUnderlying) : IBytesSource +{ + private static readonly RecyclableMemoryStreamManager Mgr + = new(); + private RecyclableMemoryStream? _copy; + + public void Dispose() + { + if (disposeUnderlying) + { + underlying?.Dispose(); + } + _copy?.Dispose(); + } + + /// + /// Note that bytes will not be valid after StreamSource is disposed + /// + /// + /// + /// + public async Task> GetBytesAsync(CancellationToken cancellationToken) + { + if (_copy != null) + { + return new ArraySegment(_copy.GetBuffer(), 0, + (int) _copy.Length); + } + var length = underlying.CanSeek ? underlying.Length : 0; + if (length >= int.MaxValue) throw new OverflowException("Streams cannot exceed 2GB"); + + if (underlying is MemoryStream underlyingMemoryStream && + underlyingMemoryStream.TryGetBufferSliceAllWrittenData(out var underlyingBuffer)) + { + return underlyingBuffer; + } + + if (_copy == null) + { + _copy = new RecyclableMemoryStream(Mgr,"StreamSource: IBytesSource", length); + await underlying.CopyToAsync(_copy,81920, cancellationToken); + } + + return new ArraySegment(_copy.GetBuffer(), 0, + (int) _copy.Length); + } + + internal bool AsyncPreferred => _copy != null && underlying is not MemoryStream && underlying is not UnmanagedMemoryStream; + + public static implicit operator BytesSourceAdapter(StreamSource source) + { + return new BytesSourceAdapter(source); + } +} \ No newline at end of file diff --git a/src/Imageflow/IO/ProcessEx.cs b/src/Imageflow/IO/ProcessEx.cs index 7777388..f4d20a8 100644 --- a/src/Imageflow/IO/ProcessEx.cs +++ b/src/Imageflow/IO/ProcessEx.cs @@ -81,7 +81,7 @@ public static async Task RunAsync(ProcessStartInfo processStartI }; var standardErrorResults = new TaskCompletionSource(); - process.ErrorDataReceived += (sender, args) => { + process.ErrorDataReceived += (_, args) => { if (args.Data != null) standardError.Add(args.Data); else diff --git a/src/Imageflow/Imageflow.Net.csproj b/src/Imageflow/Imageflow.Net.csproj index 5d51f87..7513809 100644 --- a/src/Imageflow/Imageflow.Net.csproj +++ b/src/Imageflow/Imageflow.Net.csproj @@ -15,6 +15,9 @@ true Imageflow README.md + + true + 0.10.2 diff --git a/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs b/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs index db4f636..a3a0b9d 100644 --- a/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs +++ b/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs @@ -7,8 +7,6 @@ namespace Imageflow.Internal.Helpers; // The .NET Foundation licenses this file to you under the MIT license. // Imazen licenses any changes to th -#nullable enable - internal static partial class ArgumentNullThrowHelper { /// Throws an if is null. @@ -37,4 +35,20 @@ public static void ThrowIfNull( internal static void Throw(string? paramName) => throw new ArgumentNullException(paramName); #endif -} \ No newline at end of file +} + +internal static class ObjectDisposedHelper +{ + public static void ThrowIf([DoesNotReturnIf(true)]bool condition, object instance) + { +#if NET8_0_OR_GREATER + + ObjectDisposedException.ThrowIf(condition, instance); +#else + if (condition) + { + throw new ObjectDisposedException(instance?.GetType().Name); + } +#endif + } +} diff --git a/tests/Imageflow.Test/TestApi.cs b/tests/Imageflow.Test/TestApi.cs index 13fb4c4..d7466ef 100644 --- a/tests/Imageflow.Test/TestApi.cs +++ b/tests/Imageflow.Test/TestApi.cs @@ -16,7 +16,8 @@ public TestApi(ITestOutputHelper output) } [Fact] - public async void TestGetImageInfo() + [Obsolete("Obsolete")] + public async void TestGetImageInfoLegacy() { var imageBytes = Convert.FromBase64String( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); @@ -30,6 +31,37 @@ public async void TestGetImageInfo() Assert.Equal(PixelFormat.Bgra_32, info.FrameDecodesInto); } + // test the new GetImageInfoAsync + [Fact] + public async void TestGetImageInfoAsync() + { + var imageBytes = Convert.FromBase64String( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); + + var info = await ImageJob.GetImageInfoAsync(new MemorySource(imageBytes),SourceLifetime.NowOwnedAndDisposedByTask); + + Assert.Equal(1, info.ImageWidth); + Assert.Equal(1, info.ImageHeight); + Assert.Equal("png", info.PreferredExtension); + Assert.Equal("image/png", info.PreferredMimeType); + Assert.Equal(PixelFormat.Bgra_32, info.FrameDecodesInto); + } + + // Test GetImageInfo + [Fact] + public void TestGetImageInfoSync() + { + var imageBytes = Convert.FromBase64String( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); + + var info = ImageJob.GetImageInfo(new MemorySource(imageBytes),SourceLifetime.NowOwnedAndDisposedByTask); + + Assert.Equal(1, info.ImageWidth); + Assert.Equal(1, info.ImageHeight); + Assert.Equal("png", info.PreferredExtension); + Assert.Equal("image/png", info.PreferredMimeType); + Assert.Equal(PixelFormat.Bgra_32, info.FrameDecodesInto); + } [Fact] @@ -85,7 +117,7 @@ public async Task TestAllJob() .RoundAllImageCornersPercent(100, AnyColor.Black) .RoundAllImageCorners(1, AnyColor.Transparent) .ConstrainWithin(5, 5) - .Watermark(new BytesSource(imageBytes), + .Watermark(new MemorySource(imageBytes), new WatermarkOptions() .SetMarginsLayout( new WatermarkMargins(WatermarkAlign.Image, 1,1,1,1), @@ -230,7 +262,7 @@ public async Task TestBuildCommandString() { var r = await b.BuildCommandString( - new BytesSource(imageBytes), // or new StreamSource(Stream stream, bool disposeStream) + new MemorySource(imageBytes), // or new StreamSource(Stream stream, bool disposeStream) new BytesDestination(), // or new StreamDestination "width=3&height=2&mode=stretch&scale=both&format=webp") .Finish().InProcessAsync(); @@ -243,7 +275,8 @@ public async Task TestBuildCommandString() } [Fact] - public async Task TestBuildCommandStringWithWatermarks() + [Obsolete("Obsolete")] + public async Task TestBuildCommandStringWithWatermarksLegacy() { var imageBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); using (var b = new ImageJob()) @@ -263,8 +296,31 @@ public async Task TestBuildCommandStringWithWatermarks() } } + [Fact] - public async Task TestBuildCommandStringWithStreamsAndWatermarks() + public async Task TestBuildCommandStringWithWatermarks() + { + var imageBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); + using (var b = new ImageJob()) + { + var watermarks = new List(); + watermarks.Add(new InputWatermark(new MemorySource(imageBytes), new WatermarkOptions())); + watermarks.Add(new InputWatermark(new MemorySource(imageBytes), new WatermarkOptions().SetGravity(new ConstraintGravity(100,100)))); + + var r = await b.BuildCommandString( + new MemorySource(imageBytes), + new BytesDestination(), + "width=3&height=2&mode=stretch&scale=both&format=webp",watermarks).Finish().InProcessAsync(); + + Assert.Equal(3, r.First!.Width); + Assert.Equal("webp", r.First.PreferredExtension); + Assert.True(r.First.TryGetBytes().HasValue); + } + + } + [Fact] + [Obsolete("Obsolete")] + public async Task TestBuildCommandStringWithStreamsAndWatermarksLegacy() { var imageBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); // var stream1 = new BufferedStream(new System.IO.MemoryStream(imageBytes)); @@ -292,7 +348,32 @@ public async Task TestBuildCommandStringWithStreamsAndWatermarks() } - + [Fact] + public async Task TestBuildCommandStringWithStreamsAndWatermarks() + { + var imageBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); + // var stream1 = new BufferedStream(new System.IO.MemoryStream(imageBytes)); + // var stream2 = new BufferedStream(new System.IO.MemoryStream(imageBytes)); + var stream1 = new BufferedStream(new MemoryStream(imageBytes)); + Assert.Equal(137, stream1.ReadByte()); + stream1.Seek(0, SeekOrigin.Begin); + var stream2 = new BufferedStream(new MemoryStream(imageBytes)); + var stream3 = new BufferedStream(new MemoryStream(imageBytes)); + using var b = new ImageJob(); + var watermarks = new List(); + watermarks.Add(new InputWatermark(BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream1), new WatermarkOptions())); + watermarks.Add(new InputWatermark(BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream2), new WatermarkOptions().SetGravity(new ConstraintGravity(100,100)))); + + var r = await b.BuildCommandString( + BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream3), + new BytesDestination(), + "width=3&height=2&mode=stretch&scale=both&format=webp",watermarks).Finish().InProcessAsync(); + + Assert.Equal(3, r.First!.Width); + Assert.Equal("webp", r.First.PreferredExtension); + Assert.True(r.First.TryGetBytes().HasValue); + } + [Fact] public async Task TestFilesystemJobPrep() { @@ -381,7 +462,7 @@ public async Task TestCustomDownscalingAndDecodeEncodeResults() JpegDownscalingMode = DecoderDownscalingMode.Fastest, DiscardColorProfile = true }; - var r = await b.Decode(new BytesSource(imageBytes), 0, cmd) + var r = await b.Decode(new MemorySource(imageBytes), 0, cmd) .Distort(30, 20, new ResampleHints().SetSharpen(50.0f, SharpenWhen.Always).SetResampleFilters(InterpolationFilter.Robidoux_Fast, InterpolationFilter.Cubic)) .ConstrainWithin(5, 5) .EncodeToBytes(new LodePngEncoder()).Finish().InProcessAsync(); diff --git a/tests/Imageflow.Test/TestJson.cs b/tests/Imageflow.Test/TestJson.cs index 34a33b5..11e5ba8 100644 --- a/tests/Imageflow.Test/TestJson.cs +++ b/tests/Imageflow.Test/TestJson.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; using Imageflow.Fluent; using Xunit; using Xunit.Abstractions; @@ -49,7 +48,7 @@ public void TestAllJob() .RoundAllImageCornersPercent(100, AnyColor.Black) .RoundAllImageCorners(1, AnyColor.Transparent) .ConstrainWithin(5, 5) - .Watermark(new BytesSource(Array.Empty()), + .Watermark(new MemorySource(new byte[]{}), new WatermarkOptions() .SetMarginsLayout( new WatermarkMargins(WatermarkAlign.Image, 1, 1, 1, 1), diff --git a/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json b/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json index 78b81df..3394a9b 100644 --- a/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json +++ b/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json @@ -132,94 +132,6 @@ "System.Text.Json": "[6.*, )" } } - }, - ".NETFramework,Version=v4.7.2/win": { - "Imageflow.NativeRuntime.osx-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" - }, - "Imageflow.NativeRuntime.ubuntu-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" - }, - "Imageflow.NativeRuntime.win-x86": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" - }, - "Imageflow.NativeRuntime.win-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" - } - }, - ".NETFramework,Version=v4.7.2/win-arm64": { - "Imageflow.NativeRuntime.osx-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" - }, - "Imageflow.NativeRuntime.ubuntu-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" - }, - "Imageflow.NativeRuntime.win-x86": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" - }, - "Imageflow.NativeRuntime.win-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" - } - }, - ".NETFramework,Version=v4.7.2/win-x64": { - "Imageflow.NativeRuntime.osx-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" - }, - "Imageflow.NativeRuntime.ubuntu-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" - }, - "Imageflow.NativeRuntime.win-x86": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" - }, - "Imageflow.NativeRuntime.win-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" - } - }, - ".NETFramework,Version=v4.7.2/win-x86": { - "Imageflow.NativeRuntime.osx-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" - }, - "Imageflow.NativeRuntime.ubuntu-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" - }, - "Imageflow.NativeRuntime.win-x86": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" - }, - "Imageflow.NativeRuntime.win-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" - } } } } \ No newline at end of file diff --git a/tests/Imageflow.TestWebAOT/packages.lock.json b/tests/Imageflow.TestWebAOT/packages.lock.json index afa3c4a..3e3c2cf 100644 --- a/tests/Imageflow.TestWebAOT/packages.lock.json +++ b/tests/Imageflow.TestWebAOT/packages.lock.json @@ -4,9 +4,9 @@ "net8.0": { "Microsoft.DotNet.ILCompiler": { "type": "Direct", - "requested": "[8.0.1, )", - "resolved": "8.0.1", - "contentHash": "vEmBtYRsH2gpnUJ9p7ElgB2UZSx5+CF2nHVr2QJ/3kzlfAYBxqXMx3VmQcxOcrsDEuF4/n1VH95qobrj8xnPdw==" + "requested": "[8.0.2, )", + "resolved": "8.0.2", + "contentHash": "rGE3+673xn1iZJCoMIuUb9lhqje4DiCB+qTyPAJ97Bw15c4N69ZbMAO/GJyxchaoLJCHIPXP52hwxEzqrllwCg==" }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", From cf01242a548a6d28f7f9412dac6c972d1cec5284 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 15:33:06 -0700 Subject: [PATCH 04/22] Update the examples to use MemorySource. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e0a6dbe..5300715 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ -Imageflow.NET is a .NET API for [Imageflow](https://github.com/imazen/imageflow), the fast image optimization and processing library for web servers. Imageflow focuses on security, quality, and performance - in that order. Imageflow.NET is a .NET 8.0 & .NET Standard 2.0 library, and as such is compatible with .NET 4.6.2+, .NET Core 2.0+, and .NET 5/6/7/8. +Imageflow.NET is a .NET API for [Imageflow](https://github.com/imazen/imageflow), the fast image optimization and processing library for web servers. Imageflow focuses on security, quality, and performance - in that order. Imageflow.NET is a .NET 8.0 & .NET Standard 2.0 library, and as such is compatible with .NET 4.6.2+, .NET Core 2.0+, and .NET 5/6/7/8/9. -Note: We recently switched from Newtonsoft to System.Text.Json to support AOT and trimming; see [CHANGES.md](https://github.com/imazen/imageflow-dotnet/blob/master/CHANGES.md) for details and some breaking changes. +Note: We recently switched from Newtonsoft to System.Text.Json to support AOT and trimming; see [CHANGES.md](https://github.com/imazen/imageflow-dotnet/blob/master/CHANGES.md) for details and some breaking changes. There are also new classes for attaching source image data to jobs; use MemorySource.* over ByteSource and BufferedStreamSource.* instead of StreamSource. ### On .NET Core 3.x and .NET 5/6/7/8 (or if using PackageReference on .NET 4.x) @@ -48,7 +48,7 @@ public async void TestGetImageInfo() var imageBytes = Convert.FromBase64String( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); - var info = await ImageJob.GetImageInfo(new BytesSource(imageBytes)); + var info = await ImageJob.GetImageInfo(new MemorySource(imageBytes)); Assert.Equal(info.ImageWidth, 1); Assert.Equal(info.ImageHeight, 1); @@ -149,7 +149,7 @@ public async Task TestBuildCommandString() { var r = await b.BuildCommandString( - new BytesSource(imageBytes), // or new StreamSource(Stream stream, bool disposeStream) + new MemorySource(imageBytes), // or new StreamSource(Stream stream, bool disposeStream) new BytesDestination(), // or new StreamDestination "width=3&height=2&mode=stretch&scale=both&format=webp&webp.quality=80") .Finish().InProcessAsync(); From 0a203608d417a0fb62657316e0f9609944f4ea2c Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 15:39:01 -0700 Subject: [PATCH 05/22] Describe changes --- CHANGES.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 615e000..e44dd4e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,11 +2,16 @@ ## v0.13 -This release makes user-facing changes: +This release makes user-facing changes with deprecation warnings. Please review your build warnings to avoid breakage in the future. +* There are new classes for attaching source image data to jobs; use MemorySource.* over ByteSource and BufferedStreamSource.* instead of StreamSource. +* Microsoft.IO.RecyclableMemoryStream 3.x is now required +* System.Buffers and System.Memory 4.x+ are now required on .NET 4.x / .NET Standard 2.0 +* InputWatermark.Source is now IMemorySource instead of IBytesSource +It also makes lots of internal changes to increase performance, eliminate unnecessary allocations/copies, and improve compatibility with AOT and trimming. -InputWatermark.Source is now IMemorySource instead of IBytesSource\ +It is now possible to provide ReadOnlyMemory data and IOwnedMemory data, without copying to a byte[] array. The new IAsyncMemorySource interface allows for asynchronous data sources, and the new IMemorySource interface allows for synchronous data sources. ## v0.12 (2024-02-06) From d915523e2606db4c3c750c18157622573d59075d Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 16:03:41 -0700 Subject: [PATCH 06/22] Only generate package on build for published nuget packages --- Directory.Build.props | 2 +- src/Imageflow.AllPlatforms/Imageflow.AllPlatforms.csproj | 1 + src/Imageflow/Imageflow.Net.csproj | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index c7e67fe..466dfe5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -28,7 +28,7 @@ Image;Resize;Optimize;Crop;Gif;Jpg;Jpeg;Bitmap;Png;Core;WebP;ImageResizer;Imageflow;Rotate;Imaging;Crunch false - true + diff --git a/src/Imageflow.AllPlatforms/Imageflow.AllPlatforms.csproj b/src/Imageflow.AllPlatforms/Imageflow.AllPlatforms.csproj index 8fc9794..e12c936 100644 --- a/src/Imageflow.AllPlatforms/Imageflow.AllPlatforms.csproj +++ b/src/Imageflow.AllPlatforms/Imageflow.AllPlatforms.csproj @@ -11,6 +11,7 @@ true icon.png README.md + true diff --git a/src/Imageflow/Imageflow.Net.csproj b/src/Imageflow/Imageflow.Net.csproj index 7513809..c5c5767 100644 --- a/src/Imageflow/Imageflow.Net.csproj +++ b/src/Imageflow/Imageflow.Net.csproj @@ -18,6 +18,7 @@ true 0.10.2 + true From f9c0805c58d968647e3488ef57c915e22c2c24a3 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 16:04:04 -0700 Subject: [PATCH 07/22] Remove default constructor on BuildJobResult and BuildEncodeResult; these are not user-created types. --- CHANGES.md | 1 + src/Imageflow/Fluent/BuildEncodeResult.cs | 2 +- src/Imageflow/Fluent/BuildJobResult.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e44dd4e..8d951e8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ It is now possible to provide ReadOnlyMemory data and IOwnedMemory d ## v0.12 (2024-02-06) * Fix compatibility with RecyclableMemoryStream 3.x, drop compatibility with 1.x +* Remove default constructor on BuildJobResult and BuildEncodeResult; these are not user-created types. ## v0.11 (2024-01-29) diff --git a/src/Imageflow/Fluent/BuildEncodeResult.cs b/src/Imageflow/Fluent/BuildEncodeResult.cs index 1d460db..9901b3b 100644 --- a/src/Imageflow/Fluent/BuildEncodeResult.cs +++ b/src/Imageflow/Fluent/BuildEncodeResult.cs @@ -14,7 +14,7 @@ public record class BuildEncodeResult // Destination = destination; // } - public BuildEncodeResult() + internal BuildEncodeResult() { } // maps to "preferred_mime_type" in json diff --git a/src/Imageflow/Fluent/BuildJobResult.cs b/src/Imageflow/Fluent/BuildJobResult.cs index 890efcb..82895ba 100644 --- a/src/Imageflow/Fluent/BuildJobResult.cs +++ b/src/Imageflow/Fluent/BuildJobResult.cs @@ -9,7 +9,7 @@ public class BuildJobResult { [Obsolete("Use ImageJob.FinishAsync() to get a result; you should never create a BuildJobResult directly.")] - public BuildJobResult(){ + internal BuildJobResult(){ _encodeResults = new Dictionary(); DecodeResults = new List(); EncodeResults = new List(); From b33ebc63f8e672b0be0e7812f3f08cdc37c18d5b Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 16:04:31 -0700 Subject: [PATCH 08/22] Don't test multiple versions of RecyclableMemoryStream; we only support 3.x --- .github/workflows/dotnet-core.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index f9a2dcf..e940999 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -25,7 +25,6 @@ jobs: test_aot: true - os: ubuntu-latest SystemTextJsonVersion: 8.0.1 - RecyclableMemoryStreamVersion: 3.0.0 - os: windows-latest pack: true docs: true From c2228f2441a6f6224917fa70b1a6ae6370e096cd Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 16:29:16 -0700 Subject: [PATCH 09/22] Upgrade test framework packages, drop a compat rule --- src/Imageflow/CompatibilitySuppressions.xml | 7 - tests/Imageflow.Test/Imageflow.Test.csproj | 14 +- tests/Imageflow.Test/TestJson.cs | 21 + tests/Imageflow.Test/packages.lock.json | 4435 ++--------------- ...flow.TestDotNetFullPackageReference.csproj | 4 +- .../packages.lock.json | 189 +- 6 files changed, 500 insertions(+), 4170 deletions(-) diff --git a/src/Imageflow/CompatibilitySuppressions.xml b/src/Imageflow/CompatibilitySuppressions.xml index bc81cde..d1efb99 100644 --- a/src/Imageflow/CompatibilitySuppressions.xml +++ b/src/Imageflow/CompatibilitySuppressions.xml @@ -57,13 +57,6 @@ lib/netstandard2.0/Imageflow.Net.dll true - - CP0009 - T:Imageflow.Fluent.BuildEncodeResult - lib/netstandard2.0/Imageflow.Net.dll - lib/netstandard2.0/Imageflow.Net.dll - true - CP0009 T:Imageflow.Fluent.BuildJobResult diff --git a/tests/Imageflow.Test/Imageflow.Test.csproj b/tests/Imageflow.Test/Imageflow.Test.csproj index 4d20f3b..d3c83c8 100644 --- a/tests/Imageflow.Test/Imageflow.Test.csproj +++ b/tests/Imageflow.Test/Imageflow.Test.csproj @@ -21,12 +21,18 @@ - - + + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/Imageflow.Test/TestJson.cs b/tests/Imageflow.Test/TestJson.cs index 11e5ba8..c402dfb 100644 --- a/tests/Imageflow.Test/TestJson.cs +++ b/tests/Imageflow.Test/TestJson.cs @@ -78,6 +78,27 @@ public void TestAllJob() else Console.Error.WriteLine(e.ToString()); } + + // For using SystemTextJson.JsonDiffPatch, which fails to diff rn, and also requires Json 8.0 + // var expectedJson = + // JsonNode.Parse(SortPropertiesRecursive(JsonNode.Parse(expected))!.ToString()); //JsonNode.Parse(expected); + // var actualJson = + // JsonNode.Parse(SortPropertiesRecursive(JsonNode.Parse(jsonStr))!.ToString()); //JsonNode.Parse(jsonStr); + // try + // { + // JsonAssert.Equal(expectedJson, actualJson); + // + // } + // catch (Exception e) + // { + // Console.Error.WriteLine("Expected: " + expectedJson); + // Console.Error.WriteLine("Actual: " + actualJson); + // // Don't throw on CI + // if (Environment.GetEnvironmentVariable("CI") == null) + // throw; + // else + // Console.Error.WriteLine(e.ToString()); + // } } private static JsonNode? SortPropertiesRecursive(JsonNode? n) diff --git a/tests/Imageflow.Test/packages.lock.json b/tests/Imageflow.Test/packages.lock.json index 73d0486..4d30abe 100644 --- a/tests/Imageflow.Test/packages.lock.json +++ b/tests/Imageflow.Test/packages.lock.json @@ -4,9 +4,9 @@ ".NETFramework,Version=v4.8.1": { "coverlet.collector": { "type": "Direct", - "requested": "[3.1.0, )", - "resolved": "3.1.0", - "contentHash": "YzYqSRrjoP5lULBhTDcTOjuM4IDPPi6PhFsl4w8EI4WdZhE6llt7E38Tg4CHyrS+QKwyu1+9OwkdDgluOdoBTw==" + "requested": "[6.0.1, )", + "resolved": "6.0.1", + "contentHash": "q/8r0muHlF7TbhMUTBAk43KteZYG2ECV96pbzkToQwgjp3BvbS766svUcO6va+cohWOkigeBWmHs0Vu5vQnESA==" }, "Imageflow.NativeTool.win-x86": { "type": "Direct", @@ -31,11 +31,11 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[15.3.0-preview-20170628-02, )", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "g5Ek236LaKEzPI13kK4c2wA8eaj44PfBucXRp/7FRBXpSbRqv/1McJcGOjDbs89d2dyeGLB6jGqSpQs3Y9Wh7w==", + "requested": "[17.9.0, )", + "resolved": "17.9.0", + "contentHash": "7GUNAUbJYn644jzwLm5BD3a2p9C1dmP8Hr6fDPDxgItQk9hBs1Svdxzz07KQ/UphMSmgza9AbijBJGmw5D658A==", "dependencies": { - "Microsoft.TestPlatform.TestHost": "15.3.0-preview-20170628-02" + "Microsoft.CodeCoverage": "17.9.0" } }, "Newtonsoft.Json": { @@ -62,7 +62,7 @@ }, "xunit": { "type": "Direct", - "requested": "[2.*, )", + "requested": "[2.7.0, )", "resolved": "2.7.0", "contentHash": "KcCI5zxh8zbUfQTeErc4oT7YokViVND2V0p4vDJ2VD4lhF9V5qCYMMDNixme7FdwYy3SwPHF+2xC2Dq4Z9GSlA==", "dependencies": { @@ -82,7 +82,7 @@ }, "xunit.runner.visualstudio": { "type": "Direct", - "requested": "[2.*, )", + "requested": "[2.5.7, )", "resolved": "2.5.7", "contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w==", "dependencies": { @@ -117,6 +117,11 @@ "System.Threading.Tasks.Extensions": "4.5.4" } }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.9.0", + "contentHash": "RGD37ZSrratfScYXm7M0HjvxMxZyWZL4jm+XgMZbkIY1UPgjUpbNA/t+WTGj/rC/0Hm9A3IrH3ywbKZkOCnoZA==" + }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "17.8.0", @@ -126,11 +131,6 @@ "System.Reflection.Metadata": "1.6.0" } }, - "Microsoft.TestPlatform.TestHost": { - "type": "Transitive", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "TxJWVRa+vctEzfhA9GQWGpzspi26a5A0krD5KcJYrZc8ymLMsgR1ChKeNuekUkEXn+5tTKyy4Qvg+nitb/uK/Q==" - }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "6.5.0", @@ -252,9 +252,9 @@ "net6.0": { "coverlet.collector": { "type": "Direct", - "requested": "[3.1.0, )", - "resolved": "3.1.0", - "contentHash": "YzYqSRrjoP5lULBhTDcTOjuM4IDPPi6PhFsl4w8EI4WdZhE6llt7E38Tg4CHyrS+QKwyu1+9OwkdDgluOdoBTw==" + "requested": "[6.0.1, )", + "resolved": "6.0.1", + "contentHash": "q/8r0muHlF7TbhMUTBAk43KteZYG2ECV96pbzkToQwgjp3BvbS766svUcO6va+cohWOkigeBWmHs0Vu5vQnESA==" }, "Imageflow.NativeTool.win-x86": { "type": "Direct", @@ -276,11 +276,12 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[15.3.0-preview-20170628-02, )", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "g5Ek236LaKEzPI13kK4c2wA8eaj44PfBucXRp/7FRBXpSbRqv/1McJcGOjDbs89d2dyeGLB6jGqSpQs3Y9Wh7w==", + "requested": "[17.9.0, )", + "resolved": "17.9.0", + "contentHash": "7GUNAUbJYn644jzwLm5BD3a2p9C1dmP8Hr6fDPDxgItQk9hBs1Svdxzz07KQ/UphMSmgza9AbijBJGmw5D658A==", "dependencies": { - "Microsoft.TestPlatform.TestHost": "15.3.0-preview-20170628-02" + "Microsoft.CodeCoverage": "17.9.0", + "Microsoft.TestPlatform.TestHost": "17.9.0" } }, "Newtonsoft.Json": { @@ -301,7 +302,7 @@ }, "xunit": { "type": "Direct", - "requested": "[2.*, )", + "requested": "[2.7.0, )", "resolved": "2.7.0", "contentHash": "KcCI5zxh8zbUfQTeErc4oT7YokViVND2V0p4vDJ2VD4lhF9V5qCYMMDNixme7FdwYy3SwPHF+2xC2Dq4Z9GSlA==", "dependencies": { @@ -321,7 +322,7 @@ }, "xunit.runner.visualstudio": { "type": "Direct", - "requested": "[2.*, )", + "requested": "[2.5.7, )", "resolved": "2.5.7", "contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w==" }, @@ -345,4101 +346,412 @@ "resolved": "2.0.0-preview8", "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" }, - "Microsoft.DotNet.PlatformAbstractions": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "rF92Gp5L2asYrFNf0cKNBxzzGLh1krHuj6TRDk9wdjN2qdvJLaNYOn1s9oYkMlptYX436KiEFqxhLB+I5veXvQ==", - "dependencies": { - "System.AppContext": "4.1.0", - "System.Collections": "4.0.11", - "System.IO": "4.1.0", - "System.IO.FileSystem": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.0.0" - } - }, - "Microsoft.Extensions.DependencyModel": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "Z3o19EnheuegmvgpCzwoSlnCWxYA6qIUhvKJ7ifKHHvU7U+oYR/gliLiL3LVYOOeGMEEzkpJ5W67sOcXizGtlw==", - "dependencies": { - "Microsoft.DotNet.PlatformAbstractions": "1.0.3", - "Newtonsoft.Json": "9.0.1", - "System.Diagnostics.Debug": "4.0.11", - "System.Dynamic.Runtime": "4.0.11", - "System.Linq": "4.1.0" - } - }, - "Microsoft.NETCore.Platforms": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" - }, - "Microsoft.NETCore.Targets": { + "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + "resolved": "17.9.0", + "contentHash": "RGD37ZSrratfScYXm7M0HjvxMxZyWZL4jm+XgMZbkIY1UPgjUpbNA/t+WTGj/rC/0Hm9A3IrH3ywbKZkOCnoZA==" }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "sHQinh69ZH2XjE6EBxolUiYKAGtdxd/XnVFVRiNNwaCury4dM26TxG1kzMRmrw4fXha1Z+y6usYUD73aZjSNYA==", + "resolved": "17.9.0", + "contentHash": "1ilw/8vgmjLyKU+2SKXKXaOqpYFJCQfGqGz+x0cosl981VzjrY74Sv6qAJv+neZMZ9ZMxF3ArN6kotaQ4uvEBw==", "dependencies": { - "NETStandard.Library": "1.6.1", - "System.ComponentModel.EventBasedAsync": "4.3.0", - "System.ComponentModel.TypeConverter": "4.3.0", - "System.Diagnostics.Process": "4.3.0", - "System.Diagnostics.TextWriterTraceListener": "4.3.0", - "System.Diagnostics.TraceSource": "4.3.0", - "System.Reflection.Metadata": "1.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Runtime.Loader": "4.3.0", - "System.Runtime.Serialization.Json": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Threading.Thread": "4.3.0", - "System.Xml.XPath.XmlDocument": "4.0.1" + "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "TxJWVRa+vctEzfhA9GQWGpzspi26a5A0krD5KcJYrZc8ymLMsgR1ChKeNuekUkEXn+5tTKyy4Qvg+nitb/uK/Q==", + "resolved": "17.9.0", + "contentHash": "Spmg7Wx49Ya3SxBjyeAR+nQpjMTKZwTwpZ7KyeOTIqI/WHNPnBU4HUvl5kuHPQAwGWqMy4FGZja1HvEwvoaDiA==", "dependencies": { - "Microsoft.Extensions.DependencyModel": "1.0.3", - "Microsoft.TestPlatform.ObjectModel": "15.3.0-preview-20170628-02", - "Newtonsoft.Json": "9.0.1" + "Microsoft.TestPlatform.ObjectModel": "17.9.0", + "Newtonsoft.Json": "13.0.1" } }, - "Microsoft.Win32.Primitives": { + "System.Reflection.Metadata": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" }, - "Microsoft.Win32.Registry": { + "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Lw1/VwLH1yxz6SfFEjVRCN0pnflLEsWgnV4qsdJ512/HhTwnKXUG+zDQ4yTO3K/EJQemGoNaBHX5InISNKTzUQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, - "NETStandard.Library": { + "System.Text.Encodings.Web": { "type": "Transitive", - "resolved": "1.6.1", - "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", + "resolved": "6.0.0", + "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.AppContext": "4.3.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Console": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tools": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.Compression": "4.3.0", - "System.IO.Compression.ZipFile": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Timer": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XDocument": "4.3.0" + "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "xunit.abstractions": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "xunit.analyzers": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" + "resolved": "1.11.0", + "contentHash": "SCv+Ihxv+fCqotGeM8sVwLhw8nzAJ2aFRN5lcoKn9QtGdbVJ79JqDc+4u8/Ddnp2udxtmv+xYFWkHNlb/sk01w==" }, - "runtime.native.System": { + "xunit.assert": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } + "resolved": "2.7.0", + "contentHash": "CCTs3bUhmIS4tDwK6Cn/IiabG3RhYzdf65eIkO7u9/grKoN9MrN780LzVED3E8v+vwmmj7b5TW3/GFuZHPAzWA==" }, - "runtime.native.System.IO.Compression": { + "xunit.core": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", + "resolved": "2.7.0", + "contentHash": "98tzqYAbtc/p/2Ba455XTNbD12Qoo8kPehjC4oDT46CAsLli5JOCU9hFF2MV3HHWMw/Y3yFUV2Vcukplbs6kuA==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" + "xunit.extensibility.core": "[2.7.0]", + "xunit.extensibility.execution": "[2.7.0]" } }, - "runtime.native.System.Net.Http": { + "xunit.extensibility.core": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", + "resolved": "2.7.0", + "contentHash": "JLnx4PI0vn1Xr1Ust6ydrp2t/ktm2dyGPAVoDJV5gQuvBMSbd2K7WGzODa2ttiz030CeQ8nbsXl05+cvf7QNyA==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" + "xunit.abstractions": "2.0.3" } }, - "runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", + "imageflow.allplatforms": { + "type": "Project", "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" + "Imageflow.NativeRuntime.osx-x86_64": "[2.0.0-preview8, )", + "Imageflow.NativeRuntime.ubuntu-x86_64": "[2.0.0-preview8, )", + "Imageflow.NativeRuntime.win-x86": "[2.0.0-preview8, )", + "Imageflow.NativeRuntime.win-x86_64": "[2.0.0-preview8, )", + "Imageflow.Net": "[0.1.0--notset, )" } }, - "runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", + "imageflow.net": { + "type": "Project", "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", + "System.Text.Json": "[6.*, )" } + } + }, + "net7.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[6.0.1, )", + "resolved": "6.0.1", + "contentHash": "q/8r0muHlF7TbhMUTBAk43KteZYG2ECV96pbzkToQwgjp3BvbS766svUcO6va+cohWOkigeBWmHs0Vu5vQnESA==" }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" + "Imageflow.NativeTool.win-x86": { + "type": "Direct", + "requested": "[2.0.0-preview8, )", + "resolved": "2.0.0-preview8", + "contentHash": "h/N4F9l/pRZsTMGIWPt49gbLVfzeA++/8M3a4rVcxE/ZPxpHwlgj5+wedcgF1q7+MryZm4xqVx9QGXMHwV/8Fg==" }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" + "Imageflow.NativeTool.win-x86_64": { + "type": "Direct", + "requested": "[2.0.0-preview8, )", + "resolved": "2.0.0-preview8", + "contentHash": "d6S4l+zlPoR7CnrYAOJ1onOhi+TiASAZdrBaGKQOQ1dBpBG9SSWu/9a1F/tZmtHL9/wKMH6FqNrrLrxFmqjayg==" }, - "System.AppContext": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", - "dependencies": { - "System.Runtime": "4.3.0" - } + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Direct", + "requested": "[3.0.0, )", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.9.0, )", + "resolved": "17.9.0", + "contentHash": "7GUNAUbJYn644jzwLm5BD3a2p9C1dmP8Hr6fDPDxgItQk9hBs1Svdxzz07KQ/UphMSmgza9AbijBJGmw5D658A==", "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" + "Microsoft.CodeCoverage": "17.9.0", + "Microsoft.TestPlatform.TestHost": "17.9.0" } }, - "System.Collections": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, 14.0.0)", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "System.Collections.Concurrent": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", + "System.Text.Json": { + "type": "Direct", + "requested": "[6.*, )", + "resolved": "6.0.9", + "contentHash": "2j16oUgtIzl7Xtk7demG0i/v5aU/ZvULcAnJvPb63U3ZhXJ494UYcxuEj5Fs49i3XDrk5kU/8I+6l9zRCw3cJw==", "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "6.0.0" } }, - "System.Collections.Immutable": { - "type": "Transitive", - "resolved": "1.2.0", - "contentHash": "Cma8cBW6di16ZLibL8LYQ+cLjGzoKxpOTu/faZfDcx94ZjAGq6Nv5RO7+T1YZXqEXTZP9rt1wLVEONVpURtUqw==", + "xunit": { + "type": "Direct", + "requested": "[2.7.0, )", + "resolved": "2.7.0", + "contentHash": "KcCI5zxh8zbUfQTeErc4oT7YokViVND2V0p4vDJ2VD4lhF9V5qCYMMDNixme7FdwYy3SwPHF+2xC2Dq4Z9GSlA==", "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" + "xunit.analyzers": "1.11.0", + "xunit.assert": "2.7.0", + "xunit.core": "[2.7.0]" } }, - "System.Collections.NonGeneric": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", + "xunit.extensibility.execution": { + "type": "Direct", + "requested": "[2.*, )", + "resolved": "2.7.0", + "contentHash": "bjY+crT1jOyxKagFjCMdEVzoenO2v66ru8+CK/0UaXvyG4U9Q3UTieJkbQXbi7/1yZIK1sGh01l5/jh2CwLJtQ==", "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" + "xunit.extensibility.core": "[2.7.0]" } }, - "System.Collections.Specialized": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", - "dependencies": { - "System.Collections.NonGeneric": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[2.5.7, )", + "resolved": "2.5.7", + "contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w==" }, - "System.ComponentModel": { + "Imageflow.NativeRuntime.osx-x86_64": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", - "dependencies": { - "System.Runtime": "4.3.0" - } + "resolved": "2.0.0-preview8", + "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" }, - "System.ComponentModel.EventBasedAsync": { + "Imageflow.NativeRuntime.ubuntu-x86_64": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "fCFl8f0XdwA/BuoNrVBB5D0Y48/hv2J+w4xSDdXQitXZsR6UCSOrDVE7TCUraY802ENwcHUnUCv4En8CupDU1g==", - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } + "resolved": "2.0.0-preview8", + "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" }, - "System.ComponentModel.Primitives": { + "Imageflow.NativeRuntime.win-x86": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", - "dependencies": { - "System.ComponentModel": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - } + "resolved": "2.0.0-preview8", + "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" }, - "System.ComponentModel.TypeConverter": { + "Imageflow.NativeRuntime.win-x86_64": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Collections.NonGeneric": "4.3.0", - "System.Collections.Specialized": "4.3.0", - "System.ComponentModel": "4.3.0", - "System.ComponentModel.Primitives": "4.3.0", - "System.Globalization": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } + "resolved": "2.0.0-preview8", + "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" }, - "System.Console": { + "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } + "resolved": "17.9.0", + "contentHash": "RGD37ZSrratfScYXm7M0HjvxMxZyWZL4jm+XgMZbkIY1UPgjUpbNA/t+WTGj/rC/0Hm9A3IrH3ywbKZkOCnoZA==" }, - "System.Diagnostics.Debug": { + "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", + "resolved": "17.9.0", + "contentHash": "1ilw/8vgmjLyKU+2SKXKXaOqpYFJCQfGqGz+x0cosl981VzjrY74Sv6qAJv+neZMZ9ZMxF3ArN6kotaQ4uvEBw==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" + "System.Reflection.Metadata": "1.6.0" } }, - "System.Diagnostics.DiagnosticSource": { + "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", + "resolved": "17.9.0", + "contentHash": "Spmg7Wx49Ya3SxBjyeAR+nQpjMTKZwTwpZ7KyeOTIqI/WHNPnBU4HUvl5kuHPQAwGWqMy4FGZja1HvEwvoaDiA==", "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" + "Microsoft.TestPlatform.ObjectModel": "17.9.0", + "Newtonsoft.Json": "13.0.1" } }, - "System.Diagnostics.Process": { + "System.Reflection.Metadata": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "J0wOX07+QASQblsfxmIMFc9Iq7KTXYL3zs2G/Xc704Ylv3NpuVdo6gij6V3PGiptTxqsK0K7CdXenRvKUnkA2g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "Microsoft.Win32.Registry": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Thread": "4.3.0", - "System.Threading.ThreadPool": "4.3.0", - "runtime.native.System": "4.3.0" - } + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" }, - "System.Diagnostics.TextWriterTraceListener": { + "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "F11kHWeiwYjFWto+kr8tt9ULMH0k8MsT1XmdCGPTLYHhWgN+2g7JsIZiXDrxlFGccSNkbjfwQy4xIS38gzUiZA==", - "dependencies": { - "System.Diagnostics.TraceSource": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, - "System.Diagnostics.Tools": { + "System.Text.Encodings.Web": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", + "resolved": "6.0.0", + "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" + "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, - "System.Diagnostics.TraceSource": { + "xunit.abstractions": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VnYp1NxGx8Ww731y2LJ1vpfb/DKVNKEZ8Jsh5SgQTZREL/YpWRArgh9pI8CDLmgHspZmLL697CaLvH85qQpRiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0" - } + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" }, - "System.Diagnostics.Tracing": { + "xunit.analyzers": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } + "resolved": "1.11.0", + "contentHash": "SCv+Ihxv+fCqotGeM8sVwLhw8nzAJ2aFRN5lcoKn9QtGdbVJ79JqDc+4u8/Ddnp2udxtmv+xYFWkHNlb/sk01w==" }, - "System.Dynamic.Runtime": { + "xunit.assert": { "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Linq.Expressions": "4.1.0", - "System.ObjectModel": "4.0.12", - "System.Reflection": "4.1.0", - "System.Reflection.Emit": "4.0.1", - "System.Reflection.Emit.ILGeneration": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } + "resolved": "2.7.0", + "contentHash": "CCTs3bUhmIS4tDwK6Cn/IiabG3RhYzdf65eIkO7u9/grKoN9MrN780LzVED3E8v+vwmmj7b5TW3/GFuZHPAzWA==" }, - "System.Globalization": { + "xunit.core": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "resolved": "2.7.0", + "contentHash": "98tzqYAbtc/p/2Ba455XTNbD12Qoo8kPehjC4oDT46CAsLli5JOCU9hFF2MV3HHWMw/Y3yFUV2Vcukplbs6kuA==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" + "xunit.extensibility.core": "[2.7.0]", + "xunit.extensibility.execution": "[2.7.0]" } }, - "System.Globalization.Calendars": { + "xunit.extensibility.core": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", + "resolved": "2.7.0", + "contentHash": "JLnx4PI0vn1Xr1Ust6ydrp2t/ktm2dyGPAVoDJV5gQuvBMSbd2K7WGzODa2ttiz030CeQ8nbsXl05+cvf7QNyA==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" + "xunit.abstractions": "2.0.3" } }, - "System.Globalization.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", + "imageflow.allplatforms": { + "type": "Project", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" + "Imageflow.NativeRuntime.osx-x86_64": "[2.0.0-preview8, )", + "Imageflow.NativeRuntime.ubuntu-x86_64": "[2.0.0-preview8, )", + "Imageflow.NativeRuntime.win-x86": "[2.0.0-preview8, )", + "Imageflow.NativeRuntime.win-x86_64": "[2.0.0-preview8, )", + "Imageflow.Net": "[0.1.0--notset, )" } }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "imageflow.net": { + "type": "Project", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" + "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", + "System.Text.Json": "[6.*, )" } + } + }, + "net8.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[6.0.1, )", + "resolved": "6.0.1", + "contentHash": "q/8r0muHlF7TbhMUTBAk43KteZYG2ECV96pbzkToQwgjp3BvbS766svUcO6va+cohWOkigeBWmHs0Vu5vQnESA==" }, - "System.IO.Compression": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Buffers": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.IO.Compression": "4.3.0" - } + "Imageflow.NativeTool.win-x86": { + "type": "Direct", + "requested": "[2.0.0-preview8, )", + "resolved": "2.0.0-preview8", + "contentHash": "h/N4F9l/pRZsTMGIWPt49gbLVfzeA++/8M3a4rVcxE/ZPxpHwlgj5+wedcgF1q7+MryZm4xqVx9QGXMHwV/8Fg==" }, - "System.IO.Compression.ZipFile": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", - "dependencies": { - "System.Buffers": "4.3.0", - "System.IO": "4.3.0", - "System.IO.Compression": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0" - } + "Imageflow.NativeTool.win-x86_64": { + "type": "Direct", + "requested": "[2.0.0-preview8, )", + "resolved": "2.0.0-preview8", + "contentHash": "d6S4l+zlPoR7CnrYAOJ1onOhi+TiASAZdrBaGKQOQ1dBpBG9SSWu/9a1F/tZmtHL9/wKMH6FqNrrLrxFmqjayg==" }, - "System.IO.FileSystem": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } + "Microsoft.IO.RecyclableMemoryStream": { + "type": "Direct", + "requested": "[3.0.0, )", + "resolved": "3.0.0", + "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" }, - "System.IO.FileSystem.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.9.0, )", + "resolved": "17.9.0", + "contentHash": "7GUNAUbJYn644jzwLm5BD3a2p9C1dmP8Hr6fDPDxgItQk9hBs1Svdxzz07KQ/UphMSmgza9AbijBJGmw5D658A==", "dependencies": { - "System.Runtime": "4.3.0" + "Microsoft.CodeCoverage": "17.9.0", + "Microsoft.TestPlatform.TestHost": "17.9.0" } }, - "System.Linq": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Linq.Expressions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Net.Http": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Net.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Net.Sockets": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.ObjectModel": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Private.DataContractSerialization": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "yDaJ2x3mMmjdZEDB4IbezSnCsnjQ4BxinKhRAaP6kEgL6Bb6jANWphs5SzyD8imqeC/3FxgsuXT6ykkiH1uUmA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XDocument": "4.3.0", - "System.Xml.XmlDocument": "4.3.0", - "System.Xml.XmlSerializer": "4.3.0" - } - }, - "System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit.ILGeneration": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit.Lightweight": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Metadata": { - "type": "Transitive", - "resolved": "1.3.0", - "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Collections.Immutable": "1.2.0", - "System.Diagnostics.Debug": "4.0.11", - "System.IO": "4.1.0", - "System.Linq": "4.1.0", - "System.Reflection": "4.1.0", - "System.Reflection.Extensions": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0", - "System.Text.Encoding": "4.0.11", - "System.Text.Encoding.Extensions": "4.0.11", - "System.Threading": "4.0.11" - } - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Resources.ResourceManager": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.CompilerServices.Unsafe": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" - }, - "System.Runtime.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.InteropServices.RuntimeInformation": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Runtime.Loader": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DHMaRn8D8YCK2GG2pw+UzNxn/OHVfaWx7OTLBD/hPegHZZgcZh3H6seWegrC4BYwsfuGrywIuT+MQs+rPqRLTQ==", - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Numerics": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Runtime.Serialization.Json": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "CpVfOH0M/uZ5PH+M9+Gu56K0j9lJw3M+PKRegTkcrY/stOIvRUeonggxNrfBYLA5WOHL2j15KNJuTuld3x4o9w==", - "dependencies": { - "System.IO": "4.3.0", - "System.Private.DataContractSerialization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Serialization.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Security.Cryptography.Algorithms": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Security.Cryptography.X509Certificates": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, - "System.Text.RegularExpressions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Threading": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Threading.Thread": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OHmbT+Zz065NKII/ZHcH9XO1dEuLGI1L2k7uYss+9C1jLxTC9kTZZuzUOyXHayRk+dft9CiDf3I/QZ0t8JKyBQ==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Threading.ThreadPool": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", - "dependencies": { - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Threading.Timer": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Xml.ReaderWriter": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Tasks.Extensions": "4.3.0" - } - }, - "System.Xml.XDocument": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tools": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - } - }, - "System.Xml.XmlDocument": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - } - }, - "System.Xml.XmlSerializer": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "MYoTCP7EZ98RrANESW05J5ZwskKDoN0AuZ06ZflnowE50LTpbR5yRg3tHckTVm5j/m47stuGgCrCHWePyHS70Q==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XmlDocument": "4.3.0" - } - }, - "System.Xml.XPath": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11", - "System.Xml.ReaderWriter": "4.0.11" - } - }, - "System.Xml.XPath.XmlDocument": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "Zm2BdeanuncYs3NhCj4c9e1x3EXFzFBVv2wPEc/Dj4ZbI9R8ecLSR5frAsx4zJCPBtKQreQ7Q/KxJEohJZbfzA==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11", - "System.Xml.ReaderWriter": "4.0.11", - "System.Xml.XPath": "4.0.1", - "System.Xml.XmlDocument": "4.0.1" - } - }, - "xunit.abstractions": { - "type": "Transitive", - "resolved": "2.0.3", - "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" - }, - "xunit.analyzers": { - "type": "Transitive", - "resolved": "1.11.0", - "contentHash": "SCv+Ihxv+fCqotGeM8sVwLhw8nzAJ2aFRN5lcoKn9QtGdbVJ79JqDc+4u8/Ddnp2udxtmv+xYFWkHNlb/sk01w==" - }, - "xunit.assert": { - "type": "Transitive", - "resolved": "2.7.0", - "contentHash": "CCTs3bUhmIS4tDwK6Cn/IiabG3RhYzdf65eIkO7u9/grKoN9MrN780LzVED3E8v+vwmmj7b5TW3/GFuZHPAzWA==" - }, - "xunit.core": { - "type": "Transitive", - "resolved": "2.7.0", - "contentHash": "98tzqYAbtc/p/2Ba455XTNbD12Qoo8kPehjC4oDT46CAsLli5JOCU9hFF2MV3HHWMw/Y3yFUV2Vcukplbs6kuA==", - "dependencies": { - "xunit.extensibility.core": "[2.7.0]", - "xunit.extensibility.execution": "[2.7.0]" - } - }, - "xunit.extensibility.core": { - "type": "Transitive", - "resolved": "2.7.0", - "contentHash": "JLnx4PI0vn1Xr1Ust6ydrp2t/ktm2dyGPAVoDJV5gQuvBMSbd2K7WGzODa2ttiz030CeQ8nbsXl05+cvf7QNyA==", - "dependencies": { - "xunit.abstractions": "2.0.3" - } - }, - "imageflow.allplatforms": { - "type": "Project", - "dependencies": { - "Imageflow.NativeRuntime.osx-x86_64": "[2.0.0-preview8, )", - "Imageflow.NativeRuntime.ubuntu-x86_64": "[2.0.0-preview8, )", - "Imageflow.NativeRuntime.win-x86": "[2.0.0-preview8, )", - "Imageflow.NativeRuntime.win-x86_64": "[2.0.0-preview8, )", - "Imageflow.Net": "[0.1.0--notset, )" - } - }, - "imageflow.net": { - "type": "Project", - "dependencies": { - "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", - "System.Text.Json": "[6.*, )" - } - } - }, - "net7.0": { - "coverlet.collector": { - "type": "Direct", - "requested": "[3.1.0, )", - "resolved": "3.1.0", - "contentHash": "YzYqSRrjoP5lULBhTDcTOjuM4IDPPi6PhFsl4w8EI4WdZhE6llt7E38Tg4CHyrS+QKwyu1+9OwkdDgluOdoBTw==" - }, - "Imageflow.NativeTool.win-x86": { - "type": "Direct", - "requested": "[2.0.0-preview8, )", - "resolved": "2.0.0-preview8", - "contentHash": "h/N4F9l/pRZsTMGIWPt49gbLVfzeA++/8M3a4rVcxE/ZPxpHwlgj5+wedcgF1q7+MryZm4xqVx9QGXMHwV/8Fg==" - }, - "Imageflow.NativeTool.win-x86_64": { - "type": "Direct", - "requested": "[2.0.0-preview8, )", - "resolved": "2.0.0-preview8", - "contentHash": "d6S4l+zlPoR7CnrYAOJ1onOhi+TiASAZdrBaGKQOQ1dBpBG9SSWu/9a1F/tZmtHL9/wKMH6FqNrrLrxFmqjayg==" - }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Direct", - "requested": "[3.0.0, )", - "resolved": "3.0.0", - "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" - }, - "Microsoft.NET.Test.Sdk": { - "type": "Direct", - "requested": "[15.3.0-preview-20170628-02, )", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "g5Ek236LaKEzPI13kK4c2wA8eaj44PfBucXRp/7FRBXpSbRqv/1McJcGOjDbs89d2dyeGLB6jGqSpQs3Y9Wh7w==", - "dependencies": { - "Microsoft.TestPlatform.TestHost": "15.3.0-preview-20170628-02" - } - }, - "Newtonsoft.Json": { - "type": "Direct", - "requested": "[13.0.3, 14.0.0)", - "resolved": "13.0.3", - "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" - }, - "System.Text.Json": { - "type": "Direct", - "requested": "[6.*, )", - "resolved": "6.0.9", - "contentHash": "2j16oUgtIzl7Xtk7demG0i/v5aU/ZvULcAnJvPb63U3ZhXJ494UYcxuEj5Fs49i3XDrk5kU/8I+6l9zRCw3cJw==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Encodings.Web": "6.0.0" - } - }, - "xunit": { - "type": "Direct", - "requested": "[2.*, )", - "resolved": "2.7.0", - "contentHash": "KcCI5zxh8zbUfQTeErc4oT7YokViVND2V0p4vDJ2VD4lhF9V5qCYMMDNixme7FdwYy3SwPHF+2xC2Dq4Z9GSlA==", - "dependencies": { - "xunit.analyzers": "1.11.0", - "xunit.assert": "2.7.0", - "xunit.core": "[2.7.0]" - } - }, - "xunit.extensibility.execution": { - "type": "Direct", - "requested": "[2.*, )", - "resolved": "2.7.0", - "contentHash": "bjY+crT1jOyxKagFjCMdEVzoenO2v66ru8+CK/0UaXvyG4U9Q3UTieJkbQXbi7/1yZIK1sGh01l5/jh2CwLJtQ==", - "dependencies": { - "xunit.extensibility.core": "[2.7.0]" - } - }, - "xunit.runner.visualstudio": { - "type": "Direct", - "requested": "[2.*, )", - "resolved": "2.5.7", - "contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w==" - }, - "Imageflow.NativeRuntime.osx-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" - }, - "Imageflow.NativeRuntime.ubuntu-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" - }, - "Imageflow.NativeRuntime.win-x86": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" - }, - "Imageflow.NativeRuntime.win-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" - }, - "Microsoft.DotNet.PlatformAbstractions": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "rF92Gp5L2asYrFNf0cKNBxzzGLh1krHuj6TRDk9wdjN2qdvJLaNYOn1s9oYkMlptYX436KiEFqxhLB+I5veXvQ==", - "dependencies": { - "System.AppContext": "4.1.0", - "System.Collections": "4.0.11", - "System.IO": "4.1.0", - "System.IO.FileSystem": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.0.0" - } - }, - "Microsoft.Extensions.DependencyModel": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "Z3o19EnheuegmvgpCzwoSlnCWxYA6qIUhvKJ7ifKHHvU7U+oYR/gliLiL3LVYOOeGMEEzkpJ5W67sOcXizGtlw==", - "dependencies": { - "Microsoft.DotNet.PlatformAbstractions": "1.0.3", - "Newtonsoft.Json": "9.0.1", - "System.Diagnostics.Debug": "4.0.11", - "System.Dynamic.Runtime": "4.0.11", - "System.Linq": "4.1.0" - } - }, - "Microsoft.NETCore.Platforms": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" - }, - "Microsoft.NETCore.Targets": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" - }, - "Microsoft.TestPlatform.ObjectModel": { - "type": "Transitive", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "sHQinh69ZH2XjE6EBxolUiYKAGtdxd/XnVFVRiNNwaCury4dM26TxG1kzMRmrw4fXha1Z+y6usYUD73aZjSNYA==", - "dependencies": { - "NETStandard.Library": "1.6.1", - "System.ComponentModel.EventBasedAsync": "4.3.0", - "System.ComponentModel.TypeConverter": "4.3.0", - "System.Diagnostics.Process": "4.3.0", - "System.Diagnostics.TextWriterTraceListener": "4.3.0", - "System.Diagnostics.TraceSource": "4.3.0", - "System.Reflection.Metadata": "1.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Runtime.Loader": "4.3.0", - "System.Runtime.Serialization.Json": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Threading.Thread": "4.3.0", - "System.Xml.XPath.XmlDocument": "4.0.1" - } - }, - "Microsoft.TestPlatform.TestHost": { - "type": "Transitive", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "TxJWVRa+vctEzfhA9GQWGpzspi26a5A0krD5KcJYrZc8ymLMsgR1ChKeNuekUkEXn+5tTKyy4Qvg+nitb/uK/Q==", - "dependencies": { - "Microsoft.Extensions.DependencyModel": "1.0.3", - "Microsoft.TestPlatform.ObjectModel": "15.3.0-preview-20170628-02", - "Newtonsoft.Json": "9.0.1" - } - }, - "Microsoft.Win32.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "Microsoft.Win32.Registry": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Lw1/VwLH1yxz6SfFEjVRCN0pnflLEsWgnV4qsdJ512/HhTwnKXUG+zDQ4yTO3K/EJQemGoNaBHX5InISNKTzUQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "NETStandard.Library": { - "type": "Transitive", - "resolved": "1.6.1", - "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.AppContext": "4.3.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Console": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tools": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.Compression": "4.3.0", - "System.IO.Compression.ZipFile": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Timer": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XDocument": "4.3.0" - } - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" - }, - "runtime.native.System": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.IO.Compression": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" - }, - "System.AppContext": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Collections": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Collections.Concurrent": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Collections.Immutable": { - "type": "Transitive", - "resolved": "1.2.0", - "contentHash": "Cma8cBW6di16ZLibL8LYQ+cLjGzoKxpOTu/faZfDcx94ZjAGq6Nv5RO7+T1YZXqEXTZP9rt1wLVEONVpURtUqw==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Collections.NonGeneric": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Collections.Specialized": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", - "dependencies": { - "System.Collections.NonGeneric": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.ComponentModel": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.ComponentModel.EventBasedAsync": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "fCFl8f0XdwA/BuoNrVBB5D0Y48/hv2J+w4xSDdXQitXZsR6UCSOrDVE7TCUraY802ENwcHUnUCv4En8CupDU1g==", - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.ComponentModel.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", - "dependencies": { - "System.ComponentModel": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.ComponentModel.TypeConverter": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Collections.NonGeneric": "4.3.0", - "System.Collections.Specialized": "4.3.0", - "System.ComponentModel": "4.3.0", - "System.ComponentModel.Primitives": "4.3.0", - "System.Globalization": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Console": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Diagnostics.Debug": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Diagnostics.Process": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "J0wOX07+QASQblsfxmIMFc9Iq7KTXYL3zs2G/Xc704Ylv3NpuVdo6gij6V3PGiptTxqsK0K7CdXenRvKUnkA2g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "Microsoft.Win32.Registry": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Thread": "4.3.0", - "System.Threading.ThreadPool": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Diagnostics.TextWriterTraceListener": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "F11kHWeiwYjFWto+kr8tt9ULMH0k8MsT1XmdCGPTLYHhWgN+2g7JsIZiXDrxlFGccSNkbjfwQy4xIS38gzUiZA==", - "dependencies": { - "System.Diagnostics.TraceSource": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Diagnostics.Tools": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.TraceSource": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VnYp1NxGx8Ww731y2LJ1vpfb/DKVNKEZ8Jsh5SgQTZREL/YpWRArgh9pI8CDLmgHspZmLL697CaLvH85qQpRiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Diagnostics.Tracing": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Dynamic.Runtime": { - "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Linq.Expressions": "4.1.0", - "System.ObjectModel": "4.0.12", - "System.Reflection": "4.1.0", - "System.Reflection.Emit": "4.0.1", - "System.Reflection.Emit.ILGeneration": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Globalization": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.Compression": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Buffers": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.IO.Compression": "4.3.0" - } - }, - "System.IO.Compression.ZipFile": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", - "dependencies": { - "System.Buffers": "4.3.0", - "System.IO": "4.3.0", - "System.IO.Compression": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.IO.FileSystem": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Linq": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Linq.Expressions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Net.Http": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Net.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Net.Sockets": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.ObjectModel": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Private.DataContractSerialization": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "yDaJ2x3mMmjdZEDB4IbezSnCsnjQ4BxinKhRAaP6kEgL6Bb6jANWphs5SzyD8imqeC/3FxgsuXT6ykkiH1uUmA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XDocument": "4.3.0", - "System.Xml.XmlDocument": "4.3.0", - "System.Xml.XmlSerializer": "4.3.0" - } - }, - "System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit.ILGeneration": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit.Lightweight": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Metadata": { - "type": "Transitive", - "resolved": "1.3.0", - "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Collections.Immutable": "1.2.0", - "System.Diagnostics.Debug": "4.0.11", - "System.IO": "4.1.0", - "System.Linq": "4.1.0", - "System.Reflection": "4.1.0", - "System.Reflection.Extensions": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0", - "System.Text.Encoding": "4.0.11", - "System.Text.Encoding.Extensions": "4.0.11", - "System.Threading": "4.0.11" - } - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Resources.ResourceManager": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.CompilerServices.Unsafe": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" - }, - "System.Runtime.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.InteropServices.RuntimeInformation": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Runtime.Loader": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DHMaRn8D8YCK2GG2pw+UzNxn/OHVfaWx7OTLBD/hPegHZZgcZh3H6seWegrC4BYwsfuGrywIuT+MQs+rPqRLTQ==", - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Numerics": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Runtime.Serialization.Json": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "CpVfOH0M/uZ5PH+M9+Gu56K0j9lJw3M+PKRegTkcrY/stOIvRUeonggxNrfBYLA5WOHL2j15KNJuTuld3x4o9w==", - "dependencies": { - "System.IO": "4.3.0", - "System.Private.DataContractSerialization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Serialization.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Security.Cryptography.Algorithms": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Security.Cryptography.X509Certificates": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, - "System.Text.RegularExpressions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Threading": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Threading.Thread": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OHmbT+Zz065NKII/ZHcH9XO1dEuLGI1L2k7uYss+9C1jLxTC9kTZZuzUOyXHayRk+dft9CiDf3I/QZ0t8JKyBQ==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Threading.ThreadPool": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", - "dependencies": { - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Threading.Timer": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Xml.ReaderWriter": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Tasks.Extensions": "4.3.0" - } - }, - "System.Xml.XDocument": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tools": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - } - }, - "System.Xml.XmlDocument": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - } - }, - "System.Xml.XmlSerializer": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "MYoTCP7EZ98RrANESW05J5ZwskKDoN0AuZ06ZflnowE50LTpbR5yRg3tHckTVm5j/m47stuGgCrCHWePyHS70Q==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XmlDocument": "4.3.0" - } - }, - "System.Xml.XPath": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11", - "System.Xml.ReaderWriter": "4.0.11" - } - }, - "System.Xml.XPath.XmlDocument": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "Zm2BdeanuncYs3NhCj4c9e1x3EXFzFBVv2wPEc/Dj4ZbI9R8ecLSR5frAsx4zJCPBtKQreQ7Q/KxJEohJZbfzA==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11", - "System.Xml.ReaderWriter": "4.0.11", - "System.Xml.XPath": "4.0.1", - "System.Xml.XmlDocument": "4.0.1" - } - }, - "xunit.abstractions": { - "type": "Transitive", - "resolved": "2.0.3", - "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" - }, - "xunit.analyzers": { - "type": "Transitive", - "resolved": "1.11.0", - "contentHash": "SCv+Ihxv+fCqotGeM8sVwLhw8nzAJ2aFRN5lcoKn9QtGdbVJ79JqDc+4u8/Ddnp2udxtmv+xYFWkHNlb/sk01w==" - }, - "xunit.assert": { - "type": "Transitive", - "resolved": "2.7.0", - "contentHash": "CCTs3bUhmIS4tDwK6Cn/IiabG3RhYzdf65eIkO7u9/grKoN9MrN780LzVED3E8v+vwmmj7b5TW3/GFuZHPAzWA==" - }, - "xunit.core": { - "type": "Transitive", - "resolved": "2.7.0", - "contentHash": "98tzqYAbtc/p/2Ba455XTNbD12Qoo8kPehjC4oDT46CAsLli5JOCU9hFF2MV3HHWMw/Y3yFUV2Vcukplbs6kuA==", - "dependencies": { - "xunit.extensibility.core": "[2.7.0]", - "xunit.extensibility.execution": "[2.7.0]" - } - }, - "xunit.extensibility.core": { - "type": "Transitive", - "resolved": "2.7.0", - "contentHash": "JLnx4PI0vn1Xr1Ust6ydrp2t/ktm2dyGPAVoDJV5gQuvBMSbd2K7WGzODa2ttiz030CeQ8nbsXl05+cvf7QNyA==", - "dependencies": { - "xunit.abstractions": "2.0.3" - } - }, - "imageflow.allplatforms": { - "type": "Project", - "dependencies": { - "Imageflow.NativeRuntime.osx-x86_64": "[2.0.0-preview8, )", - "Imageflow.NativeRuntime.ubuntu-x86_64": "[2.0.0-preview8, )", - "Imageflow.NativeRuntime.win-x86": "[2.0.0-preview8, )", - "Imageflow.NativeRuntime.win-x86_64": "[2.0.0-preview8, )", - "Imageflow.Net": "[0.1.0--notset, )" - } - }, - "imageflow.net": { - "type": "Project", - "dependencies": { - "Microsoft.IO.RecyclableMemoryStream": "[3.*, 4.0.0)", - "System.Text.Json": "[6.*, )" - } - } - }, - "net8.0": { - "coverlet.collector": { - "type": "Direct", - "requested": "[3.1.0, )", - "resolved": "3.1.0", - "contentHash": "YzYqSRrjoP5lULBhTDcTOjuM4IDPPi6PhFsl4w8EI4WdZhE6llt7E38Tg4CHyrS+QKwyu1+9OwkdDgluOdoBTw==" - }, - "Imageflow.NativeTool.win-x86": { - "type": "Direct", - "requested": "[2.0.0-preview8, )", - "resolved": "2.0.0-preview8", - "contentHash": "h/N4F9l/pRZsTMGIWPt49gbLVfzeA++/8M3a4rVcxE/ZPxpHwlgj5+wedcgF1q7+MryZm4xqVx9QGXMHwV/8Fg==" - }, - "Imageflow.NativeTool.win-x86_64": { - "type": "Direct", - "requested": "[2.0.0-preview8, )", - "resolved": "2.0.0-preview8", - "contentHash": "d6S4l+zlPoR7CnrYAOJ1onOhi+TiASAZdrBaGKQOQ1dBpBG9SSWu/9a1F/tZmtHL9/wKMH6FqNrrLrxFmqjayg==" - }, - "Microsoft.IO.RecyclableMemoryStream": { - "type": "Direct", - "requested": "[3.0.0, )", - "resolved": "3.0.0", - "contentHash": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==" - }, - "Microsoft.NET.Test.Sdk": { - "type": "Direct", - "requested": "[15.3.0-preview-20170628-02, )", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "g5Ek236LaKEzPI13kK4c2wA8eaj44PfBucXRp/7FRBXpSbRqv/1McJcGOjDbs89d2dyeGLB6jGqSpQs3Y9Wh7w==", - "dependencies": { - "Microsoft.TestPlatform.TestHost": "15.3.0-preview-20170628-02" - } - }, - "Newtonsoft.Json": { - "type": "Direct", - "requested": "[13.0.3, 14.0.0)", - "resolved": "13.0.3", - "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" - }, - "System.Text.Json": { - "type": "Direct", - "requested": "[6.*, )", - "resolved": "6.0.9", - "contentHash": "2j16oUgtIzl7Xtk7demG0i/v5aU/ZvULcAnJvPb63U3ZhXJ494UYcxuEj5Fs49i3XDrk5kU/8I+6l9zRCw3cJw==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Encodings.Web": "6.0.0" - } - }, - "xunit": { - "type": "Direct", - "requested": "[2.*, )", - "resolved": "2.7.0", - "contentHash": "KcCI5zxh8zbUfQTeErc4oT7YokViVND2V0p4vDJ2VD4lhF9V5qCYMMDNixme7FdwYy3SwPHF+2xC2Dq4Z9GSlA==", - "dependencies": { - "xunit.analyzers": "1.11.0", - "xunit.assert": "2.7.0", - "xunit.core": "[2.7.0]" - } - }, - "xunit.extensibility.execution": { - "type": "Direct", - "requested": "[2.*, )", - "resolved": "2.7.0", - "contentHash": "bjY+crT1jOyxKagFjCMdEVzoenO2v66ru8+CK/0UaXvyG4U9Q3UTieJkbQXbi7/1yZIK1sGh01l5/jh2CwLJtQ==", - "dependencies": { - "xunit.extensibility.core": "[2.7.0]" - } - }, - "xunit.runner.visualstudio": { - "type": "Direct", - "requested": "[2.*, )", - "resolved": "2.5.7", - "contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w==" - }, - "Imageflow.NativeRuntime.osx-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" - }, - "Imageflow.NativeRuntime.ubuntu-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" - }, - "Imageflow.NativeRuntime.win-x86": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" - }, - "Imageflow.NativeRuntime.win-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" - }, - "Microsoft.DotNet.PlatformAbstractions": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "rF92Gp5L2asYrFNf0cKNBxzzGLh1krHuj6TRDk9wdjN2qdvJLaNYOn1s9oYkMlptYX436KiEFqxhLB+I5veXvQ==", - "dependencies": { - "System.AppContext": "4.1.0", - "System.Collections": "4.0.11", - "System.IO": "4.1.0", - "System.IO.FileSystem": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.0.0" - } - }, - "Microsoft.Extensions.DependencyModel": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "Z3o19EnheuegmvgpCzwoSlnCWxYA6qIUhvKJ7ifKHHvU7U+oYR/gliLiL3LVYOOeGMEEzkpJ5W67sOcXizGtlw==", - "dependencies": { - "Microsoft.DotNet.PlatformAbstractions": "1.0.3", - "Newtonsoft.Json": "9.0.1", - "System.Diagnostics.Debug": "4.0.11", - "System.Dynamic.Runtime": "4.0.11", - "System.Linq": "4.1.0" - } - }, - "Microsoft.NETCore.Platforms": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" - }, - "Microsoft.NETCore.Targets": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" - }, - "Microsoft.TestPlatform.ObjectModel": { - "type": "Transitive", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "sHQinh69ZH2XjE6EBxolUiYKAGtdxd/XnVFVRiNNwaCury4dM26TxG1kzMRmrw4fXha1Z+y6usYUD73aZjSNYA==", - "dependencies": { - "NETStandard.Library": "1.6.1", - "System.ComponentModel.EventBasedAsync": "4.3.0", - "System.ComponentModel.TypeConverter": "4.3.0", - "System.Diagnostics.Process": "4.3.0", - "System.Diagnostics.TextWriterTraceListener": "4.3.0", - "System.Diagnostics.TraceSource": "4.3.0", - "System.Reflection.Metadata": "1.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Runtime.Loader": "4.3.0", - "System.Runtime.Serialization.Json": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Threading.Thread": "4.3.0", - "System.Xml.XPath.XmlDocument": "4.0.1" - } - }, - "Microsoft.TestPlatform.TestHost": { - "type": "Transitive", - "resolved": "15.3.0-preview-20170628-02", - "contentHash": "TxJWVRa+vctEzfhA9GQWGpzspi26a5A0krD5KcJYrZc8ymLMsgR1ChKeNuekUkEXn+5tTKyy4Qvg+nitb/uK/Q==", - "dependencies": { - "Microsoft.Extensions.DependencyModel": "1.0.3", - "Microsoft.TestPlatform.ObjectModel": "15.3.0-preview-20170628-02", - "Newtonsoft.Json": "9.0.1" - } - }, - "Microsoft.Win32.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "Microsoft.Win32.Registry": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Lw1/VwLH1yxz6SfFEjVRCN0pnflLEsWgnV4qsdJ512/HhTwnKXUG+zDQ4yTO3K/EJQemGoNaBHX5InISNKTzUQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "NETStandard.Library": { - "type": "Transitive", - "resolved": "1.6.1", - "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.AppContext": "4.3.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Console": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tools": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.Compression": "4.3.0", - "System.IO.Compression.ZipFile": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Timer": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XDocument": "4.3.0" - } - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" - }, - "runtime.native.System": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.IO.Compression": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" - }, - "System.AppContext": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Collections": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Collections.Concurrent": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Collections.Immutable": { - "type": "Transitive", - "resolved": "1.2.0", - "contentHash": "Cma8cBW6di16ZLibL8LYQ+cLjGzoKxpOTu/faZfDcx94ZjAGq6Nv5RO7+T1YZXqEXTZP9rt1wLVEONVpURtUqw==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Collections.NonGeneric": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Collections.Specialized": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", - "dependencies": { - "System.Collections.NonGeneric": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.ComponentModel": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.ComponentModel.EventBasedAsync": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "fCFl8f0XdwA/BuoNrVBB5D0Y48/hv2J+w4xSDdXQitXZsR6UCSOrDVE7TCUraY802ENwcHUnUCv4En8CupDU1g==", - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.ComponentModel.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", - "dependencies": { - "System.ComponentModel": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.ComponentModel.TypeConverter": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Collections.NonGeneric": "4.3.0", - "System.Collections.Specialized": "4.3.0", - "System.ComponentModel": "4.3.0", - "System.ComponentModel.Primitives": "4.3.0", - "System.Globalization": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Console": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Diagnostics.Debug": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Diagnostics.Process": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "J0wOX07+QASQblsfxmIMFc9Iq7KTXYL3zs2G/Xc704Ylv3NpuVdo6gij6V3PGiptTxqsK0K7CdXenRvKUnkA2g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "Microsoft.Win32.Registry": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Thread": "4.3.0", - "System.Threading.ThreadPool": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Diagnostics.TextWriterTraceListener": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "F11kHWeiwYjFWto+kr8tt9ULMH0k8MsT1XmdCGPTLYHhWgN+2g7JsIZiXDrxlFGccSNkbjfwQy4xIS38gzUiZA==", - "dependencies": { - "System.Diagnostics.TraceSource": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Diagnostics.Tools": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.TraceSource": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VnYp1NxGx8Ww731y2LJ1vpfb/DKVNKEZ8Jsh5SgQTZREL/YpWRArgh9pI8CDLmgHspZmLL697CaLvH85qQpRiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Diagnostics.Tracing": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Dynamic.Runtime": { - "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Linq.Expressions": "4.1.0", - "System.ObjectModel": "4.0.12", - "System.Reflection": "4.1.0", - "System.Reflection.Emit": "4.0.1", - "System.Reflection.Emit.ILGeneration": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "System.Globalization": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.Compression": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Buffers": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.IO.Compression": "4.3.0" - } - }, - "System.IO.Compression.ZipFile": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", - "dependencies": { - "System.Buffers": "4.3.0", - "System.IO": "4.3.0", - "System.IO.Compression": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.IO.FileSystem": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Linq": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, 14.0.0)", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "System.Linq.Expressions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", + "System.Text.Json": { + "type": "Direct", + "requested": "[6.*, )", + "resolved": "6.0.9", + "contentHash": "2j16oUgtIzl7Xtk7demG0i/v5aU/ZvULcAnJvPb63U3ZhXJ494UYcxuEj5Fs49i3XDrk5kU/8I+6l9zRCw3cJw==", "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "6.0.0" } }, - "System.Net.Http": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", + "xunit": { + "type": "Direct", + "requested": "[2.7.0, )", + "resolved": "2.7.0", + "contentHash": "KcCI5zxh8zbUfQTeErc4oT7YokViVND2V0p4vDJ2VD4lhF9V5qCYMMDNixme7FdwYy3SwPHF+2xC2Dq4Z9GSlA==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + "xunit.analyzers": "1.11.0", + "xunit.assert": "2.7.0", + "xunit.core": "[2.7.0]" } }, - "System.Net.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", + "xunit.extensibility.execution": { + "type": "Direct", + "requested": "[2.*, )", + "resolved": "2.7.0", + "contentHash": "bjY+crT1jOyxKagFjCMdEVzoenO2v66ru8+CK/0UaXvyG4U9Q3UTieJkbQXbi7/1yZIK1sGh01l5/jh2CwLJtQ==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" + "xunit.extensibility.core": "[2.7.0]" } }, - "System.Net.Sockets": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[2.5.7, )", + "resolved": "2.5.7", + "contentHash": "31Rl7dBJriX0DNwZfDp8gqFOPsiM0c9kqpcH/HvNi9vDp+K7Ydf42H7mVIvYT918Ywzn1ymLg1c4DDC6iU754w==" }, - "System.ObjectModel": { + "Imageflow.NativeRuntime.osx-x86_64": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } + "resolved": "2.0.0-preview8", + "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" }, - "System.Private.DataContractSerialization": { + "Imageflow.NativeRuntime.ubuntu-x86_64": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "yDaJ2x3mMmjdZEDB4IbezSnCsnjQ4BxinKhRAaP6kEgL6Bb6jANWphs5SzyD8imqeC/3FxgsuXT6ykkiH1uUmA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XDocument": "4.3.0", - "System.Xml.XmlDocument": "4.3.0", - "System.Xml.XmlSerializer": "4.3.0" - } + "resolved": "2.0.0-preview8", + "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" }, - "System.Reflection": { + "Imageflow.NativeRuntime.win-x86": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } + "resolved": "2.0.0-preview8", + "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" }, - "System.Reflection.Emit": { + "Imageflow.NativeRuntime.win-x86_64": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } + "resolved": "2.0.0-preview8", + "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" }, - "System.Reflection.Emit.ILGeneration": { + "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } + "resolved": "17.9.0", + "contentHash": "RGD37ZSrratfScYXm7M0HjvxMxZyWZL4jm+XgMZbkIY1UPgjUpbNA/t+WTGj/rC/0Hm9A3IrH3ywbKZkOCnoZA==" }, - "System.Reflection.Emit.Lightweight": { + "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", + "resolved": "17.9.0", + "contentHash": "1ilw/8vgmjLyKU+2SKXKXaOqpYFJCQfGqGz+x0cosl981VzjrY74Sv6qAJv+neZMZ9ZMxF3ArN6kotaQ4uvEBw==", "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" + "System.Reflection.Metadata": "1.6.0" } }, - "System.Reflection.Extensions": { + "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", + "resolved": "17.9.0", + "contentHash": "Spmg7Wx49Ya3SxBjyeAR+nQpjMTKZwTwpZ7KyeOTIqI/WHNPnBU4HUvl5kuHPQAwGWqMy4FGZja1HvEwvoaDiA==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" + "Microsoft.TestPlatform.ObjectModel": "17.9.0", + "Newtonsoft.Json": "13.0.1" } }, "System.Reflection.Metadata": { "type": "Transitive", - "resolved": "1.3.0", - "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Collections.Immutable": "1.2.0", - "System.Diagnostics.Debug": "4.0.11", - "System.IO": "4.1.0", - "System.Linq": "4.1.0", - "System.Reflection": "4.1.0", - "System.Reflection.Extensions": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0", - "System.Text.Encoding": "4.0.11", - "System.Text.Encoding.Extensions": "4.0.11", - "System.Threading": "4.0.11" - } - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Resources.ResourceManager": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, - "System.Runtime.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.InteropServices.RuntimeInformation": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Runtime.Loader": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DHMaRn8D8YCK2GG2pw+UzNxn/OHVfaWx7OTLBD/hPegHZZgcZh3H6seWegrC4BYwsfuGrywIuT+MQs+rPqRLTQ==", - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Numerics": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Runtime.Serialization.Json": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "CpVfOH0M/uZ5PH+M9+Gu56K0j9lJw3M+PKRegTkcrY/stOIvRUeonggxNrfBYLA5WOHL2j15KNJuTuld3x4o9w==", - "dependencies": { - "System.IO": "4.3.0", - "System.Private.DataContractSerialization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Serialization.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Security.Cryptography.Algorithms": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Security.Cryptography.X509Certificates": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "6.0.0", @@ -4448,185 +760,6 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, - "System.Text.RegularExpressions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Threading": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Threading.Thread": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OHmbT+Zz065NKII/ZHcH9XO1dEuLGI1L2k7uYss+9C1jLxTC9kTZZuzUOyXHayRk+dft9CiDf3I/QZ0t8JKyBQ==", - "dependencies": { - "System.Runtime": "4.3.0" - } - }, - "System.Threading.ThreadPool": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", - "dependencies": { - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Threading.Timer": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Xml.ReaderWriter": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Tasks.Extensions": "4.3.0" - } - }, - "System.Xml.XDocument": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tools": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - } - }, - "System.Xml.XmlDocument": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - } - }, - "System.Xml.XmlSerializer": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "MYoTCP7EZ98RrANESW05J5ZwskKDoN0AuZ06ZflnowE50LTpbR5yRg3tHckTVm5j/m47stuGgCrCHWePyHS70Q==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XmlDocument": "4.3.0" - } - }, - "System.Xml.XPath": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11", - "System.Xml.ReaderWriter": "4.0.11" - } - }, - "System.Xml.XPath.XmlDocument": { - "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "Zm2BdeanuncYs3NhCj4c9e1x3EXFzFBVv2wPEc/Dj4ZbI9R8ecLSR5frAsx4zJCPBtKQreQ7Q/KxJEohJZbfzA==", - "dependencies": { - "System.Collections": "4.0.11", - "System.Globalization": "4.0.11", - "System.IO": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11", - "System.Xml.ReaderWriter": "4.0.11", - "System.Xml.XPath": "4.0.1", - "System.Xml.XmlDocument": "4.0.1" - } - }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.3", diff --git a/tests/Imageflow.TestDotNetFullPackageReference/Imageflow.TestDotNetFullPackageReference.csproj b/tests/Imageflow.TestDotNetFullPackageReference/Imageflow.TestDotNetFullPackageReference.csproj index 2fc0825..5700e83 100644 --- a/tests/Imageflow.TestDotNetFullPackageReference/Imageflow.TestDotNetFullPackageReference.csproj +++ b/tests/Imageflow.TestDotNetFullPackageReference/Imageflow.TestDotNetFullPackageReference.csproj @@ -57,10 +57,10 @@ - 2.2.7 + 3.2.2 - 2.2.7 + 3.2.2 diff --git a/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json b/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json index 3394a9b..e5ac0f2 100644 --- a/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json +++ b/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json @@ -4,15 +4,20 @@ ".NETFramework,Version=v4.7.2": { "MSTest.TestAdapter": { "type": "Direct", - "requested": "[2.2.7, )", - "resolved": "2.2.7", - "contentHash": "E8seWhOKGEz4Aq6IaX6dUZGRSVYwLVy+6ZI9nq0TyHRuv54Id4Y7y/CB0KGiOUrqtygfooQm5wmpnDOW28N9Ug==" + "requested": "[3.2.2, )", + "resolved": "3.2.2", + "contentHash": "E3dnR9KTNCP61O4yIRBPV0KFXQszW2IJv3O+LqNIK6rUrXNSsXc9QeZBnFYBYwA21uTv3e27xDyreddqe9eUHg==", + "dependencies": { + "Microsoft.Testing.Extensions.Telemetry": "1.0.2", + "Microsoft.Testing.Extensions.VSTestBridge": "1.0.2", + "Microsoft.Testing.Platform.MSBuild": "1.0.2" + } }, "MSTest.TestFramework": { "type": "Direct", - "requested": "[2.2.7, )", - "resolved": "2.2.7", - "contentHash": "qroVMzabIcbqwxtlDZriZflqs3nKpmqSVoM/Nfw9fg+F6885rrgo9R4gwmK7xxPPd8gpDrmNqqwpnyh6GMoN9g==" + "requested": "[3.2.2, )", + "resolved": "3.2.2", + "contentHash": "t3o8wfuMvjq1ETxsX6qBIOJMpnNpRoBjf2+0ZEKURSH+xHnpLM7yojNkLgJbir7vQGzQh7JaAUBO1MkszBvVrw==" }, "Imageflow.NativeRuntime.osx-x86_64": { "type": "Transitive", @@ -34,6 +39,14 @@ "resolved": "2.0.0-preview8", "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" }, + "Microsoft.ApplicationInsights": { + "type": "Transitive", + "resolved": "2.21.0", + "contentHash": "btZEDWAFNo9CoYliMCriSMTX3ruRGZTtYw4mo2XyyfLlowFicYVM2Xszi5evDG95QRYV7MbbH3D2RqVwfZlJHw==", + "dependencies": { + "System.Diagnostics.DiagnosticSource": "5.0.0" + } + }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "6.0.0", @@ -50,11 +63,79 @@ "System.Memory": "4.5.5" } }, + "Microsoft.Testing.Extensions.Telemetry": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "32YnySeewFl0IAgI27bTbFZdfA+Tvmg5p7kxgPcFAPKLjW1HTNKlKMrEYFgCIIT1Onak2Iod0tvsHYqXzV3a+w==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.21.0", + "Microsoft.Testing.Platform": "1.0.2" + } + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "MPHXtnoaf3lESF7eXbYSzvb12K1SZslEpQ1evf3GZAvtfXXyBeFwpcSMd+l3jW8z+zhmniC2gAc6jq/GEgFW2Q==", + "dependencies": { + "Microsoft.Testing.Platform": "1.0.2" + } + }, + "Microsoft.Testing.Extensions.VSTestBridge": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "68/CZ2mmtdNx8QrMenQ90HVb0gYGpFP54GLRwvMyaDgnhwYgN55TDU+ypBS3X5xThJda42mckfFhBqpjjeYz+A==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.5.0", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.0.2", + "Microsoft.Testing.Platform": "1.0.2" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "pVZqxU3LsZLF7Cle0GH+j1+uz5FhmcxEe9sS5sZvONeqgv/EP/p7aI3bIaeoVs0YAVH3UmlqDAsWP5oXcT294A==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "9f5JiIEEEkI/MvCi9zCxbX8k9D4xtyqJkaWKtDIt4R/CAJEYN5op3LcUyfbMAKhsFUTn1D+DGTfu5XPoh6fwJw==", + "dependencies": { + "Microsoft.Testing.Platform": "1.0.2" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.5.0", + "contentHash": "QwiBJcC/oEA1kojOaB0uPWOIo4i6BYuTBBYJVhUvmXkyYqZ2Ut/VZfgi+enf8LF8J4sjO98oRRFt39MiRorcIw==", + "dependencies": { + "NuGet.Frameworks": "5.11.0", + "System.Reflection.Metadata": "1.6.0" + } + }, + "NuGet.Frameworks": { + "type": "Transitive", + "resolved": "5.11.0", + "contentHash": "eaiXkUjC4NPcquGWzAGMXjuxvLwc6XGKMptSyOGQeT0X70BUZObuybJFZLA0OfTdueLd3US23NBPTBb6iF3V1Q==" + }, "System.Buffers": { "type": "Transitive", "resolved": "4.5.1", "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "1.5.0", + "contentHash": "EXKiDFsChZW0RjrZ4FYHu9aW6+P4MCgEDCklsVseRfhoO0F+dXeMSsMRAlVXIo06kGJ/zv+2w1a2uc2+kxxSaQ==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, "System.Memory": { "type": "Transitive", "resolved": "4.5.5", @@ -70,6 +151,14 @@ "resolved": "4.5.0", "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==", + "dependencies": { + "System.Collections.Immutable": "1.5.0" + } + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", @@ -132,6 +221,94 @@ "System.Text.Json": "[6.*, )" } } + }, + ".NETFramework,Version=v4.7.2/win": { + "Imageflow.NativeRuntime.osx-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" + }, + "Imageflow.NativeRuntime.ubuntu-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" + }, + "Imageflow.NativeRuntime.win-x86": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" + }, + "Imageflow.NativeRuntime.win-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" + } + }, + ".NETFramework,Version=v4.7.2/win-arm64": { + "Imageflow.NativeRuntime.osx-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" + }, + "Imageflow.NativeRuntime.ubuntu-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" + }, + "Imageflow.NativeRuntime.win-x86": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" + }, + "Imageflow.NativeRuntime.win-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" + } + }, + ".NETFramework,Version=v4.7.2/win-x64": { + "Imageflow.NativeRuntime.osx-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" + }, + "Imageflow.NativeRuntime.ubuntu-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" + }, + "Imageflow.NativeRuntime.win-x86": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" + }, + "Imageflow.NativeRuntime.win-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" + } + }, + ".NETFramework,Version=v4.7.2/win-x86": { + "Imageflow.NativeRuntime.osx-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" + }, + "Imageflow.NativeRuntime.ubuntu-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" + }, + "Imageflow.NativeRuntime.win-x86": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" + }, + "Imageflow.NativeRuntime.win-x86_64": { + "type": "Transitive", + "resolved": "2.0.0-preview8", + "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" + } } } } \ No newline at end of file From 9040981293bef0849f0b49d1df7df76d75d7596d Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 21:49:41 -0700 Subject: [PATCH 10/22] Make AnyColor struct readonly. --- src/Imageflow/Fluent/AnyColor.cs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Imageflow/Fluent/AnyColor.cs b/src/Imageflow/Fluent/AnyColor.cs index 86a22be..4624184 100644 --- a/src/Imageflow/Fluent/AnyColor.cs +++ b/src/Imageflow/Fluent/AnyColor.cs @@ -6,19 +6,26 @@ namespace Imageflow.Fluent /// /// Represents a color with or without transparency. /// - public struct AnyColor + public readonly struct AnyColor { - private ColorKind _kind; - private SrgbColor _srgb; - public static AnyColor Black => new AnyColor {_kind = ColorKind.Black}; - public static AnyColor Transparent => new AnyColor {_kind = ColorKind.Transparent}; + private AnyColor(ColorKind kind, SrgbColor srgb = default) + { + _kind = kind; + _srgb = srgb; + } + private readonly ColorKind _kind; + private readonly SrgbColor _srgb; + public static AnyColor Black => new AnyColor(ColorKind.Black); + public static AnyColor Transparent => new AnyColor(ColorKind.Transparent); + /// /// Parses color in RGB, RGBA, RRGGBB or RRGGBBAA format /// /// /// - public static AnyColor FromHexSrgb(string hex) => new AnyColor {_kind = ColorKind.Srgb, _srgb = SrgbColor.FromHex(hex)}; - public static AnyColor Srgb(SrgbColor c) => new AnyColor {_kind = ColorKind.Srgb, _srgb = c}; + public static AnyColor FromHexSrgb(string hex) => new AnyColor(ColorKind.Srgb, SrgbColor.FromHex(hex)); + + public static AnyColor Srgb(SrgbColor c) => new AnyColor(ColorKind.Srgb, c); [Obsolete("Use ToJsonNode() instead")] public object ToImageflowDynamic() From 6cbbb6bc62fc49b34f45958e816045515be7befe Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 21:50:05 -0700 Subject: [PATCH 11/22] Organize classes to be in their own files. --- src/Imageflow/Fluent/BuildEncodeResult.cs | 2 +- src/Imageflow/Fluent/BytesDestination.cs | 74 ++++++ src/Imageflow/Fluent/IOutputDestination.cs | 220 +----------------- .../Fluent/IOutputDestinationExtensions.cs | 114 +++++++++ src/Imageflow/Fluent/ImageJob.cs | 2 +- src/Imageflow/Fluent/SrgbColor.cs | 2 +- src/Imageflow/Fluent/StreamDestination.cs | 42 ++++ src/Imageflow/Fluent/WatermarkFitBox.cs | 1 + .../OutputDestinationWrapper.cs | 0 9 files changed, 235 insertions(+), 222 deletions(-) create mode 100644 src/Imageflow/Fluent/BytesDestination.cs create mode 100644 src/Imageflow/Fluent/IOutputDestinationExtensions.cs create mode 100644 src/Imageflow/Fluent/StreamDestination.cs rename src/Imageflow/{Fluent => Internal.Helpers}/OutputDestinationWrapper.cs (100%) diff --git a/src/Imageflow/Fluent/BuildEncodeResult.cs b/src/Imageflow/Fluent/BuildEncodeResult.cs index 9901b3b..fa0fb02 100644 --- a/src/Imageflow/Fluent/BuildEncodeResult.cs +++ b/src/Imageflow/Fluent/BuildEncodeResult.cs @@ -1,6 +1,6 @@ namespace Imageflow.Fluent { - public record class BuildEncodeResult + public record BuildEncodeResult { // internal BuildEncodeResult(string preferredMimeType, // string preferredExtension, int ioId, int width, int height, IOutputDestination destination) diff --git a/src/Imageflow/Fluent/BytesDestination.cs b/src/Imageflow/Fluent/BytesDestination.cs new file mode 100644 index 0000000..1cb62e2 --- /dev/null +++ b/src/Imageflow/Fluent/BytesDestination.cs @@ -0,0 +1,74 @@ +using Imageflow.Bindings; +using Imageflow.Internal.Helpers; + +namespace Imageflow.Fluent; + +public class BytesDestination : IOutputDestination, IOutputSink, IAsyncOutputSink +{ + private MemoryStream? _m; + public void Dispose() + { + + } + + public Task RequestCapacityAsync(int bytes) + { + RequestCapacity(bytes); + return Task.CompletedTask; + } + + public Task WriteAsync(ArraySegment bytes, CancellationToken cancellationToken) + { + if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.WriteAsync called before RequestCapacityAsync"); + if (bytes.Array == null) throw new ImageflowAssertionFailed("BytesDestination.WriteAsync called with null array"); + return _m.WriteAsync(bytes.Array, bytes.Offset, bytes.Count, cancellationToken); + } + + public Task FlushAsync(CancellationToken cancellationToken) + { + if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.FlushAsync called before RequestCapacityAsync"); + return _m.FlushAsync(cancellationToken); + } + + public ArraySegment GetBytes() + { + if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.GetBytes called before RequestCapacityAsync"); + if (!_m.TryGetBuffer(out var bytes)) + { + throw new ImageflowAssertionFailed("MemoryStream TryGetBuffer should not fail here"); + } + return bytes; + } + + public void RequestCapacity(int bytes) + { + _m ??= new MemoryStream(bytes); + if (_m.Capacity < bytes) _m.Capacity = bytes; + } + + public void Write(ReadOnlySpan data) + { + if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.Write called before RequestCapacity"); + _m.WriteSpan(data); + } + public ValueTask FastWriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken) + { + if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.FastWriteAsync called before RequestCapacityAsync"); + return _m.WriteMemoryAsync(data, cancellationToken); + } + public void Flush() + { + _m?.Flush(); // Redundant for MemoryStream. + } + + public ValueTask FastRequestCapacityAsync(int bytes) + { + RequestCapacity(bytes); + return new ValueTask(); + } + public ValueTask FastFlushAsync(CancellationToken cancellationToken) + { + Flush(); + return new ValueTask(); + } +} \ No newline at end of file diff --git a/src/Imageflow/Fluent/IOutputDestination.cs b/src/Imageflow/Fluent/IOutputDestination.cs index 9812fa6..3262990 100644 --- a/src/Imageflow/Fluent/IOutputDestination.cs +++ b/src/Imageflow/Fluent/IOutputDestination.cs @@ -1,9 +1,4 @@ -using System.Buffers; -using System.Runtime.InteropServices; -using Imageflow.Bindings; -using Imageflow.Internal.Helpers; - -namespace Imageflow.Fluent +namespace Imageflow.Fluent { public interface IOutputDestination : IDisposable { @@ -15,217 +10,4 @@ public interface IOutputDestination : IDisposable // ReSharper disable once InconsistentNaming - public static class IOutputDestinationExtensions - { - internal static void AdaptiveWriteAll(this IOutputDestination dest, ReadOnlyMemory data) - { - if (dest is IOutputSink syncSink) - { - syncSink.RequestCapacity(data.Length); - syncSink.Write(data.Span); - syncSink.Flush(); - } - else - { - dest.RequestCapacityAsync(data.Length).Wait(); - dest.AdaptedWrite(data.Span); - dest.FlushAsync(default).Wait(); - } - } - - internal static async ValueTask AdaptiveWriteAllAsync(this IOutputDestination dest, ReadOnlyMemory data, CancellationToken cancellationToken) - { - if (dest is IAsyncOutputSink sink) - { - await sink.FastRequestCapacityAsync(data.Length); - await sink.FastWriteAsync(data, cancellationToken); - await sink.FastFlushAsync(cancellationToken); - return; - } - await dest.RequestCapacityAsync(data.Length); - await dest.AdaptedWriteAsync(data, cancellationToken); - await dest.FlushAsync(cancellationToken); - } - - - internal static async ValueTask AdaptedWriteAsync(this IOutputDestination dest, ReadOnlyMemory data, CancellationToken cancellationToken) - { - if (MemoryMarshal.TryGetArray(data, out ArraySegment segment)) - { - await dest.WriteAsync(segment, cancellationToken).ConfigureAwait(false); - return; - } - - var rent = ArrayPool.Shared.Rent(Math.Min(81920,data.Length)); - try - { - for (int i = 0; i < data.Length; i += rent.Length) - { - int len = Math.Min(rent.Length, data.Length - i); - data.Span.Slice(i, len).CopyTo(rent); - await dest.WriteAsync(new ArraySegment(rent, 0, len), cancellationToken).ConfigureAwait(false); - } - } - finally - { - ArrayPool.Shared.Return(rent); - } - - } - internal static void AdaptedWrite(this IOutputDestination dest, ReadOnlySpan data) - { - - var rent = ArrayPool.Shared.Rent(Math.Min(81920,data.Length)); - try - { - for (int i = 0; i < data.Length; i += rent.Length) - { - int len = Math.Min(rent.Length, data.Length - i); - data.Slice(i, len).CopyTo(rent); - dest.WriteAsync(new ArraySegment(rent, 0, len), default).Wait(); - } - } - finally - { - ArrayPool.Shared.Return(rent); - } - } - - - // internal static IAsyncOutputSink ToAsyncOutputSink(this IOutputDestination dest, bool disposeUnderlying = true) - // { - // if (dest is IAsyncOutputSink sink) return sink; - // return new OutputDestinationToSinkAdapter(dest, disposeUnderlying); - // } - - public static async Task CopyFromStreamAsync(this IOutputDestination dest, Stream stream, - CancellationToken cancellationToken) - { - if (stream is { CanRead: true, CanSeek: true }) - { - await dest.RequestCapacityAsync((int) stream.Length); - } - - const int bufferSize = 81920; - var buffer = new byte[bufferSize]; - - int bytesRead; - while ((bytesRead = - await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) - { - await dest.WriteAsync(new ArraySegment(buffer, 0, bytesRead), cancellationToken) - .ConfigureAwait(false); - } - - await dest.FlushAsync(cancellationToken); - } - } - - public class BytesDestination : IOutputDestination, IOutputSink, IAsyncOutputSink - { - private MemoryStream? _m; - public void Dispose() - { - - } - - public Task RequestCapacityAsync(int bytes) - { - RequestCapacity(bytes); - return Task.CompletedTask; - } - - public Task WriteAsync(ArraySegment bytes, CancellationToken cancellationToken) - { - if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.WriteAsync called before RequestCapacityAsync"); - if (bytes.Array == null) throw new ImageflowAssertionFailed("BytesDestination.WriteAsync called with null array"); - return _m.WriteAsync(bytes.Array, bytes.Offset, bytes.Count, cancellationToken); - } - - public Task FlushAsync(CancellationToken cancellationToken) - { - if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.FlushAsync called before RequestCapacityAsync"); - return _m.FlushAsync(cancellationToken); - } - - public ArraySegment GetBytes() - { - if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.GetBytes called before RequestCapacityAsync"); - if (!_m.TryGetBuffer(out var bytes)) - { - throw new ImageflowAssertionFailed("MemoryStream TryGetBuffer should not fail here"); - } - return bytes; - } - - public void RequestCapacity(int bytes) - { - _m ??= new MemoryStream(bytes); - if (_m.Capacity < bytes) _m.Capacity = bytes; - } - - public void Write(ReadOnlySpan data) - { - if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.Write called before RequestCapacity"); - _m.WriteSpan(data); - } - public ValueTask FastWriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken) - { - if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.FastWriteAsync called before RequestCapacityAsync"); - return _m.WriteMemoryAsync(data, cancellationToken); - } - public void Flush() - { - _m?.Flush(); // Redundant for MemoryStream. - } - - public ValueTask FastRequestCapacityAsync(int bytes) - { - RequestCapacity(bytes); - return new ValueTask(); - } - public ValueTask FastFlushAsync(CancellationToken cancellationToken) - { - Flush(); - return new ValueTask(); - } - } - - public class StreamDestination(Stream underlying, bool disposeUnderlying) : IOutputDestination - { - public void Dispose() - { - if (disposeUnderlying) underlying?.Dispose(); - } - - public Task RequestCapacityAsync(int bytes) - { - if (underlying is { CanSeek: true, CanWrite: true }) underlying.SetLength(bytes); - return Task.CompletedTask; - } - - public Task WriteAsync(ArraySegment bytes, CancellationToken cancellationToken) - { - if (bytes.Array == null) throw new ImageflowAssertionFailed("StreamDestination.WriteAsync called with null array"); - return underlying.WriteAsync(bytes.Array, bytes.Offset, bytes.Count, cancellationToken); - } - - public ValueTask WriteAsync(ReadOnlyMemory bytes, CancellationToken cancellationToken) - { - return underlying.WriteMemoryAsync(bytes, cancellationToken); - } - - public void Write(ReadOnlySpan bytes) - { - underlying.WriteSpan(bytes); - } - - public Task FlushAsync(CancellationToken cancellationToken) - { - if (underlying is { CanSeek: true, CanWrite: true } - && underlying.Position < underlying.Length) - underlying.SetLength(underlying.Position); - return underlying.FlushAsync(cancellationToken); - } - } } \ No newline at end of file diff --git a/src/Imageflow/Fluent/IOutputDestinationExtensions.cs b/src/Imageflow/Fluent/IOutputDestinationExtensions.cs new file mode 100644 index 0000000..7632fa1 --- /dev/null +++ b/src/Imageflow/Fluent/IOutputDestinationExtensions.cs @@ -0,0 +1,114 @@ +using System.Buffers; +using System.Runtime.InteropServices; + +namespace Imageflow.Fluent; + +public static class IOutputDestinationExtensions +{ + internal static void AdaptiveWriteAll(this IOutputDestination dest, ReadOnlyMemory data) + { + if (dest is IOutputSink syncSink) + { + syncSink.RequestCapacity(data.Length); + syncSink.Write(data.Span); + syncSink.Flush(); + } + else + { + dest.RequestCapacityAsync(data.Length).Wait(); + dest.AdaptedWrite(data.Span); + dest.FlushAsync(default).Wait(); + } + } + + internal static async ValueTask AdaptiveWriteAllAsync(this IOutputDestination dest, ReadOnlyMemory data, CancellationToken cancellationToken) + { + if (dest is IAsyncOutputSink sink) + { + await sink.FastRequestCapacityAsync(data.Length); + await sink.FastWriteAsync(data, cancellationToken); + await sink.FastFlushAsync(cancellationToken); + return; + } + await dest.RequestCapacityAsync(data.Length); + await dest.AdaptedWriteAsync(data, cancellationToken); + await dest.FlushAsync(cancellationToken); + } + + + internal static async ValueTask AdaptedWriteAsync(this IOutputDestination dest, ReadOnlyMemory data, CancellationToken cancellationToken) + { + if (MemoryMarshal.TryGetArray(data, out ArraySegment segment)) + { + await dest.WriteAsync(segment, cancellationToken).ConfigureAwait(false); + return; + } + + var rent = ArrayPool.Shared.Rent(Math.Min(81920,data.Length)); + try + { + for (int i = 0; i < data.Length; i += rent.Length) + { + int len = Math.Min(rent.Length, data.Length - i); + data.Span.Slice(i, len).CopyTo(rent); + await dest.WriteAsync(new ArraySegment(rent, 0, len), cancellationToken).ConfigureAwait(false); + } + } + finally + { + ArrayPool.Shared.Return(rent); + } + + } + internal static void AdaptedWrite(this IOutputDestination dest, ReadOnlySpan data) + { + + var rent = ArrayPool.Shared.Rent(Math.Min(81920,data.Length)); + try + { + for (int i = 0; i < data.Length; i += rent.Length) + { + int len = Math.Min(rent.Length, data.Length - i); + data.Slice(i, len).CopyTo(rent); + dest.WriteAsync(new ArraySegment(rent, 0, len), default).Wait(); + } + } + finally + { + ArrayPool.Shared.Return(rent); + } + } + + + // internal static IAsyncOutputSink ToAsyncOutputSink(this IOutputDestination dest, bool disposeUnderlying = true) + // { + // if (dest is IAsyncOutputSink sink) return sink; + // return new OutputDestinationToSinkAdapter(dest, disposeUnderlying); + // } + [Obsolete("Users should not write to IOutputDestination directly; this is only for Imageflow internal use.")] + public static async Task CopyFromStreamAsync(this IOutputDestination dest, Stream stream, + CancellationToken cancellationToken) + => await dest.CopyFromStreamAsyncInternal(stream, cancellationToken); + + internal static async Task CopyFromStreamAsyncInternal(this IOutputDestination dest, Stream stream, + CancellationToken cancellationToken) + { + if (stream is { CanRead: true, CanSeek: true }) + { + await dest.RequestCapacityAsync((int) stream.Length); + } + + const int bufferSize = 81920; + var buffer = new byte[bufferSize]; + + int bytesRead; + while ((bytesRead = + await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + await dest.WriteAsync(new ArraySegment(buffer, 0, bytesRead), cancellationToken) + .ConfigureAwait(false); + } + + await dest.FlushAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/src/Imageflow/Fluent/ImageJob.cs b/src/Imageflow/Fluent/ImageJob.cs index 79f7c14..9f36b65 100644 --- a/src/Imageflow/Fluent/ImageJob.cs +++ b/src/Imageflow/Fluent/ImageJob.cs @@ -371,7 +371,7 @@ internal async Task CopyOutputsToDestinations(CancellationToken token) { using (var stream = pair.Key.ReadFromBeginning()) { - await pair.Value.CopyFromStreamAsync(stream, token); + await pair.Value.CopyFromStreamAsyncInternal(stream, token); } } } diff --git a/src/Imageflow/Fluent/SrgbColor.cs b/src/Imageflow/Fluent/SrgbColor.cs index 1fc6981..2a7a6dc 100644 --- a/src/Imageflow/Fluent/SrgbColor.cs +++ b/src/Imageflow/Fluent/SrgbColor.cs @@ -4,7 +4,7 @@ namespace Imageflow.Fluent { /// - /// Represents a color in the sRGB colorspace + /// Represents a color in the sRGB colorspace. Default value: transparent black. /// public readonly struct SrgbColor(byte r, byte g, byte b, byte a) { diff --git a/src/Imageflow/Fluent/StreamDestination.cs b/src/Imageflow/Fluent/StreamDestination.cs new file mode 100644 index 0000000..589233c --- /dev/null +++ b/src/Imageflow/Fluent/StreamDestination.cs @@ -0,0 +1,42 @@ +using Imageflow.Bindings; +using Imageflow.Internal.Helpers; + +namespace Imageflow.Fluent; + +public class StreamDestination(Stream underlying, bool disposeUnderlying) : IOutputDestination +{ + public void Dispose() + { + if (disposeUnderlying) underlying?.Dispose(); + } + + public Task RequestCapacityAsync(int bytes) + { + if (underlying is { CanSeek: true, CanWrite: true }) underlying.SetLength(bytes); + return Task.CompletedTask; + } + + public Task WriteAsync(ArraySegment bytes, CancellationToken cancellationToken) + { + if (bytes.Array == null) throw new ImageflowAssertionFailed("StreamDestination.WriteAsync called with null array"); + return underlying.WriteAsync(bytes.Array, bytes.Offset, bytes.Count, cancellationToken); + } + + public ValueTask WriteAsync(ReadOnlyMemory bytes, CancellationToken cancellationToken) + { + return underlying.WriteMemoryAsync(bytes, cancellationToken); + } + + public void Write(ReadOnlySpan bytes) + { + underlying.WriteSpan(bytes); + } + + public Task FlushAsync(CancellationToken cancellationToken) + { + if (underlying is { CanSeek: true, CanWrite: true } + && underlying.Position < underlying.Length) + underlying.SetLength(underlying.Position); + return underlying.FlushAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/src/Imageflow/Fluent/WatermarkFitBox.cs b/src/Imageflow/Fluent/WatermarkFitBox.cs index a2b495d..b3e829e 100644 --- a/src/Imageflow/Fluent/WatermarkFitBox.cs +++ b/src/Imageflow/Fluent/WatermarkFitBox.cs @@ -18,6 +18,7 @@ public WatermarkFitBox(WatermarkAlign relativeTo, float x1, float y1, float x2, Y2 = y2; } public WatermarkAlign RelativeTo { get; set; } = WatermarkAlign.Image; + public float X1 { get; set; } public float Y1 { get; set; } public float X2 { get; set; } = 100; diff --git a/src/Imageflow/Fluent/OutputDestinationWrapper.cs b/src/Imageflow/Internal.Helpers/OutputDestinationWrapper.cs similarity index 100% rename from src/Imageflow/Fluent/OutputDestinationWrapper.cs rename to src/Imageflow/Internal.Helpers/OutputDestinationWrapper.cs From 52c8cc76f0fd4efe21b5cb98b435b23696f1ccfe Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 22:20:48 -0700 Subject: [PATCH 12/22] Add .editorconfig (from aspnetcore repo), dotnet format whitespace src/Imageflow.dnfull.sln --- .editorconfig | 460 ++++++++++++++++++ src/Imageflow/Bindings/ImageInfo.cs | 13 +- .../Bindings/ImageflowAssertionFailed.cs | 2 +- src/Imageflow/Bindings/ImageflowException.cs | 14 +- src/Imageflow/Bindings/ImageflowMethods.cs | 4 +- .../Bindings/ImageflowUnmanagedReadStream.cs | 11 +- src/Imageflow/Bindings/JobContext.cs | 92 ++-- src/Imageflow/Bindings/JobContextHandle.cs | 4 +- src/Imageflow/Bindings/JsonResponse.cs | 38 +- src/Imageflow/Bindings/JsonResponseHandle.cs | 12 +- .../Bindings/NativeLibraryLoading.cs | 83 ++-- src/Imageflow/Bindings/NativeMethods.cs | 5 +- src/Imageflow/Bindings/VersionInfo.cs | 16 +- src/Imageflow/Fluent/AnyColor.cs | 19 +- src/Imageflow/Fluent/BufferedStreamSource.cs | 25 +- src/Imageflow/Fluent/BuildDecodeResult.cs | 10 +- src/Imageflow/Fluent/BuildEncodeResult.cs | 18 +- src/Imageflow/Fluent/BuildEndpoint.cs | 19 +- src/Imageflow/Fluent/BuildItemBase.cs | 14 +- src/Imageflow/Fluent/BuildJobResult.cs | 56 +-- src/Imageflow/Fluent/BuildNode.cs | 143 +++--- src/Imageflow/Fluent/BytesDestination.cs | 8 +- src/Imageflow/Fluent/BytesSourceAdapter.cs | 8 +- src/Imageflow/Fluent/Constraint.cs | 16 +- src/Imageflow/Fluent/ConstraintGravity.cs | 5 +- src/Imageflow/Fluent/DecodeCommands.cs | 28 +- src/Imageflow/Fluent/Enumerations.cs | 41 +- src/Imageflow/Fluent/FinishJobBuilder.cs | 18 +- src/Imageflow/Fluent/FrameSizeLimit.cs | 36 +- src/Imageflow/Fluent/IAsyncMemorySource.cs | 10 +- src/Imageflow/Fluent/IBytesSource.cs | 11 +- src/Imageflow/Fluent/IEncoderPreset.cs | 93 ++-- src/Imageflow/Fluent/IOutputDestination.cs | 4 +- .../Fluent/IOutputDestinationExtensions.cs | 26 +- src/Imageflow/Fluent/IOutputSink.cs | 4 +- .../Fluent/IPreparedFilesystemJob.cs | 4 +- .../Fluent/IWatermarkConstraintBox.cs | 2 +- src/Imageflow/Fluent/ImageJob.cs | 184 +++---- src/Imageflow/Fluent/InputWatermark.cs | 10 +- src/Imageflow/Fluent/JobExecutionOptions.cs | 2 +- src/Imageflow/Fluent/MagicBytes.cs | 63 +-- src/Imageflow/Fluent/MemoryLifetimePromise.cs | 6 +- src/Imageflow/Fluent/MemorySource.cs | 21 +- src/Imageflow/Fluent/PerformanceDetails.cs | 7 +- .../Fluent/PerformanceDetailsFrame.cs | 5 +- .../Fluent/PerformanceDetailsNode.cs | 4 +- src/Imageflow/Fluent/ResampleHints.cs | 22 +- src/Imageflow/Fluent/SecurityOptions.cs | 10 +- src/Imageflow/Fluent/SourceLifetime.cs | 4 +- src/Imageflow/Fluent/SrgbColor.cs | 26 +- src/Imageflow/Fluent/StreamDestination.cs | 10 +- src/Imageflow/Fluent/StreamSource.cs | 17 +- src/Imageflow/Fluent/WatermarkFitBox.cs | 16 +- src/Imageflow/Fluent/WatermarkMargins.cs | 12 +- src/Imageflow/Fluent/WatermarkOptions.cs | 18 +- src/Imageflow/GlobalSuppressions.cs | 4 +- src/Imageflow/IO/ProcessEx.cs | 35 +- src/Imageflow/IO/TemporaryFileProviders.cs | 10 +- .../ArgumentNullThrowHelperPolyfill.cs | 2 +- .../ArraySegmentExtensions.cs | 6 +- .../Internal.Helpers/IAssertReady.cs | 2 +- .../OutputDestinationWrapper.cs | 2 +- src/Imageflow/Internal.Helpers/PinBox.cs | 2 +- .../SafeHandleMemoryManager.cs | 17 +- .../StreamMemoryExtensionsPolyfills.cs | 6 +- src/Imageflow/Internal.Helpers/TextHelpers.cs | 20 +- .../UnmanagedMemoryManager.cs | 2 +- tests/Imageflow.Test/TestApi.cs | 144 +++--- tests/Imageflow.Test/TestExamples.cs | 4 +- tests/Imageflow.Test/TestJobContext.cs | 84 ++-- tests/Imageflow.Test/TestJson.cs | 241 ++++----- tests/Imageflow.Test/TestSrgbColor.cs | 13 +- tests/Imageflow.Test/TestStreams.cs | 5 +- .../TestDotNetClassicLibraryLoading.cs | 2 +- ...etClassicPackageReferenceLibraryLoading.cs | 2 +- .../packages.lock.json | 88 ---- tests/Imageflow.TestWebAOT/Program.cs | 5 +- 77 files changed, 1472 insertions(+), 1033 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..97e8c72 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,460 @@ +; EditorConfig to support per-solution formatting. +; Use the EditorConfig VS add-in to make this work. +; http://editorconfig.org/ +; +; Here are some resources for what's supported for .NET/C# +; https://kent-boogaart.com/blog/editorconfig-reference-for-c-developers +; https://learn.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +; +; Be **careful** editing this because some of the rules don't support adding a severity level +; For instance if you change to `dotnet_sort_system_directives_first = true:warning` (adding `:warning`) +; then the rule will be silently ignored. + +; This is the default for the codeline. +root = true + +[*] +indent_style = space +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.cs] +indent_size = 4 +dotnet_sort_system_directives_first = true + +# Don't use this. qualifier +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion + +# use int x = .. over Int32 +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +# use int.MaxValue over Int32.MaxValue +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Require var all the time. +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# Disallow throw expressions. +csharp_style_throw_expression = false:suggestion + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true + +# Namespace settings +csharp_style_namespace_declarations = file_scoped + +# Brace settings +csharp_prefer_braces = true # Prefer curly braces even for one line of code + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +[*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}] +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +[*.json] +indent_size = 2 + +[*.{ps1,psm1}] +indent_size = 4 + +[*.sh] +indent_size = 4 +end_of_line = lf + +[*.{razor,cshtml}] +charset = utf-8-bom + +[*.{cs,vb}] + +# SYSLIB1054: Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time +dotnet_diagnostic.SYSLIB1054.severity = warning + +# CA1018: Mark attributes with AttributeUsageAttribute +dotnet_diagnostic.CA1018.severity = warning + +# CA1047: Do not declare protected member in sealed type +dotnet_diagnostic.CA1047.severity = warning + +# CA1305: Specify IFormatProvider +dotnet_diagnostic.CA1305.severity = warning + +# CA1507: Use nameof to express symbol names +dotnet_diagnostic.CA1507.severity = warning + +# CA1510: Use ArgumentNullException throw helper +dotnet_diagnostic.CA1510.severity = warning + +# CA1511: Use ArgumentException throw helper +dotnet_diagnostic.CA1511.severity = warning + +# CA1512: Use ArgumentOutOfRangeException throw helper +dotnet_diagnostic.CA1512.severity = warning + +# CA1513: Use ObjectDisposedException throw helper +dotnet_diagnostic.CA1513.severity = warning + +# CA1725: Parameter names should match base declaration +dotnet_diagnostic.CA1725.severity = suggestion + +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = warning + +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = warning + +# CA1810: Do not initialize unnecessarily +dotnet_diagnostic.CA1810.severity = warning + +# CA1821: Remove empty Finalizers +dotnet_diagnostic.CA1821.severity = warning + +# CA1822: Make member static +dotnet_diagnostic.CA1822.severity = warning +dotnet_code_quality.CA1822.api_surface = private, internal + +# CA1823: Avoid unused private fields +dotnet_diagnostic.CA1823.severity = warning + +# CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = warning + +# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly +dotnet_diagnostic.CA1826.severity = warning + +# CA1827: Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1827.severity = warning + +# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used +dotnet_diagnostic.CA1828.severity = warning + +# CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1829.severity = warning + +# CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder +dotnet_diagnostic.CA1830.severity = warning + +# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1831.severity = warning + +# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1832.severity = warning + +# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1833.severity = warning + +# CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1834.severity = warning + +# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' +dotnet_diagnostic.CA1835.severity = warning + +# CA1836: Prefer IsEmpty over Count +dotnet_diagnostic.CA1836.severity = warning + +# CA1837: Use 'Environment.ProcessId' +dotnet_diagnostic.CA1837.severity = warning + +# CA1838: Avoid 'StringBuilder' parameters for P/Invokes +dotnet_diagnostic.CA1838.severity = warning + +# CA1839: Use 'Environment.ProcessPath' +dotnet_diagnostic.CA1839.severity = warning + +# CA1840: Use 'Environment.CurrentManagedThreadId' +dotnet_diagnostic.CA1840.severity = warning + +# CA1841: Prefer Dictionary.Contains methods +dotnet_diagnostic.CA1841.severity = warning + +# CA1842: Do not use 'WhenAll' with a single task +dotnet_diagnostic.CA1842.severity = warning + +# CA1843: Do not use 'WaitAll' with a single task +dotnet_diagnostic.CA1843.severity = warning + +# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' +dotnet_diagnostic.CA1844.severity = warning + +# CA1845: Use span-based 'string.Concat' +dotnet_diagnostic.CA1845.severity = warning + +# CA1846: Prefer AsSpan over Substring +dotnet_diagnostic.CA1846.severity = warning + +# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters +dotnet_diagnostic.CA1847.severity = warning + +# CA1852: Seal internal types +dotnet_diagnostic.CA1852.severity = warning + +# CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method +dotnet_diagnostic.CA1854.severity = warning + +# CA1855: Prefer 'Clear' over 'Fill' +dotnet_diagnostic.CA1855.severity = warning + +# CA1856: Incorrect usage of ConstantExpected attribute +dotnet_diagnostic.CA1856.severity = error + +# CA1857: A constant is expected for the parameter +dotnet_diagnostic.CA1857.severity = warning + +# CA1858: Use 'StartsWith' instead of 'IndexOf' +dotnet_diagnostic.CA1858.severity = warning + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = warning + +# CA2008: Do not create tasks without passing a TaskScheduler +dotnet_diagnostic.CA2008.severity = warning + +# CA2009: Do not call ToImmutableCollection on an ImmutableCollection value +dotnet_diagnostic.CA2009.severity = warning + +# CA2011: Avoid infinite recursion +dotnet_diagnostic.CA2011.severity = warning + +# CA2012: Use ValueTask correctly +dotnet_diagnostic.CA2012.severity = warning + +# CA2013: Do not use ReferenceEquals with value types +dotnet_diagnostic.CA2013.severity = warning + +# CA2014: Do not use stackalloc in loops. +dotnet_diagnostic.CA2014.severity = warning + +# CA2016: Forward the 'CancellationToken' parameter to methods that take one +dotnet_diagnostic.CA2016.severity = warning + +# CA2200: Rethrow to preserve stack details +dotnet_diagnostic.CA2200.severity = warning + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = warning + +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = warning + +# CA2245: Do not assign a property to itself +dotnet_diagnostic.CA2245.severity = warning + +# CA2246: Assigning symbol and its member in the same statement +dotnet_diagnostic.CA2246.severity = warning + +# CA2249: Use string.Contains instead of string.IndexOf to improve readability. +dotnet_diagnostic.CA2249.severity = warning + +# IDE0005: Remove unnecessary usings +dotnet_diagnostic.IDE0005.severity = warning + +# IDE0011: Curly braces to surround blocks of code +dotnet_diagnostic.IDE0011.severity = warning + +# IDE0020: Use pattern matching to avoid is check followed by a cast (with variable) +dotnet_diagnostic.IDE0020.severity = warning + +# IDE0029: Use coalesce expression (non-nullable types) +dotnet_diagnostic.IDE0029.severity = warning + +# IDE0030: Use coalesce expression (nullable types) +dotnet_diagnostic.IDE0030.severity = warning + +# IDE0031: Use null propagation +dotnet_diagnostic.IDE0031.severity = warning + +# IDE0035: Remove unreachable code +dotnet_diagnostic.IDE0035.severity = warning + +# IDE0036: Order modifiers +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +dotnet_diagnostic.IDE0036.severity = warning + +# IDE0038: Use pattern matching to avoid is check followed by a cast (without variable) +dotnet_diagnostic.IDE0038.severity = warning + +# IDE0043: Format string contains invalid placeholder +dotnet_diagnostic.IDE0043.severity = warning + +# IDE0044: Make field readonly +dotnet_diagnostic.IDE0044.severity = warning + +# IDE0051: Remove unused private members +dotnet_diagnostic.IDE0051.severity = warning + +# IDE0055: All formatting rules +dotnet_diagnostic.IDE0055.severity = suggestion + +# IDE0059: Unnecessary assignment to a value +dotnet_diagnostic.IDE0059.severity = warning + +# IDE0060: Remove unused parameter +dotnet_code_quality_unused_parameters = non_public +dotnet_diagnostic.IDE0060.severity = warning + +# IDE0062: Make local function static +dotnet_diagnostic.IDE0062.severity = warning + +# IDE0073: File header +dotnet_diagnostic.IDE0073.severity = warning +# file_header_template = Imazen LLC licenses this file under one or more open source and commercial licenses. See the LICENSE file in the project root for more information. + +# IDE0161: Convert to file-scoped namespace +dotnet_diagnostic.IDE0161.severity = warning + +# IDE0200: Lambda expression can be removed +dotnet_diagnostic.IDE0200.severity = warning + +# IDE2000: Disallow multiple blank lines +dotnet_style_allow_multiple_blank_lines_experimental = false +dotnet_diagnostic.IDE2000.severity = warning + +[{eng/tools/**.cs,**/{test,testassets,samples,Samples,perf,benchmarkapps,scripts,stress}/**.cs,src/Hosting/Server.IntegrationTesting/**.cs,src/Servers/IIS/IntegrationTesting.IIS/**.cs,src/Shared/Http2cat/**.cs,src/Testing/**.cs}] +# CA1018: Mark attributes with AttributeUsageAttribute +dotnet_diagnostic.CA1018.severity = suggestion +# CA1507: Use nameof to express symbol names +dotnet_diagnostic.CA1507.severity = suggestion +# CA1510: Use ArgumentNullException throw helper +dotnet_diagnostic.CA1510.severity = suggestion +# CA1511: Use ArgumentException throw helper +dotnet_diagnostic.CA1511.severity = suggestion +# CA1512: Use ArgumentOutOfRangeException throw helper +dotnet_diagnostic.CA1512.severity = suggestion +# CA1513: Use ObjectDisposedException throw helper +dotnet_diagnostic.CA1513.severity = suggestion +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = suggestion +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = suggestion +# CA1810: Do not initialize unnecessarily +dotnet_diagnostic.CA1810.severity = suggestion +# CA1822: Make member static +dotnet_diagnostic.CA1822.severity = suggestion +# CA1823: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = suggestion +# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly +dotnet_diagnostic.CA1826.severity = suggestion +# CA1827: Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1827.severity = suggestion +# CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1829.severity = suggestion +# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1831.severity = suggestion +# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1832.severity = suggestion +# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1833.severity = suggestion +# CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1834.severity = suggestion +# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' +dotnet_diagnostic.CA1835.severity = suggestion +# CA1837: Use 'Environment.ProcessId' +dotnet_diagnostic.CA1837.severity = suggestion +# CA1838: Avoid 'StringBuilder' parameters for P/Invokes +dotnet_diagnostic.CA1838.severity = suggestion +# CA1841: Prefer Dictionary.Contains methods +dotnet_diagnostic.CA1841.severity = suggestion +# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' +dotnet_diagnostic.CA1844.severity = suggestion +# CA1845: Use span-based 'string.Concat' +dotnet_diagnostic.CA1845.severity = suggestion +# CA1846: Prefer AsSpan over Substring +dotnet_diagnostic.CA1846.severity = suggestion +# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters +dotnet_diagnostic.CA1847.severity = suggestion +# CA1852: Seal internal types +dotnet_diagnostic.CA1852.severity = suggestion +# CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method +dotnet_diagnostic.CA1854.severity = suggestion +# CA1855: Prefer 'Clear' over 'Fill' +dotnet_diagnostic.CA1855.severity = suggestion +# CA1856: Incorrect usage of ConstantExpected attribute +dotnet_diagnostic.CA1856.severity = suggestion +# CA1857: A constant is expected for the parameter +dotnet_diagnostic.CA1857.severity = suggestion +# CA1858: Use 'StartsWith' instead of 'IndexOf' +dotnet_diagnostic.CA1858.severity = suggestion +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = suggestion +# CA2008: Do not create tasks without passing a TaskScheduler +dotnet_diagnostic.CA2008.severity = suggestion +# CA2012: Use ValueTask correctly +dotnet_diagnostic.CA2012.severity = suggestion +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = suggestion +# CA2249: Use string.Contains instead of string.IndexOf to improve readability. +dotnet_diagnostic.CA2249.severity = suggestion +# IDE0005: Remove unnecessary usings +dotnet_diagnostic.IDE0005.severity = suggestion +# IDE0020: Use pattern matching to avoid is check followed by a cast (with variable) +dotnet_diagnostic.IDE0020.severity = suggestion +# IDE0029: Use coalesce expression (non-nullable types) +dotnet_diagnostic.IDE0029.severity = suggestion +# IDE0030: Use coalesce expression (nullable types) +dotnet_diagnostic.IDE0030.severity = suggestion +# IDE0031: Use null propagation +dotnet_diagnostic.IDE0031.severity = suggestion +# IDE0038: Use pattern matching to avoid is check followed by a cast (without variable) +dotnet_diagnostic.IDE0038.severity = suggestion +# IDE0044: Make field readonly +dotnet_diagnostic.IDE0044.severity = suggestion +# IDE0051: Remove unused private members +dotnet_diagnostic.IDE0051.severity = suggestion +# IDE0059: Unnecessary assignment to a value +dotnet_diagnostic.IDE0059.severity = suggestion +# IDE0060: Remove unused parameters +dotnet_diagnostic.IDE0060.severity = suggestion +# IDE0062: Make local function static +dotnet_diagnostic.IDE0062.severity = suggestion +# IDE0200: Lambda expression can be removed +dotnet_diagnostic.IDE0200.severity = suggestion + +# CA2016: Forward the 'CancellationToken' parameter to methods that take one +dotnet_diagnostic.CA2016.severity = suggestion + +# Defaults for content in the shared src/ and shared runtime dir + +[{**/Shared/runtime/**.{cs,vb},src/Shared/test/Shared.Tests/runtime/**.{cs,vb},**/microsoft.extensions.hostfactoryresolver.sources/**.{cs,vb}}] +# CA1822: Make member static +dotnet_diagnostic.CA1822.severity = silent +# IDE0011: Use braces +dotnet_diagnostic.IDE0011.severity = silent +# IDE0055: Fix formatting +dotnet_diagnostic.IDE0055.severity = silent +# IDE0060: Remove unused parameters +dotnet_diagnostic.IDE0060.severity = silent +# IDE0062: Make local function static +dotnet_diagnostic.IDE0062.severity = silent +# IDE0161: Convert to file-scoped namespace +dotnet_diagnostic.IDE0161.severity = silent + +[{**/Shared/**.cs,**/microsoft.extensions.hostfactoryresolver.sources/**.{cs,vb}}] +# IDE0005: Remove unused usings. Ignore for shared src files since imports for those depend on the projects in which they are included. +dotnet_diagnostic.IDE0005.severity = silent \ No newline at end of file diff --git a/src/Imageflow/Bindings/ImageInfo.cs b/src/Imageflow/Bindings/ImageInfo.cs index 45bd4c7..42bd872 100644 --- a/src/Imageflow/Bindings/ImageInfo.cs +++ b/src/Imageflow/Bindings/ImageInfo.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; + using Imageflow.Fluent; namespace Imageflow.Bindings @@ -18,27 +19,27 @@ private ImageInfo(JsonNode imageInfo) ImageWidth = obj.TryGetPropertyValue("image_width", out var imageWidthValue) ? imageWidthValue?.GetValue() ?? throw new ImageflowAssertionFailed(widthMsg) : throw new ImageflowAssertionFailed(widthMsg); - + const string heightMsg = "Imageflow get_image_info responded with null image_info.image_height"; ImageHeight = obj.TryGetPropertyValue("image_height", out var imageHeightValue) ? imageHeightValue?.GetValue() ?? throw new ImageflowAssertionFailed(heightMsg) : throw new ImageflowAssertionFailed(heightMsg); - + const string mimeMsg = "Imageflow get_image_info responded with null image_info.preferred_mime_type"; PreferredMimeType = obj.TryGetPropertyValue("preferred_mime_type", out var preferredMimeTypeValue) ? preferredMimeTypeValue?.GetValue() ?? throw new ImageflowAssertionFailed(mimeMsg) : throw new ImageflowAssertionFailed(mimeMsg); - + const string extMsg = "Imageflow get_image_info responded with null image_info.preferred_extension"; PreferredExtension = obj.TryGetPropertyValue("preferred_extension", out var preferredExtensionValue) ? preferredExtensionValue?.GetValue() ?? throw new ImageflowAssertionFailed(extMsg) : throw new ImageflowAssertionFailed(extMsg); - + const string frameMsg = "Imageflow get_image_info responded with null image_info.frame_decodes_into"; FrameDecodesInto = obj.TryGetPropertyValue("frame_decodes_into", out var frameDecodesIntoValue) ? PixelFormatParser.Parse(frameDecodesIntoValue?.GetValue() ?? throw new ImageflowAssertionFailed(frameMsg)) : throw new ImageflowAssertionFailed(frameMsg); - + } internal static ImageInfo FromDynamic(JsonNode imageInfo) { diff --git a/src/Imageflow/Bindings/ImageflowAssertionFailed.cs b/src/Imageflow/Bindings/ImageflowAssertionFailed.cs index bb69209..6815ae4 100644 --- a/src/Imageflow/Bindings/ImageflowAssertionFailed.cs +++ b/src/Imageflow/Bindings/ImageflowAssertionFailed.cs @@ -1,4 +1,4 @@ -namespace Imageflow.Bindings +namespace Imageflow.Bindings { /// /// diff --git a/src/Imageflow/Bindings/ImageflowException.cs b/src/Imageflow/Bindings/ImageflowException.cs index 180f142..dc421b2 100644 --- a/src/Imageflow/Bindings/ImageflowException.cs +++ b/src/Imageflow/Bindings/ImageflowException.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using System.Text; namespace Imageflow.Bindings @@ -9,7 +9,7 @@ public class ImageflowException : Exception internal ImageflowException(string message) : base(message) { - + } private enum ErrorFetchResult @@ -32,14 +32,14 @@ private static ErrorFetchResult TryGetErrorString(JobContextHandle c, ulong buff } var buffer = new byte[bufferSize]; var pinned = GCHandle.Alloc(buffer, GCHandleType.Pinned); - + try { var everythingWritten = NativeMethods.imageflow_context_error_write_to_buffer(c, - pinned.AddrOfPinnedObject(), new UIntPtr((ulong) buffer.LongLength), out var bytesWritten); + pinned.AddrOfPinnedObject(), new UIntPtr((ulong)buffer.LongLength), out var bytesWritten); - message = bytesWritten.ToUInt64() > 0 - ? Encoding.UTF8.GetString(buffer, 0, (int)Math.Min(bytesWritten.ToUInt64(), bufferSize)) + message = bytesWritten.ToUInt64() > 0 + ? Encoding.UTF8.GetString(buffer, 0, (int)Math.Min(bytesWritten.ToUInt64(), bufferSize)) : ""; return everythingWritten ? ErrorFetchResult.Success : ErrorFetchResult.BufferTooSmall; @@ -49,7 +49,7 @@ private static ErrorFetchResult TryGetErrorString(JobContextHandle c, ulong buff pinned.Free(); } } - + internal static ImageflowException FromContext(JobContextHandle c, ulong defaultBufferSize = 2048, string? additionalInfo = null) { for (var bufferSize = defaultBufferSize; bufferSize < MaxBufferSize; bufferSize *= 2) diff --git a/src/Imageflow/Bindings/ImageflowMethods.cs b/src/Imageflow/Bindings/ImageflowMethods.cs index 9ccaf2d..99192bb 100644 --- a/src/Imageflow/Bindings/ImageflowMethods.cs +++ b/src/Imageflow/Bindings/ImageflowMethods.cs @@ -1,8 +1,8 @@ -namespace Imageflow.Bindings; +namespace Imageflow.Bindings; internal static class ImageflowMethods { internal const string Execute = "v0.1/execute"; internal const string GetVersionInfo = "v1/get_version_info"; internal const string GetImageInfo = "v0.1/get_image_info"; -} \ No newline at end of file +} diff --git a/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs b/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs index 067432a..6217456 100644 --- a/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs +++ b/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs @@ -1,9 +1,10 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; + using Imageflow.Internal.Helpers; namespace Imageflow.Bindings { - + /// /// An UnmanagedMemoryStream that checks that the underlying Imageflow context isn't in a disposed or errored state /// @@ -15,8 +16,8 @@ public sealed class ImageflowUnmanagedReadStream : UnmanagedMemoryStream private readonly IAssertReady _underlying; private SafeHandle? _handle; private int _handleReferenced; - - internal unsafe ImageflowUnmanagedReadStream(IAssertReady underlying, SafeHandle handle, IntPtr buffer, UIntPtr length) : base( (byte*)buffer.ToPointer(), (long)length.ToUInt64(), (long)length.ToUInt64(), FileAccess.Read) + + internal unsafe ImageflowUnmanagedReadStream(IAssertReady underlying, SafeHandle handle, IntPtr buffer, UIntPtr length) : base((byte*)buffer.ToPointer(), (long)length.ToUInt64(), (long)length.ToUInt64(), FileAccess.Read) { _underlying = underlying; _handle = handle; @@ -30,7 +31,7 @@ internal unsafe ImageflowUnmanagedReadStream(IAssertReady underlying, SafeHandle } private void CheckSafe() - { + { _underlying.AssertReady(); } public override int Read(byte[] buffer, int offset, int count) diff --git a/src/Imageflow/Bindings/JobContext.cs b/src/Imageflow/Bindings/JobContext.cs index d966e11..75692ad 100644 --- a/src/Imageflow/Bindings/JobContext.cs +++ b/src/Imageflow/Bindings/JobContext.cs @@ -3,6 +3,7 @@ using System.Runtime.ConstrainedExecution; using System.Text.Json; using System.Text.Json.Nodes; + using Imageflow.Fluent; using Imageflow.Internal.Helpers; @@ -107,7 +108,7 @@ public IJsonResponseProvider SendMessage(string method, T message) AssertReady(); return InvokeInternal(method, ObsoleteSerializeToJson(message)); } - + [Obsolete("Use ExecuteJsonNode instead for AOT compatibility")] [RequiresUnreferencedCode("Use ExecuteJsonNode instead for AOT compatibility")] [RequiresDynamicCode("Use ExecuteJsonNode instead for AOT compatibility")] @@ -116,24 +117,26 @@ public IJsonResponseProvider Execute(T message) AssertReady(); return InvokeInternal(ImageflowMethods.Execute, ObsoleteSerializeToJson(message)); } - + [Obsolete("Use Invoke(string method, JsonNode message) instead.")] - public IJsonResponseProvider SendMessage(string method, JsonNode message){ + public IJsonResponseProvider SendMessage(string method, JsonNode message) + { AssertReady(); return InvokeInternal(method, message); } - + [Obsolete("Use .InvokeExecute(JsonNode message) instead")] - public IJsonResponseProvider ExecuteJsonNode(JsonNode message){ + public IJsonResponseProvider ExecuteJsonNode(JsonNode message) + { AssertReady(); return InvokeInternal(ImageflowMethods.Execute, message); } - + [Obsolete("Use .Invoke(string method, ReadOnlySpan utf8Json) instead")] public IJsonResponseProvider SendJsonBytes(string method, byte[] utf8Json) => InvokeInternal(method, utf8Json.AsSpan()); - + public ImageInfo GetImageInfo(int ioId) { var node = InvokeAndParse(ImageflowMethods.GetImageInfo, new JsonObject() { { "io_id", ioId } }); @@ -222,7 +225,7 @@ public IJsonResponse Invoke(string method) using var response = InvokeInternal(method); return response.Parse(); } - + public IJsonResponse InvokeExecute(JsonNode message) { @@ -261,7 +264,7 @@ private ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTerminatedMe message.WriteTo(utf8JsonWriter); utf8JsonWriter.Flush(); return ms.TryGetBufferSliceAllWrittenData(out var buffer) ? - InvokeInternal(nullTerminatedMethod, buffer) : + InvokeInternal(nullTerminatedMethod, buffer) : InvokeInternal(nullTerminatedMethod, ms.ToArray()); #endif @@ -277,7 +280,7 @@ private ImageflowJsonResponse InvokeInternal(string method, JsonNode message) } return InvokeInternal(nullTerminatedBytes, message); } - + private ImageflowJsonResponse InvokeInternal(string method, ReadOnlySpan utf8Json) { AssertReady(); @@ -288,7 +291,7 @@ private ImageflowJsonResponse InvokeInternal(string method, ReadOnlySpan u } return InvokeInternal(nullTerminatedBytes, utf8Json); } - + private unsafe ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTerminatedMethod, ReadOnlySpan utf8Json) { if (utf8Json.Length < 0) throw new ArgumentException("utf8Json cannot be empty", nameof(utf8Json)); @@ -300,22 +303,22 @@ private unsafe ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTermi { AssertReady(); var ptr = NativeMethods.imageflow_context_send_json(Handle, new IntPtr(methodPtr), new IntPtr(jsonPtr), - new UIntPtr((ulong) utf8Json.Length)); + new UIntPtr((ulong)utf8Json.Length)); // check HasError, throw exception with our input JSON too if (HasError) throw ImageflowException.FromContext(Handle, 2048, "JSON:\n" + TextHelpers.Utf8ToString(utf8Json)); - + AssertReady(); return new ImageflowJsonResponse(new JsonResponseHandle(_handle, ptr)); } } } - - + + public void AssertReady() { - if (!_handle.IsValid) throw new ObjectDisposedException("Imageflow JobContext"); + if (!_handle.IsValid) throw new ObjectDisposedException("Imageflow JobContext"); if (HasError) throw ImageflowException.FromContext(Handle); } @@ -325,10 +328,10 @@ public IJsonResponseProvider ExecuteImageResizer4CommandString(int inputId, int AssertReady(); return ExecuteImageResizer4CommandStringInternal(inputId, outputId, commands); } - internal ImageflowJsonResponse ExecuteImageResizer4CommandStringInternal( int inputId, int outputId, string commands) + internal ImageflowJsonResponse ExecuteImageResizer4CommandStringInternal(int inputId, int outputId, string commands) { - // var message = new + // var message = new // { // framewise = new // { @@ -366,10 +369,10 @@ internal ImageflowJsonResponse ExecuteImageResizer4CommandStringInternal( int in }} }} }; - + return InvokeInternal(ImageflowMethods.Execute, message); } - + /// /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. /// @@ -406,7 +409,7 @@ public void AddInputBytes(int ioId, byte[] buffer, long offset, long count) if (count > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(count), " count must be less than or equal to int.MaxValue"); if (offset < 0 || offset > buffer.LongLength - 1) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be within array bounds"); if (count < 0 || offset + count > buffer.LongLength) throw new ArgumentOutOfRangeException(nameof(count), count, "offset + count must be within array bounds. count cannot be negative"); - + AddInputBytes(ioId, buffer.AsSpan((int)offset, (int)count)); } /// @@ -420,8 +423,8 @@ public void AddInputBytes(int ioId, ReadOnlySpan data) { AssertReady(); if (ContainsIoId(ioId)) throw new ArgumentException($"ioId {ioId} already in use", nameof(ioId)); - - var length = (ulong) data.Length; + + var length = (ulong)data.Length; unsafe { fixed (byte* ptr = data) @@ -474,22 +477,22 @@ public void AddInputBytesPinned(int ioId, ArraySegment buffer) public void AddInputBytesPinned(int ioId, byte[] buffer, long offset, long count) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - if (offset > int.MaxValue) + if (offset > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(offset), "offset must be less than or equal to int.MaxValue"); if (count > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(count), " count must be less than or equal to int.MaxValue"); - + if (offset < 0 || offset > buffer.LongLength - 1) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be within array bounds"); if (count < 0 || offset + count > buffer.LongLength) throw new ArgumentOutOfRangeException(nameof(count), count, "offset + count must be within array bounds. count cannot be negative"); - - + + var rom = new ReadOnlyMemory(buffer, (int)offset, (int)count); AddInputBytesPinned(ioId, rom, MemoryLifetimePromise.MemoryIsOwnedByRuntime); } - + /// /// Pines the given Memory and gives Imageflow a pointer to it. You must promise that the /// memory will remain valid until after the JobContext is disposed. @@ -505,7 +508,7 @@ public unsafe void AddInputBytesPinned(int ioId, ReadOnlyMemory data, Memo throw new ArgumentException("callerPromise cannot be MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource", nameof(callerPromise)); AssertReady(); if (ContainsIoId(ioId)) throw new ArgumentException($"ioId {ioId} already in use", nameof(ioId)); - + var pinned = data.Pin(); try { @@ -521,14 +524,15 @@ public unsafe void AddInputBytesPinned(int ioId, ReadOnlyMemory data, Memo } _ioSet.Add(ioId, IoKind.InputBuffer); - }catch + } + catch { _pinnedMemory?.Remove(pinned); pinned.Dispose(); - throw; + throw; } } - + public void AddOutputBuffer(int ioId) { @@ -543,7 +547,7 @@ public void AddOutputBuffer(int ioId) } public bool ContainsIoId(int ioId) => _ioSet.ContainsKey(ioId); - + /// /// Will raise an unrecoverable exception if this is not an output buffer. /// Stream is not valid after the JobContext is disposed @@ -565,7 +569,7 @@ public Stream GetOutputBuffer(int ioId) } return new ImageflowUnmanagedReadStream(this, this._handle, buffer, bufferSize); } - + /// /// The memory remains valid only until the JobContext is disposed. /// @@ -573,7 +577,8 @@ public Stream GetOutputBuffer(int ioId) /// /// /// - internal unsafe Span BorrowOutputBuffer(int ioId){ + internal unsafe Span BorrowOutputBuffer(int ioId) + { if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) { throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); @@ -587,7 +592,7 @@ internal unsafe Span BorrowOutputBuffer(int ioId){ } return new Span((void*)buffer, (int)bufferSize); } - + /// /// Returns an IMemoryOwner<byte> that will keep the Imageflow Job in memory until both it and the JobContext are disposed. /// The memory should be treated as read-only. @@ -596,7 +601,8 @@ internal unsafe Span BorrowOutputBuffer(int ioId){ /// /// /// - internal IMemoryOwner BorrowOutputBufferMemoryAndAddReference(int ioId){ + internal IMemoryOwner BorrowOutputBufferMemoryAndAddReference(int ioId) + { if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) { throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); @@ -610,7 +616,7 @@ internal IMemoryOwner BorrowOutputBufferMemoryAndAddReference(int ioId){ } return SafeHandleMemoryManager.BorrowFromHandle(_handle, buffer, (uint)bufferSize); } - + private int _refCount; internal void AddRef() { @@ -631,7 +637,7 @@ public void Dispose() { throw new InvalidOperationException("Cannot dispose a JobContext that is still in use. "); } - + // Do not allocate or throw exceptions unless (disposing) Exception? e = null; try @@ -641,7 +647,7 @@ public void Dispose() finally { UnpinAll(); - + //Dispose all managed data held for context lifetime if (_toDispose != null) { @@ -651,9 +657,9 @@ public void Dispose() } GC.SuppressFinalize(this); if (e != null) throw e; - } + } } - + private void UnpinAll() { //Unpin @@ -674,6 +680,6 @@ private void UnpinAll() // _handle specifically handles it's own disposal and finalizer UnpinAll(); } - + } } diff --git a/src/Imageflow/Bindings/JobContextHandle.cs b/src/Imageflow/Bindings/JobContextHandle.cs index 66da70a..dd7078c 100644 --- a/src/Imageflow/Bindings/JobContextHandle.cs +++ b/src/Imageflow/Bindings/JobContextHandle.cs @@ -1,10 +1,12 @@ using System.Runtime.ConstrainedExecution; + using Imageflow.Internal.Helpers; + using Microsoft.Win32.SafeHandles; namespace Imageflow.Bindings { - + /// /// The handle is ready even if there is an error condition stored in the context. /// diff --git a/src/Imageflow/Bindings/JsonResponse.cs b/src/Imageflow/Bindings/JsonResponse.cs index ce357fd..c5b9f2e 100644 --- a/src/Imageflow/Bindings/JsonResponse.cs +++ b/src/Imageflow/Bindings/JsonResponse.cs @@ -1,7 +1,9 @@ -using System.Buffers; +using System.Buffers; using System.Text; using System.Text.Json.Nodes; + using Imageflow.Internal.Helpers; + using Utf8JsonReader = System.Text.Json.Utf8JsonReader; namespace Imageflow.Bindings @@ -13,14 +15,14 @@ public interface IJsonResponseProvider : IDisposable Stream GetStream(); } - public interface IJsonResponse: IDisposable + public interface IJsonResponse : IDisposable { public int ImageflowErrorCode { get; } public string CopyString(); public JsonNode? Parse(); public byte[] CopyBytes(); } - + internal interface IJsonResponseSpanProvider : IDisposable { /// @@ -37,7 +39,7 @@ internal MemoryJsonResponse(int statusCode, ReadOnlyMemory memory) ImageflowErrorCode = statusCode; _memory = memory; } - + private readonly ReadOnlyMemory _memory; public int ImageflowErrorCode { get; } @@ -46,28 +48,28 @@ public string CopyString() { return _memory.Span.Utf8ToString(); } - + public JsonNode? Parse() { return _memory.Span.ParseJsonNode(); } - + public byte[] CopyBytes() { return _memory.ToArray(); } - + public ReadOnlySpan BorrowBytes() { return _memory.Span; } - + public void Dispose() { // no-op } } - + /// /// Readable even if the JobContext is in an error state. /// @@ -120,7 +122,7 @@ public Stream GetStream() Read(out var _, out var utf8Buffer, out var bufferSize); return new ImageflowUnmanagedReadStream(this, _handle, utf8Buffer, bufferSize); } - + public unsafe ReadOnlySpan BorrowBytes() { Read(out var _, out var utf8Buffer, out var bufferSize); @@ -130,7 +132,7 @@ public unsafe ReadOnlySpan BorrowBytes() return new ReadOnlySpan((void*)utf8Buffer, (int)bufferSize); } - + public MemoryManager BorrowMemory() { Read(out var _, out var utf8Buffer, out var bufferSize); @@ -145,14 +147,14 @@ public void Dispose() { _handle.Dispose(); } - - + + public int GetStatusCode() { Read(out var statusCode, out var _, out var _); return statusCode; } - + public int ImageflowErrorCode => _statusCode ??= GetStatusCode(); public string CopyString() { @@ -175,12 +177,12 @@ public static class IJsonResponseProviderExtensions { internal static JsonNode? ParseJsonNode(this ReadOnlySpan buffer) { - + var reader3 = new Utf8JsonReader(buffer); return JsonNode.Parse(ref reader3); } - + // [Obsolete("Use DeserializeJsonNode() instead")] // public static T? Deserialize(this IJsonResponseProvider p) where T : class // { @@ -202,7 +204,7 @@ public static class IJsonResponseProviderExtensions // using var reader = new StreamReader(p.GetStream(), Encoding.UTF8); // //return JsonSerializer.Create().Deserialize(new JsonTextReader(reader)); // } - + [Obsolete("Use IJsonResponse.Parse()? instead")] public static JsonNode? DeserializeJsonNode(this IJsonResponseProvider p) { @@ -219,7 +221,7 @@ public static class IJsonResponseProviderExtensions var reader2 = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); return JsonNode.Parse(ref reader2); } - + [Obsolete("IJsonResponseProvider is deprecated; use IJsonResponse instead")] public static string GetString(this IJsonResponseProvider p) diff --git a/src/Imageflow/Bindings/JsonResponseHandle.cs b/src/Imageflow/Bindings/JsonResponseHandle.cs index 58fbb5b..d179893 100644 --- a/src/Imageflow/Bindings/JsonResponseHandle.cs +++ b/src/Imageflow/Bindings/JsonResponseHandle.cs @@ -1,5 +1,7 @@ -using System.Runtime.ConstrainedExecution; +using System.Runtime.ConstrainedExecution; + using Imageflow.Internal.Helpers; + using Microsoft.Win32.SafeHandles; namespace Imageflow.Bindings @@ -15,7 +17,7 @@ public JsonResponseHandle(JobContextHandle parent, IntPtr ptr) { ParentContext = parent ?? throw new ArgumentNullException(nameof(parent)); SetHandle(ptr); - + var addRefSucceeded = false; parent.DangerousAddRef(ref addRefSucceeded); if (!addRefSucceeded) @@ -34,8 +36,8 @@ public void AssertReady() if (!ParentContext.IsValid) throw new ObjectDisposedException("Imageflow JobContextHandle"); if (!IsValid) throw new ObjectDisposedException("Imageflow JsonResponseHandle"); } - - + + #pragma warning disable SYSLIB0004 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] @@ -50,4 +52,4 @@ protected override bool ReleaseHandle() return true; } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Bindings/NativeLibraryLoading.cs b/src/Imageflow/Bindings/NativeLibraryLoading.cs index 41144da..68067a7 100644 --- a/src/Imageflow/Bindings/NativeLibraryLoading.cs +++ b/src/Imageflow/Bindings/NativeLibraryLoading.cs @@ -12,8 +12,8 @@ namespace Imageflow.Bindings internal class LoadLogger : ILibraryLoadLogger { internal string Verb = "loaded"; - internal string Filename = $"{ RuntimeFileLocator.SharedLibraryPrefix.Value}"; - + internal string Filename = $"{RuntimeFileLocator.SharedLibraryPrefix.Value}"; + internal Exception? FirstException; internal Exception? LastException; @@ -67,8 +67,8 @@ internal void RaiseException() string errorCode = e.LoadErrorCode.Value < 0 ? string.Format(CultureInfo.InvariantCulture, "0x{0:X8}", e.LoadErrorCode.Value) : e.LoadErrorCode.Value.ToString(CultureInfo.InvariantCulture); - - sb.AppendFormat("Error \"{0}\" ({1}) loading {2} from {3}", + + sb.AppendFormat("Error \"{0}\" ({1}) loading {2} from {3}", new Win32Exception(e.LoadErrorCode.Value).Message, errorCode, e.Basename, e.FullPath); @@ -76,7 +76,7 @@ internal void RaiseException() if (e.LoadErrorCode.Value == 193 && RuntimeFileLocator.PlatformRuntimePrefix.Value == "win") { - var installed = Environment.Is64BitProcess ? "32-bit (x86)" : "64-bit (x86_64)" ; + var installed = Environment.Is64BitProcess ? "32-bit (x86)" : "64-bit (x86_64)"; var needed = Environment.Is64BitProcess ? "64-bit (x86_64)" : "32-bit (x86)"; sb.AppendFormat("\n> You have installed a {0} copy of imageflow.dll but need the {1} version", @@ -86,10 +86,10 @@ internal void RaiseException() if (e.LoadErrorCode.Value == 126 && RuntimeFileLocator.PlatformRuntimePrefix.Value == "win") { - var crtLink = "https://aka.ms/vs/16/release/vc_redist." + var crtLink = "https://aka.ms/vs/16/release/vc_redist." + (Environment.Is64BitProcess ? "x64.exe" : "x86.exe"); - sb.AppendFormat("\n> You may need to install the C Runtime from {0}",crtLink); + sb.AppendFormat("\n> You may need to install the C Runtime from {0}", crtLink); } } else @@ -116,7 +116,7 @@ internal static class RuntimeFileLocator internal static readonly Lazy IsDotNetCore = new Lazy(() => true , LazyThreadSafetyMode.PublicationOnly); - #else +#else internal static readonly Lazy IsDotNetCore = new Lazy(() => typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.CodeBase.Contains("Microsoft.NETCore.App") , LazyThreadSafetyMode.PublicationOnly); @@ -177,7 +177,7 @@ internal static class RuntimeFileLocator return "dll"; } }, LazyThreadSafetyMode.PublicationOnly); - + /// /// The output subdirectory that NuGet .props/.targets should be copying unmanaged binaries to. /// If you're using .NET Core you don't need this. @@ -208,7 +208,7 @@ internal static class RuntimeFileLocator /// /// /// - private static IEnumerable> BaseFolders(IEnumerable? customSearchDirectories = null) + private static IEnumerable> BaseFolders(IEnumerable? customSearchDirectories = null) { // Prioritize user suggestions if (customSearchDirectories != null) @@ -218,7 +218,7 @@ private static IEnumerable> BaseFolders(IEnumerable? yield return Tuple.Create(true, d); } } - + // First look in AppDomain.CurrentDomain.RelativeSearchPath - if it is within the BaseDirectory if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath) && AppDomain.CurrentDomain.RelativeSearchPath.StartsWith(AppDomain.CurrentDomain.BaseDirectory)) @@ -230,24 +230,24 @@ private static IEnumerable> BaseFolders(IEnumerable? { yield return Tuple.Create(true, AppContext.BaseDirectory); } - + // Look in the base directory from which .NET looks for managed assemblies yield return Tuple.Create(true, AppDomain.CurrentDomain.BaseDirectory); - + //Issue #17 - Azure Functions 2.0 - https://github.com/imazen/imageflow-dotnet/issues/17 // If the BaseDirectory is /bin/, look one step outside of it. - if(AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).EndsWith("bin")) + if (AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).EndsWith("bin")) { //Look in the parent directory if we're in /bin/, but only look in ../runtimes/:rid:/native var dir = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory); if (dir != null) - yield return Tuple.Create(false, Path.Combine(dir.FullName, + yield return Tuple.Create(false, Path.Combine(dir.FullName, "runtimes", PlatformRuntimePrefix.Value + "-" + ArchitectureSubdir.Value, "native")); - + } - + string? assemblyLocation = null; - #if !NETCOREAPP && !NET5_0_OR_GREATER && !NET8_0_OR_GREATER +#if !NETCOREAPP && !NET5_0_OR_GREATER && !NET8_0_OR_GREATER try{ // Look in the folder that *this* assembly is located. assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); @@ -255,7 +255,7 @@ private static IEnumerable> BaseFolders(IEnumerable? } catch (NotImplementedException){ // ignored } - #endif +#endif if (!string.IsNullOrEmpty(assemblyLocation)) yield return Tuple.Create(true, assemblyLocation!); } @@ -275,15 +275,17 @@ internal static IEnumerable SearchPossibilitiesForFile(string filename, { // First try the simple arch subdir since that is where the nuget native packages unpack path = Path.Combine(directory, ArchitectureSubdir.Value, filename); - if (attemptedPaths.Add(path)){ + if (attemptedPaths.Add(path)) + { yield return path; } } // Try the folder itself path = Path.Combine(directory, filename); - - if (attemptedPaths.Add(path)){ + + if (attemptedPaths.Add(path)) + { yield return path; } @@ -292,7 +294,8 @@ internal static IEnumerable SearchPossibilitiesForFile(string filename, // Last try native runtimes directory in case this is happening in .NET Core path = Path.Combine(directory, "runtimes", PlatformRuntimePrefix.Value + "-" + ArchitectureSubdir.Value, "native", filename); - if (attemptedPaths.Add(path)){ + if (attemptedPaths.Add(path)) + { yield return path; } } @@ -311,21 +314,21 @@ internal static IEnumerable SearchPossibilitiesForFile(string filename, return SearchPossibilitiesForFile(filename, customSearchDirectories).FirstOrDefault(File.Exists); } } - + internal interface ILibraryLoadLogger { void NotifyAttempt(string basename, string? fullPath, bool fileExists, bool previouslyLoaded, int? loadErrorCode); } - - + + public static class ExecutableLocator { - + private static string GetFilenameWithoutDirectory(string basename) => RuntimeFileLocator.ExecutableExtension.Value.Length > 0 ? $"{basename}.{RuntimeFileLocator.ExecutableExtension.Value}" : basename; - - + + /// /// Raises an exception if the file couldn't be found /// @@ -342,7 +345,7 @@ private static string GetFilenameWithoutDirectory(string basename) => RuntimeFil logger.RaiseException(); return null; } - + private static readonly Lazy> ExecutablePathsByName = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); // Not yet implemented. @@ -388,11 +391,11 @@ internal static bool TryLoadByBasename(string basename, ILibraryLoadLogger log, return false; } } - - + + internal static class NativeLibraryLoader { - private static string GetFilenameWithoutDirectory(string basename) => $"{RuntimeFileLocator.SharedLibraryPrefix.Value}{basename}.{RuntimeFileLocator.SharedLibraryExtension.Value}"; + private static string GetFilenameWithoutDirectory(string basename) => $"{RuntimeFileLocator.SharedLibraryPrefix.Value}{basename}.{RuntimeFileLocator.SharedLibraryExtension.Value}"; /// /// Attempts to resolve DllNotFoundException and BadImageFormatExceptions @@ -420,10 +423,10 @@ internal static class NativeLibraryLoader { caughtException = b; } - + //Try loading var logger = new LoadLogger - { FirstException = caughtException, Filename = GetFilenameWithoutDirectory(basename) }; + { FirstException = caughtException, Filename = GetFilenameWithoutDirectory(basename) }; if (TryLoadByBasename(basename, logger, out var _, customSearchDirectories)) { try @@ -475,9 +478,9 @@ public static bool TryLoadByBasename(string basename, ILibraryLoadLogger log, ou if (success) LibraryHandlesByBasename.Value[basename] = handle; return success; } - } + } + - private static bool TryLoadByBasenameInternal(string basename, ILibraryLoadLogger log, out IntPtr handle, IEnumerable? customSearchDirectories = null) { var filename = GetFilenameWithoutDirectory(basename); @@ -500,13 +503,13 @@ private static bool TryLoadByBasenameInternal(string basename, ILibraryLoadLogge handle = IntPtr.Zero; return false; } - + private static bool LoadLibrary(string fullPath, out IntPtr handle, out int? errorCode) { handle = RuntimeFileLocator.IsUnix ? UnixLoadLibrary.Execute(fullPath) : WindowsLoadLibrary.Execute(fullPath); if (handle == IntPtr.Zero) { - errorCode = Marshal.GetLastWin32Error(); + errorCode = Marshal.GetLastWin32Error(); return false; } errorCode = null; @@ -544,5 +547,5 @@ public static IntPtr Execute(string fileName) } } - + } diff --git a/src/Imageflow/Bindings/NativeMethods.cs b/src/Imageflow/Bindings/NativeMethods.cs index 9532ca6..ba29c3c 100644 --- a/src/Imageflow/Bindings/NativeMethods.cs +++ b/src/Imageflow/Bindings/NativeMethods.cs @@ -14,7 +14,7 @@ public enum Lifetime /// OutlivesContext -> 1 OutlivesContext = 1, } - + // ReSharper disable once InconsistentNaming public const int ABI_MAJOR = 3; // ReSharper disable once InconsistentNaming @@ -114,7 +114,7 @@ public static extern IntPtr imageflow_context_memory_allocate(JobContextHandle c [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_add_input_buffer(JobContextHandle context, int ioId, IntPtr buffer, + public static extern bool imageflow_context_add_input_buffer(JobContextHandle context, int ioId, IntPtr buffer, UIntPtr bufferByteCount, Lifetime lifetime); [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] @@ -130,4 +130,3 @@ public static extern bool imageflow_context_get_output_buffer_by_id(JobContextHa } } - diff --git a/src/Imageflow/Bindings/VersionInfo.cs b/src/Imageflow/Bindings/VersionInfo.cs index a202382..b202ce3 100644 --- a/src/Imageflow/Bindings/VersionInfo.cs +++ b/src/Imageflow/Bindings/VersionInfo.cs @@ -21,7 +21,7 @@ private VersionInfo(JsonNode versionInfo) // { // BuildDate = new DateTimeOffset(dt); // } - + var obj = versionInfo.AsObject(); const string longVersionMsg = "Imageflow get_version_info responded with null version_info.long_version_string"; LongVersionString = obj.TryGetPropertyValue("long_version_string", out var longVersionValue) @@ -46,23 +46,23 @@ private VersionInfo(JsonNode versionInfo) GitDescribeAlways = obj.TryGetPropertyValue("git_describe_always", out var gitDescribeAlwaysValue) ? gitDescribeAlwaysValue?.GetValue() : null; - - - + + + } internal static VersionInfo FromNode(JsonNode versionInfo) { return new VersionInfo(versionInfo); } - + public string LongVersionString { get; private set; } public string LastGitCommit { get; private set; } - + /// /// Usually includes the last version tag, the number of commits since, and a shortened git hash /// public string? GitDescribeAlways { get; private set; } - + /// /// May be null if the current version was not a tagged release /// @@ -70,4 +70,4 @@ internal static VersionInfo FromNode(JsonNode versionInfo) public bool DirtyWorkingTree { get; private set; } public DateTimeOffset BuildDate { get; private set; } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/AnyColor.cs b/src/Imageflow/Fluent/AnyColor.cs index 4624184..622238d 100644 --- a/src/Imageflow/Fluent/AnyColor.cs +++ b/src/Imageflow/Fluent/AnyColor.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; + using Imageflow.Bindings; namespace Imageflow.Fluent @@ -32,22 +33,22 @@ public object ToImageflowDynamic() { switch (_kind) { - case ColorKind.Black: return new {black = (string?)null}; - case ColorKind.Transparent: return new {transparent = (string?)null }; - case ColorKind.Srgb: return new {srgb = new { hex = _srgb.ToHexUnprefixed()}}; + case ColorKind.Black: return new { black = (string?)null }; + case ColorKind.Transparent: return new { transparent = (string?)null }; + case ColorKind.Srgb: return new { srgb = new { hex = _srgb.ToHexUnprefixed() } }; default: throw new ImageflowAssertionFailed("default"); } } - + public JsonNode ToJsonNode() { switch (_kind) { - case ColorKind.Black: return new JsonObject(){{"black", (string?)null}}; - case ColorKind.Transparent: return new JsonObject(){{"transparent", (string?)null}}; - case ColorKind.Srgb: return new JsonObject(){{"srgb", new JsonObject(){{"hex", _srgb.ToHexUnprefixed()}}}}; + case ColorKind.Black: return new JsonObject() { { "black", (string?)null } }; + case ColorKind.Transparent: return new JsonObject() { { "transparent", (string?)null } }; + case ColorKind.Srgb: return new JsonObject() { { "srgb", new JsonObject() { { "hex", _srgb.ToHexUnprefixed() } } } }; default: throw new ImageflowAssertionFailed("default"); } } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/BufferedStreamSource.cs b/src/Imageflow/Fluent/BufferedStreamSource.cs index 6e5f589..d283cf0 100644 --- a/src/Imageflow/Fluent/BufferedStreamSource.cs +++ b/src/Imageflow/Fluent/BufferedStreamSource.cs @@ -1,4 +1,5 @@ -using Imageflow.Internal.Helpers; +using Imageflow.Internal.Helpers; + using Microsoft.IO; namespace Imageflow.Fluent; @@ -13,12 +14,12 @@ private BufferedStreamSource(Stream stream, bool disposeUnderlying, bool seekToS } var length = stream.CanSeek ? stream.Length : 0; if (length >= int.MaxValue) throw new OverflowException("Streams cannot exceed 2GB"); - + _underlying = stream; _disposeUnderlying = disposeUnderlying; _seekToStart = seekToStart; } - + private readonly bool _seekToStart; private Stream? _underlying; private readonly bool _disposeUnderlying; @@ -38,9 +39,9 @@ public void Dispose() _copy?.Dispose(); _copy = null; - + } - + private bool TryGetWrittenMemory( out ReadOnlyMemory memory) { @@ -84,10 +85,10 @@ public async ValueTask> BorrowReadOnlyMemoryAsync(Cancellat if (!TryGetWrittenMemory(out segment)) { throw new InvalidOperationException("Could not get RecyclableMemoryStream buffer; please report this bug to support@imazen.io"); - } + } return segment; } - + public ReadOnlyMemory BorrowReadOnlyMemory() { ObjectDisposedHelper.ThrowIf(_underlying == null, this); @@ -99,14 +100,14 @@ public ReadOnlyMemory BorrowReadOnlyMemory() { _underlying.Seek(0, SeekOrigin.Begin); } - + _copy = new RecyclableMemoryStream(Mgr, "BufferedStreamSource: IMemorySource", _underlying.CanSeek ? _underlying.Length : 0); _underlying.CopyTo(_copy); _copy.Seek(0, SeekOrigin.Begin); if (!TryGetWrittenMemory(out segment)) { throw new InvalidOperationException("Could not get RecyclableMemoryStream buffer; please report this bug to support@imazen.io"); - } + } return segment; } @@ -135,7 +136,7 @@ public static IAsyncMemorySource BorrowStreamRemainder(Stream stream) { return new BufferedStreamSource(stream, false, false); } - + /// /// The stream will be closed and disposed with the BufferedStreamSource. You must not close, dispose, or reuse the stream or its underlying streams/buffers until after the job and the owning objects are disposed. /// You must not close, dispose, or reuse the stream or its underlying streams/buffers until after the job and the owning objects are disposed.
@@ -148,7 +149,7 @@ public static IAsyncMemorySource UseEntireStreamAndDisposeWithSource(Stream stre { return new BufferedStreamSource(stream, true, true); } - + /// /// The stream will be closed and disposed with the BufferedStreamSource. /// You must not close, dispose, or reuse the stream or its underlying streams/buffers until after the job and the owning objects are disposed. @@ -160,4 +161,4 @@ public static IAsyncMemorySource UseStreamRemainderAndDisposeWithSource(Stream s { return new BufferedStreamSource(stream, true, false); } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/BuildDecodeResult.cs b/src/Imageflow/Fluent/BuildDecodeResult.cs index ee1917c..ad22061 100644 --- a/src/Imageflow/Fluent/BuildDecodeResult.cs +++ b/src/Imageflow/Fluent/BuildDecodeResult.cs @@ -3,11 +3,11 @@ namespace Imageflow.Fluent public class BuildDecodeResult { public string? PreferredMimeType { get; internal init; } - public string? PreferredExtension { get;internal init; } - public int IoId { get; internal init;} - public int Width { get; internal init;} - public int Height { get; internal init;} + public string? PreferredExtension { get; internal init; } + public int IoId { get; internal init; } + public int Width { get; internal init; } + public int Height { get; internal init; } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/BuildEncodeResult.cs b/src/Imageflow/Fluent/BuildEncodeResult.cs index fa0fb02..7749154 100644 --- a/src/Imageflow/Fluent/BuildEncodeResult.cs +++ b/src/Imageflow/Fluent/BuildEncodeResult.cs @@ -13,23 +13,23 @@ public record BuildEncodeResult // Height = height; // Destination = destination; // } - + internal BuildEncodeResult() { } // maps to "preferred_mime_type" in json public required string PreferredMimeType { get; init; } - + // maps to "preferred_extension" in json public required string PreferredExtension { get; init; } - public required int IoId { get; init;} + public required int IoId { get; init; } // maps to "w" in json - public required int Width { get; init;} + public required int Width { get; init; } // maps to "h" in json - public required int Height { get; init;} - - public required IOutputDestination Destination { get; init;} - + public required int Height { get; init; } + + public required IOutputDestination Destination { get; init; } + /// /// If this Destination is a BytesDestination, returns the ArraySegment - otherwise null /// Returns the byte segment for the given output ID (if that output is a BytesDestination) @@ -42,4 +42,4 @@ internal BuildEncodeResult() // PreferredExtension = er.preferred_extension, // PreferredMimeType = er.preferred_mime_type, // Destination = outputs[(int)er.io_id.Value] -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/BuildEndpoint.cs b/src/Imageflow/Fluent/BuildEndpoint.cs index 4283474..6100d75 100644 --- a/src/Imageflow/Fluent/BuildEndpoint.cs +++ b/src/Imageflow/Fluent/BuildEndpoint.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; namespace Imageflow.Fluent { @@ -9,11 +9,12 @@ namespace Imageflow.Fluent /// public class BuildEndpoint : BuildItemBase { - internal BuildEndpoint(ImageJob builder,JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) : base(builder, nodeData, inputNode, - canvasNode){} - + internal BuildEndpoint(ImageJob builder, JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) : base(builder, nodeData, inputNode, + canvasNode) + { } + public FinishJobBuilder Finish() => new FinishJobBuilder(Builder, default); - + [Obsolete("Use Finish().WithCancellationToken")] public FinishJobBuilder FinishWithToken(CancellationToken token) => new FinishJobBuilder(Builder, token); @@ -23,9 +24,9 @@ public FinishJobBuilder FinishWithTimeout(int milliseconds) using var tokenSource = new CancellationTokenSource(milliseconds); return FinishWithToken(tokenSource.Token); } - + } - - - + + + } diff --git a/src/Imageflow/Fluent/BuildItemBase.cs b/src/Imageflow/Fluent/BuildItemBase.cs index c7f47f3..80e67df 100644 --- a/src/Imageflow/Fluent/BuildItemBase.cs +++ b/src/Imageflow/Fluent/BuildItemBase.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; namespace Imageflow.Fluent { @@ -10,15 +10,15 @@ public class BuildItemBase internal ImageJob Builder { get; } internal BuildNode? Input { get; } internal BuildNode? Canvas { get; } - internal JsonNode? NodeData { get; } - internal long Uid { get; } + internal JsonNode? NodeData { get; } + internal long Uid { get; } public override bool Equals(object? obj) => Uid == (obj as BuildItemBase)?.Uid; - public override int GetHashCode() => (int) Uid; //We probably don't need to worry about more than 2 billion instances? - + public override int GetHashCode() => (int)Uid; //We probably don't need to worry about more than 2 billion instances? + private static long _next; private static long NextUid() => Interlocked.Increment(ref _next); internal bool IsEmpty => NodeData == null; - + protected BuildItemBase(ImageJob builder, JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) { Builder = builder; @@ -29,4 +29,4 @@ protected BuildItemBase(ImageJob builder, JsonNode nodeData, BuildNode? inputNod builder.AddNode(this); } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/BuildJobResult.cs b/src/Imageflow/Fluent/BuildJobResult.cs index 82895ba..3b39370 100644 --- a/src/Imageflow/Fluent/BuildJobResult.cs +++ b/src/Imageflow/Fluent/BuildJobResult.cs @@ -1,4 +1,4 @@ -using Imageflow.Bindings; +using Imageflow.Bindings; // ReSharper disable CheckNamespace namespace Imageflow.Fluent @@ -7,20 +7,21 @@ namespace Imageflow.Fluent public class BuildJobResult { - + [Obsolete("Use ImageJob.FinishAsync() to get a result; you should never create a BuildJobResult directly.")] - internal BuildJobResult(){ + internal BuildJobResult() + { _encodeResults = new Dictionary(); DecodeResults = new List(); EncodeResults = new List(); PerformanceDetails = new PerformanceDetails(null); } - private BuildJobResult(IReadOnlyCollection decodeResults, - IReadOnlyCollection encodeResults, + private BuildJobResult(IReadOnlyCollection decodeResults, + IReadOnlyCollection encodeResults, PerformanceDetails performanceDetails) { - _encodeResults = encodeResults.ToDictionary(r => r.IoId); + _encodeResults = encodeResults.ToDictionary(r => r.IoId); DecodeResults = decodeResults; EncodeResults = encodeResults; PerformanceDetails = performanceDetails; @@ -32,12 +33,12 @@ private BuildJobResult(IReadOnlyCollection decodeResults, /// A collection of the decoded image details produced by the job /// public IReadOnlyCollection DecodeResults { get; private set; } - + /// /// A collection of the encoded images produced by the job /// public IReadOnlyCollection EncodeResults { get; private set; } - + /// /// Details about the runtime performance of the job /// @@ -47,9 +48,9 @@ private BuildJobResult(IReadOnlyCollection decodeResults, /// The first encoded image produced by the job (with the lowest io_id) ///
public BuildEncodeResult? First => EncodeResults.FirstOrDefault(); - + public BuildEncodeResult this[int ioId] => _encodeResults[ioId]; - + /// /// Returns null if the encode result by the given io_id doesn't exist. /// @@ -61,8 +62,8 @@ internal static BuildJobResult From(IJsonResponse response, Dictionary() + bool? success = v.AsObject().TryGetPropertyValue("success", out var successValue) + ? successValue?.GetValue() : null; switch (success) { @@ -71,8 +72,8 @@ internal static BuildJobResult From(IJsonResponse response, Dictionary er.IoId).ToList(); - - var jobResult = data.AsObject().TryGetPropertyValue("job_result", out var jobResultValue) + + var jobResult = data.AsObject().TryGetPropertyValue("job_result", out var jobResultValue) ? jobResultValue : null; - + if (jobResult == null) data.AsObject().TryGetPropertyValue("build_result", out jobResultValue); if (jobResult == null) throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response (missing job_result or build_result) " + response.CopyString()); - + var encodeResults = new List(); if (!jobResult.AsObject().TryGetPropertyValue("encodes", out var encodeArray)) { @@ -119,7 +120,7 @@ internal static BuildJobResult From(IJsonResponse response, Dictionary(); foreach (var decode in (decodeArray?.AsArray() ?? [])) @@ -160,7 +161,7 @@ internal static BuildJobResult From(IJsonResponse response, Dictionary() ?? throw new ImageflowAssertionFailed(requiredMessage), @@ -171,18 +172,17 @@ internal static BuildJobResult From(IJsonResponse response, Dictionary er.IoId).ToList(), + return new BuildJobResult(decodeResults.OrderBy(er => er.IoId).ToList(), encodeResults.OrderBy(er => er.IoId).ToList(), perfDetails); } - - + + } - -} +} diff --git a/src/Imageflow/Fluent/BuildNode.cs b/src/Imageflow/Fluent/BuildNode.cs index 13175f5..8ee6825 100644 --- a/src/Imageflow/Fluent/BuildNode.cs +++ b/src/Imageflow/Fluent/BuildNode.cs @@ -3,11 +3,11 @@ namespace Imageflow.Fluent { - public class BuildNode :BuildItemBase + public class BuildNode : BuildItemBase { - internal static BuildNode StartNode(ImageJob graph, JsonNode data) + internal static BuildNode StartNode(ImageJob graph, JsonNode data) => new BuildNode(graph, data, null, null); - + /// /// Encode the result to the given destination (such as a BytesDestination or StreamDestination) /// @@ -21,7 +21,7 @@ public BuildEndpoint Encode(IOutputDestination destination, int ioId, IEncoderPr // return new BuildEndpoint(Builder, // new {encode = new {io_id = ioId, preset = encoderPreset?.ToImageflowDynamic()}}, this, null); return new BuildEndpoint(Builder, - new JsonObject() {["encode"] = new JsonObject() {["io_id"] = ioId, ["preset"] = encoderPreset.ToJsonNode()}}, this, null); + new JsonObject() { ["encode"] = new JsonObject() { ["io_id"] = ioId, ["preset"] = encoderPreset.ToJsonNode() } }, this, null); } /// /// Encode the result to the given destination (such as a BytesDestination or StreamDestination) @@ -30,26 +30,27 @@ public BuildEndpoint Encode(IOutputDestination destination, int ioId, IEncoderPr /// An encoder class, such as `new MozJpegEncoder()` /// public BuildEndpoint Encode(IOutputDestination destination, IEncoderPreset encoderPreset) => - Encode( destination, Builder.GenerateIoId(), encoderPreset); - + Encode(destination, Builder.GenerateIoId(), encoderPreset); + [Obsolete("Use Encode(IOutputDestination destination, int ioId, IEncoderPreset encoderPreset)")] public BuildEndpoint EncodeToBytes(int ioId, IEncoderPreset encoderPreset) => Encode(new BytesDestination(), ioId, encoderPreset); public BuildEndpoint EncodeToBytes(IEncoderPreset encoderPreset) => Encode(new BytesDestination(), encoderPreset); - + [Obsolete("Use Encode(IOutputDestination destination, int ioId, IEncoderPreset encoderPreset)")] public BuildEndpoint EncodeToStream(Stream stream, bool disposeStream, int ioId, IEncoderPreset encoderPreset) => Encode(new StreamDestination(stream, disposeStream), ioId, encoderPreset); public BuildEndpoint EncodeToStream(Stream stream, bool disposeStream, IEncoderPreset encoderPreset) => Encode(new StreamDestination(stream, disposeStream), encoderPreset); - - - private BuildNode(ImageJob builder,JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) : base(builder, nodeData, inputNode, - canvasNode){} + + + private BuildNode(ImageJob builder, JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) : base(builder, nodeData, inputNode, + canvasNode) + { } // private BuildNode To(object data) => new BuildNode(Builder, data, this, null); - + private BuildNode To(JsonNode data) => new BuildNode(Builder, data, this, null); // private BuildNode NodeWithCanvas(BuildNode canvas, object data) => new BuildNode(Builder, data, this, canvas); private BuildNode NodeWithCanvas(BuildNode canvas, JsonNode data) => new BuildNode(Builder, data, this, canvas); @@ -61,7 +62,7 @@ private BuildNode(ImageJob builder,JsonNode nodeData, BuildNode? inputNode, Buil /// /// /// - public BuildNode ConstrainWithin(uint? w, uint? h) + public BuildNode ConstrainWithin(uint? w, uint? h) { var jsonObject = new JsonObject { @@ -101,9 +102,9 @@ public BuildNode ConstrainWithin(uint? w, uint? h, ResampleHints? hints) /// /// /// - public BuildNode Constrain(Constraint constraint) + public BuildNode Constrain(Constraint constraint) //=> To(new { constrain = constraint.ToImageflowDynamic() }); - => To(new JsonObject {["constrain"] = constraint.ToJsonNode()}); + => To(new JsonObject { ["constrain"] = constraint.ToJsonNode() }); /// /// Distort the image to exactly the given dimensions. /// @@ -236,15 +237,15 @@ public BuildNode RegionPercent(float x1, float y1, float x2, float y2, AnyColor ["background_color"] = backgroundColor.ToJsonNode() } }); - - /// - /// Crops away whitespace of any color at the edges of the image. - /// - /// (1..255). determines how much noise/edges to tolerate before cropping - /// is finalized. 80 is a good starting point. - /// determines how much of the image to restore after cropping to - /// provide some padding. 0.5 (half a percent) is a good starting point. - /// + + /// + /// Crops away whitespace of any color at the edges of the image. + /// + /// (1..255). determines how much noise/edges to tolerate before cropping + /// is finalized. 80 is a good starting point. + /// determines how much of the image to restore after cropping to + /// provide some padding. 0.5 (half a percent) is a good starting point. + /// public BuildNode CropWhitespace(int threshold, float percentPadding) // => To(new // { @@ -291,40 +292,40 @@ public BuildNode ResizerCommands(string commandString) /// Flips the image vertically ///
/// - public BuildNode FlipVertical() + public BuildNode FlipVertical() // => To(new {flip_v = (string?)null}); - => To(new JsonObject {["flip_v"] = null}); + => To(new JsonObject { ["flip_v"] = null }); /// /// Flips the image horizontally /// /// public BuildNode FlipHorizontal() // => To(new {flip_h = (string?)null }); - => To(new JsonObject {["flip_h"] = null}); - + => To(new JsonObject { ["flip_h"] = null }); + /// /// Rotates the image 90 degrees clockwise. /// /// public BuildNode Rotate90() // => To(new {rotate_90 = (string?)null }); - => To(new JsonObject {["rotate_90"] = null}); + => To(new JsonObject { ["rotate_90"] = null }); /// /// Rotates the image 180 degrees clockwise. /// /// public BuildNode Rotate180() // To(new {rotate_180 = (string?)null }); - => To(new JsonObject {["rotate_180"] = null}); + => To(new JsonObject { ["rotate_180"] = null }); /// /// Rotates the image 270 degrees clockwise. (same as 90 degrees counterclockwise). /// /// public BuildNode Rotate270() //To(new {rotate_270 = (string?)null }); - => To(new JsonObject {["rotate_270"] = null}); + => To(new JsonObject { ["rotate_270"] = null }); /// /// Swaps the x and y dimensions of the image /// /// public BuildNode Transpose() //=> To(new {transpose = (string?)null }); - => To(new JsonObject {["transpose"] = null}); + => To(new JsonObject { ["transpose"] = null }); /// /// Allows you to generate multiple outputs by branching the graph @@ -342,7 +343,7 @@ public BuildNode Branch(Func f) { f(this); return this; - } + } /// /// Copies (not composes) the given rectangle from input to canvas. @@ -352,19 +353,19 @@ public BuildNode Branch(Func f) /// /// /// - public BuildNode CopyRectTo(BuildNode canvas, Rectangle area, Point to) - // => NodeWithCanvas(canvas, new - // { - // copy_rect_to_canvas = new - // { - // from_x = area.X, - // from_y = area.Y, - // w = area.Width, - // h = area.Height, - // x = to.X, - // y = to.Y - // } - // }); + public BuildNode CopyRectTo(BuildNode canvas, Rectangle area, Point to) + // => NodeWithCanvas(canvas, new + // { + // copy_rect_to_canvas = new + // { + // from_x = area.X, + // from_y = area.Y, + // w = area.Width, + // h = area.Height, + // x = to.X, + // y = to.Y + // } + // }); => NodeWithCanvas(canvas, new JsonObject { ["copy_rect_to_canvas"] = new JsonObject @@ -377,7 +378,7 @@ public BuildNode CopyRectTo(BuildNode canvas, Rectangle area, Point to) ["y"] = to.Y } }); - + /// /// Draws the input image to the given rectangle on the canvas, distorting if the aspect ratios differ. /// @@ -387,19 +388,19 @@ public BuildNode CopyRectTo(BuildNode canvas, Rectangle area, Point to) /// /// /// - public BuildNode DrawImageExactTo(BuildNode canvas, Rectangle to, ResampleHints? hints, CompositingMode? blend) - // => NodeWithCanvas(canvas, new - // { - // draw_image_exact = new - // { - // w = to.Width, - // h = to.Height, - // x = to.X, - // y = to.Y, - // blend = blend?.ToString()?.ToLowerInvariant(), - // hints = hints?.ToImageflowDynamic() - // } - // }); + public BuildNode DrawImageExactTo(BuildNode canvas, Rectangle to, ResampleHints? hints, CompositingMode? blend) + // => NodeWithCanvas(canvas, new + // { + // draw_image_exact = new + // { + // w = to.Width, + // h = to.Height, + // x = to.X, + // y = to.Y, + // blend = blend?.ToString()?.ToLowerInvariant(), + // hints = hints?.ToImageflowDynamic() + // } + // }); => NodeWithCanvas(canvas, new JsonObject { ["draw_image_exact"] = new JsonObject @@ -412,8 +413,8 @@ public BuildNode DrawImageExactTo(BuildNode canvas, Rectangle to, ResampleHints? ["hints"] = hints?.ToJsonNode() } }); - - + + /// /// Rounds all 4 corners using the given radius in pixels @@ -444,7 +445,7 @@ public BuildNode RoundAllImageCorners(int radiusPixels, AnyColor backgroundColor ["background_color"] = backgroundColor.ToJsonNode() } }); - + /// /// Rounds all 4 corners by a percentage. 100% would make a circle if the image was square. /// @@ -474,7 +475,7 @@ public BuildNode RoundAllImageCornersPercent(float radiusPercent, AnyColor backg ["background_color"] = backgroundColor.ToJsonNode() } }); - + /// /// Fills the given rectangle with the specified color /// @@ -507,7 +508,7 @@ public BuildNode FillRectangle(int x1, int y1, int x2, int y2, AnyColor color) ["color"] = color.ToJsonNode() } }); - + /// /// Adds padding of the given color by enlarging the canvas on the sides specified. /// @@ -560,7 +561,7 @@ public BuildNode WhiteBalanceSrgb(int threshold) ["threshold"] = threshold } }); - + /// /// Set the transparency of the image from 0 (transparent) to 1 (opaque) /// @@ -581,7 +582,7 @@ public BuildNode TransparencySrgb(float opacity) ["alpha"] = opacity } }); - + /// /// Adjust contrast between -1 and 1. /// @@ -602,7 +603,7 @@ public BuildNode ContrastSrgb(float amount) ["contrast"] = amount } }); - + /// /// Adjust brightness between -1 and 1. /// @@ -623,7 +624,7 @@ public BuildNode BrightnessSrgb(float amount) ["brightness"] = amount } }); - + /// /// Adjust saturation between -1 and 1. /// @@ -669,7 +670,7 @@ public BuildNode ColorFilterSrgb(ColorFilterSrgb filter) [Obsolete("Use Watermark(IMemorySource source, ..) instead. BufferedStreamSource and MemorySource are preferred over BytesSource and StreamSource.")] public BuildNode Watermark(IBytesSource source, WatermarkOptions watermark) => Watermark(source.ToMemorySource(), null, watermark); - + /// /// Draw a watermark from the given BufferedStreamSource or MemorySource /// @@ -711,7 +712,7 @@ public BuildNode Watermark(IAsyncMemorySource source, int? ioId, WatermarkOption { ["watermark"] = watermark.ToJsonNode(ioId.Value) }); - + } } diff --git a/src/Imageflow/Fluent/BytesDestination.cs b/src/Imageflow/Fluent/BytesDestination.cs index 1cb62e2..3e5b84a 100644 --- a/src/Imageflow/Fluent/BytesDestination.cs +++ b/src/Imageflow/Fluent/BytesDestination.cs @@ -1,4 +1,4 @@ -using Imageflow.Bindings; +using Imageflow.Bindings; using Imageflow.Internal.Helpers; namespace Imageflow.Fluent; @@ -8,7 +8,7 @@ public class BytesDestination : IOutputDestination, IOutputSink, IAsyncOutputSin private MemoryStream? _m; public void Dispose() { - + } public Task RequestCapacityAsync(int bytes) @@ -29,7 +29,7 @@ public Task FlushAsync(CancellationToken cancellationToken) if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.FlushAsync called before RequestCapacityAsync"); return _m.FlushAsync(cancellationToken); } - + public ArraySegment GetBytes() { if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.GetBytes called before RequestCapacityAsync"); @@ -71,4 +71,4 @@ public ValueTask FastFlushAsync(CancellationToken cancellationToken) Flush(); return new ValueTask(); } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/BytesSourceAdapter.cs b/src/Imageflow/Fluent/BytesSourceAdapter.cs index d8f1b32..6a21e9f 100644 --- a/src/Imageflow/Fluent/BytesSourceAdapter.cs +++ b/src/Imageflow/Fluent/BytesSourceAdapter.cs @@ -1,4 +1,4 @@ -namespace Imageflow.Fluent; +namespace Imageflow.Fluent; #pragma warning disable CS0618 // Type or member is obsolete public sealed class BytesSourceAdapter(IBytesSource source) : IAsyncMemorySource, IMemorySource @@ -14,9 +14,9 @@ public async ValueTask> BorrowReadOnlyMemoryAsync(Cancellat var bytes = await source.GetBytesAsync(cancellationToken).ConfigureAwait(false); return new ReadOnlyMemory(bytes.Array, bytes.Offset, bytes.Count); } - + public ReadOnlyMemory BorrowReadOnlyMemory() - { + { var bytes = source.GetBytesAsync(default).Result; return new ReadOnlyMemory(bytes.Array, bytes.Offset, bytes.Count); } @@ -24,4 +24,4 @@ public ReadOnlyMemory BorrowReadOnlyMemory() #pragma warning disable CS0618 // Type or member is obsolete public bool AsyncPreferred => source is not (BytesSource or StreamSource { AsyncPreferred: true }); #pragma warning restore CS0618 // Type or member is obsolete -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/Constraint.cs b/src/Imageflow/Fluent/Constraint.cs index d37b122..f73fa7e 100644 --- a/src/Imageflow/Fluent/Constraint.cs +++ b/src/Imageflow/Fluent/Constraint.cs @@ -1,10 +1,10 @@ -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; namespace Imageflow.Fluent { public class Constraint { - public Constraint(uint? w, uint? h) : this(ConstraintMode.Within, w, h){} + public Constraint(uint? w, uint? h) : this(ConstraintMode.Within, w, h) { } public Constraint(ConstraintMode mode, uint? w, uint? h, ResampleHints hints, AnyColor? canvasColor) { Mode = mode; @@ -30,14 +30,14 @@ public Constraint(ConstraintMode mode, uint? w, uint? h) public ResampleHints? Hints { get; set; } public AnyColor? CanvasColor { get; set; } - public ConstraintGravity? Gravity { get; set; } + public ConstraintGravity? Gravity { get; set; } - public Constraint SetConstraintMode(ConstraintMode mode) + public Constraint SetConstraintMode(ConstraintMode mode) { Mode = mode; return this; } - + public Constraint SetHints(ResampleHints hints) { Hints = hints; @@ -51,11 +51,11 @@ public Constraint SetCanvasColor(AnyColor? canvasColor) } public Constraint SetGravity(ConstraintGravity gravity) - { + { Gravity = gravity; return this; - } + } [Obsolete("Use ToJsonNode() instead")] public object ToImageflowDynamic() @@ -80,6 +80,6 @@ internal JsonNode ToJsonNode() if (CanvasColor != null) node.Add("canvas_color", CanvasColor?.ToJsonNode()); if (Gravity != null) node.Add("gravity", Gravity.ToJsonNode()); return node; - } + } } } diff --git a/src/Imageflow/Fluent/ConstraintGravity.cs b/src/Imageflow/Fluent/ConstraintGravity.cs index cd4446d..d6d1eb8 100644 --- a/src/Imageflow/Fluent/ConstraintGravity.cs +++ b/src/Imageflow/Fluent/ConstraintGravity.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; namespace Imageflow.Fluent { @@ -31,7 +31,8 @@ public object ToImageflowDynamic() { return new { - percentage = new { + percentage = new + { x = XPercent, y = YPercent } diff --git a/src/Imageflow/Fluent/DecodeCommands.cs b/src/Imageflow/Fluent/DecodeCommands.cs index a882559..df102b5 100644 --- a/src/Imageflow/Fluent/DecodeCommands.cs +++ b/src/Imageflow/Fluent/DecodeCommands.cs @@ -1,10 +1,10 @@ -using System.Drawing; +using System.Drawing; using System.Text.Json.Nodes; namespace Imageflow.Fluent { - + public class DecodeCommands { public Size? JpegDownscaleHint { get; set; } @@ -13,7 +13,7 @@ public class DecodeCommands [Obsolete("Use WebPDownscaleHint instead")] public Size? WebpDownscaleHint { get => WebPDownscaleHint; set => WebPDownscaleHint = value; } - + public Size? WebPDownscaleHint { get; set; } public bool DiscardColorProfile { get; set; } @@ -48,14 +48,16 @@ public DecodeCommands SetIgnoreColorProfileErrors(bool value) public object[] ToImageflowDynamic() { - object? downscale = JpegDownscaleHint.HasValue ? new { - jpeg_downscale_hints = new { + object? downscale = JpegDownscaleHint.HasValue ? new + { + jpeg_downscale_hints = new + { width = JpegDownscaleHint.Value.Width, - height = JpegDownscaleHint.Value.Height, + height = JpegDownscaleHint.Value.Height, scale_luma_spatially = JpegDownscalingMode == DecoderDownscalingMode.SpatialLumaScaling || JpegDownscalingMode == DecoderDownscalingMode.GammaCorrectSpatialLumaScaling, gamma_correct_for_srgb_during_spatial_luma_scaling = JpegDownscalingMode == DecoderDownscalingMode.GammaCorrectSpatialLumaScaling - } - }: null; + } + } : null; object? downscaleWebP = WebPDownscaleHint.HasValue ? new { @@ -66,11 +68,11 @@ public object[] ToImageflowDynamic() } } : null; - - - object? ignore = DiscardColorProfile ? new {discard_color_profile = (string?) null} : null; - object? ignoreErrors = IgnoreColorProfileErrors ? new {ignore_color_profile_errors = (string?) null} : null; - return new [] {downscale, ignore, ignoreErrors, downscaleWebP}.Where(obj => obj != null).Cast().ToArray(); + + + object? ignore = DiscardColorProfile ? new { discard_color_profile = (string?)null } : null; + object? ignoreErrors = IgnoreColorProfileErrors ? new { ignore_color_profile_errors = (string?)null } : null; + return new[] { downscale, ignore, ignoreErrors, downscaleWebP }.Where(obj => obj != null).Cast().ToArray(); } public JsonArray ToJsonNode() diff --git a/src/Imageflow/Fluent/Enumerations.cs b/src/Imageflow/Fluent/Enumerations.cs index b64c8ca..b20ba69 100644 --- a/src/Imageflow/Fluent/Enumerations.cs +++ b/src/Imageflow/Fluent/Enumerations.cs @@ -1,14 +1,15 @@ -using Imageflow.Bindings; +using Imageflow.Bindings; namespace Imageflow.Fluent { -// ReSharper disable InconsistentNaming - - - public enum PixelFormat { + // ReSharper disable InconsistentNaming + + + public enum PixelFormat + { Bgra_32 = 4, Bgr_32 = 70, -// Bgr_24 = 3, + // Bgr_24 = 3, // Gray_8 = 1, } internal static class PixelFormatParser @@ -19,14 +20,15 @@ public static PixelFormat Parse(string s) { "bgra_32" => PixelFormat.Bgra_32, "bgr_32" => PixelFormat.Bgr_32, -// "bgr_24" => PixelFormat.Bgr_24, + // "bgr_24" => PixelFormat.Bgr_24, // "gray_8" => PixelFormat.Gray_8, _ => throw new ImageflowAssertionFailed($"Unknown pixel format {s}") }; } } - - public enum ResampleWhen{ + + public enum ResampleWhen + { Size_Differs, Size_Differs_Or_Sharpening_Requested, Always @@ -41,11 +43,13 @@ public enum SharpenWhen } // ReSharper enable InconsistentNaming - public enum ScalingFloatspace { + public enum ScalingFloatspace + { Srgb, Linear } - public enum InterpolationFilter { + public enum InterpolationFilter + { Robidoux_Fast = 1, Robidoux = 2, Robidoux_Sharp = 3, @@ -59,7 +63,7 @@ public enum InterpolationFilter { Cubic_Sharp = 12, Catmull_Rom = 13, Mitchell = 14, - + Cubic_B_Spline = 15, Hermite = 16, Jinc = 17, @@ -68,7 +72,7 @@ public enum InterpolationFilter { Box = 24, Fastest = 27, - + N_Cubic = 29, N_Cubic_Sharp = 30, } @@ -81,7 +85,8 @@ public enum ColorKind Srgb, } - public enum PngBitDepth { + public enum PngBitDepth + { Png_32, Png_24, } @@ -128,8 +133,8 @@ public enum ConstraintMode /// If only one dimension is specified, behaves like `fit`. Fit_Pad, } - - + + public enum WatermarkConstraintMode { /// Distort the image to exactly the given dimensions. @@ -158,7 +163,7 @@ public enum WatermarkAlign /// Image } - + /// /// What quality level to use when downscaling the jpeg block-wise /// @@ -172,7 +177,7 @@ public enum DecoderDownscalingMode /// Use the fastest method /// Fastest = 1, - + /// /// A slower (but more accurate) scaling method is employed; the DCT blocks are fully decoded, then a true re-sampling kernel is applied. /// diff --git a/src/Imageflow/Fluent/FinishJobBuilder.cs b/src/Imageflow/Fluent/FinishJobBuilder.cs index 49f4428..37ad923 100644 --- a/src/Imageflow/Fluent/FinishJobBuilder.cs +++ b/src/Imageflow/Fluent/FinishJobBuilder.cs @@ -10,12 +10,12 @@ public class FinishJobBuilder private CancellationTokenSource? _tokenSource; private SecurityOptions? _security; - internal FinishJobBuilder(ImageJob imageJob, CancellationToken cancellationToken) + internal FinishJobBuilder(ImageJob imageJob, CancellationToken cancellationToken) { _builder = imageJob; _token = cancellationToken; } - + public FinishJobBuilder WithSecurityOptions(SecurityOptions securityOptions) { _security = securityOptions; @@ -69,10 +69,10 @@ public FinishJobBuilder SetCancellationTimeout(int milliseconds) return WithCancellationToken(_tokenSource.Token); } - public Task InProcessAsync() => _builder.FinishAsync(new JobExecutionOptions(),_security, _token); + public Task InProcessAsync() => _builder.FinishAsync(new JobExecutionOptions(), _security, _token); public Task InSubprocessAsync(string? imageflowToolPath = null, long? outputBufferCapacity = null) => - _builder.FinishInSubprocessAsync(_security, imageflowToolPath, outputBufferCapacity, _token); + _builder.FinishInSubprocessAsync(_security, imageflowToolPath, outputBufferCapacity, _token); /// /// Returns a prepared job that can be executed with `imageflow_tool --json [job.JsonPath]`. Supporting input/output files are also created. @@ -81,12 +81,12 @@ public Task InSubprocessAsync(string? imageflowToolPath = null, /// public Task WriteJsonJobAndInputs(bool deleteFilesOnDispose) => _builder.WriteJsonJobAndInputs(_token, _security, deleteFilesOnDispose); - + internal string ToJsonDebug(SecurityOptions? securityOptions = null) => _builder.ToJsonDebug(securityOptions); - + public async Task InProcessAndDisposeAsync() { - BuildJobResult r; + BuildJobResult r; try { r = await InProcessAsync(); @@ -98,8 +98,8 @@ public async Task InProcessAndDisposeAsync() return r; } - + } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/FrameSizeLimit.cs b/src/Imageflow/Fluent/FrameSizeLimit.cs index ce753b3..911e99c 100644 --- a/src/Imageflow/Fluent/FrameSizeLimit.cs +++ b/src/Imageflow/Fluent/FrameSizeLimit.cs @@ -5,29 +5,29 @@ namespace Imageflow.Fluent public readonly struct FrameSizeLimit(uint maxWidth, uint maxHeight, float maxMegapixels) { public uint MaxWidth { get; } = maxWidth; - public uint MaxHeight { get; } = maxHeight; - public float MaxMegapixels { get; } = maxMegapixels; + public uint MaxHeight { get; } = maxHeight; + public float MaxMegapixels { get; } = maxMegapixels; - internal object ToImageflowDynamic() - { - return new - { - w = MaxWidth, - h = MaxHeight, - megapixels = MaxMegapixels - }; - } + internal object ToImageflowDynamic() + { + return new + { + w = MaxWidth, + h = MaxHeight, + megapixels = MaxMegapixels + }; + } - internal JsonNode ToJsonNode() - { - var node = new JsonObject + internal JsonNode ToJsonNode() + { + var node = new JsonObject { { "w", MaxWidth }, { "h", MaxHeight }, { "megapixels", MaxMegapixels } }; - return node; - - } + return node; + + } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/IAsyncMemorySource.cs b/src/Imageflow/Fluent/IAsyncMemorySource.cs index c355243..38e4255 100644 --- a/src/Imageflow/Fluent/IAsyncMemorySource.cs +++ b/src/Imageflow/Fluent/IAsyncMemorySource.cs @@ -1,6 +1,6 @@ -namespace Imageflow.Fluent; +namespace Imageflow.Fluent; -public interface IAsyncMemorySource: IDisposable +public interface IAsyncMemorySource : IDisposable { /// /// Implementations should return a reference to a byte array that (at least until the IMemorySource implementation is disposed) will remain valid, immutable, and pinnable. @@ -9,16 +9,16 @@ public interface IAsyncMemorySource: IDisposable ValueTask> BorrowReadOnlyMemoryAsync(CancellationToken cancellationToken); } -public interface IMemorySource: IDisposable +public interface IMemorySource : IDisposable { /// /// Implementations should return a reference to a byte array that (at least until the IMemorySource implementation is disposed) will remain valid, immutable, and pinnable. /// /// ReadOnlyMemory BorrowReadOnlyMemory(); - + /// /// If true, implementations should prefer to use async methods over sync methods. /// bool AsyncPreferred { get; } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/IBytesSource.cs b/src/Imageflow/Fluent/IBytesSource.cs index 06160af..923a08e 100644 --- a/src/Imageflow/Fluent/IBytesSource.cs +++ b/src/Imageflow/Fluent/IBytesSource.cs @@ -1,7 +1,7 @@ -namespace Imageflow.Fluent +namespace Imageflow.Fluent { [Obsolete("Use IMemorySource instead")] - public interface IBytesSource: IDisposable + public interface IBytesSource : IDisposable { /// /// Return a reference to a byte array that (until the implementor is disposed) will (a) remain immutable, and (b) can be GC pinned. @@ -28,7 +28,7 @@ public BytesSource(ArraySegment bytes) { _bytes = bytes; } - + private readonly ArraySegment _bytes; public void Dispose() @@ -39,7 +39,7 @@ public Task> GetBytesAsync(CancellationToken cancellationToke { return Task.FromResult(_bytes); } - + public static implicit operator MemorySource(BytesSource source) { return new MemorySource(source._bytes); @@ -52,9 +52,8 @@ public static class BytesSourceExtensions public static IAsyncMemorySource ToMemorySource(this IBytesSource source) #pragma warning restore CS0618 // Type or member is obsolete { - return new BytesSourceAdapter(source); + return new BytesSourceAdapter(source); } } } - diff --git a/src/Imageflow/Fluent/IEncoderPreset.cs b/src/Imageflow/Fluent/IEncoderPreset.cs index 002b337..c9d15d3 100644 --- a/src/Imageflow/Fluent/IEncoderPreset.cs +++ b/src/Imageflow/Fluent/IEncoderPreset.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; namespace Imageflow.Fluent { @@ -11,18 +11,18 @@ public interface IEncoderPreset JsonNode ToJsonNode(); } - - + + /// /// Encodes the image as a .gif /// public class GifEncoder : IEncoderPreset { [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new {gif = (string?)null}; - - public JsonNode ToJsonNode() => new JsonObject(){ {"gif", (string?)null}}; - } + public object ToImageflowDynamic() => new { gif = (string?)null }; + + public JsonNode ToJsonNode() => new JsonObject() { { "gif", (string?)null } }; + } /// /// Use LodePngEncoder instead /// @@ -32,23 +32,23 @@ public class LibPngEncoder : IEncoderPreset public AnyColor? Matte { get; set; } public int? ZlibCompression { get; set; } public PngBitDepth? BitDepth { get; set; } - + [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new {libpng = new { depth = BitDepth?.ToString().ToLowerInvariant(), zlib_compression = ZlibCompression, matte = Matte?.ToImageflowDynamic()}}; - + public object ToImageflowDynamic() => new { libpng = new { depth = BitDepth?.ToString().ToLowerInvariant(), zlib_compression = ZlibCompression, matte = Matte?.ToImageflowDynamic() } }; + public JsonNode ToJsonNode() { var node = new JsonObject(); if (BitDepth != null) node.Add("depth", BitDepth?.ToString().ToLowerInvariant()); if (ZlibCompression != null) node.Add("zlib_compression", ZlibCompression); if (Matte != null) node.Add("matte", Matte?.ToJsonNode()); - return new JsonObject(){{"libpng", node}}; + return new JsonObject() { { "libpng", node } }; } - } - + } + public class PngQuantEncoder : IEncoderPreset { - public PngQuantEncoder(): this(null,null){} + public PngQuantEncoder() : this(null, null) { } /// /// Try to quantize the PNG first, falling back to lossless PNG if the minimumQuality value cannot be reached /// @@ -64,7 +64,7 @@ public PngQuantEncoder(int? quality, int? minimumQuality) /// public int? Quality { get; set; } - + /// /// (0..100) The minimum visual quality below which to revert to lossless encoding /// @@ -89,7 +89,7 @@ public PngQuantEncoder(int? quality, int? minimumQuality) public PngQuantEncoder SetQuality(int? quality) { Quality = quality; - return this; + return this; } /// /// (0..100) The minimum visual quality below which to revert to lossless encoding @@ -101,7 +101,7 @@ public PngQuantEncoder SetMinimumQuality(int? minimumQuality) MinimumQuality = minimumQuality; return this; } - + /// /// speed: 1..10 controls the speed/quality trade-off for encoding. /// @@ -112,7 +112,7 @@ public PngQuantEncoder SetSpeed(int? speed) Speed = speed; return this; } - + /// /// Not suggested; only saves 1-2% on file size but takes 10x CPU time. /// @@ -124,14 +124,17 @@ public PngQuantEncoder SetMaximumDeflate(bool value) return this; } [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new {pngquant = new + public object ToImageflowDynamic() => new { - quality = Quality, - minimum_quality = MinimumQuality, - speed = Speed, - maximum_deflate = MaximumDeflate - }}; - + pngquant = new + { + quality = Quality, + minimum_quality = MinimumQuality, + speed = Speed, + maximum_deflate = MaximumDeflate + } + }; + public JsonNode ToJsonNode() { var node = new JsonObject(); @@ -139,9 +142,9 @@ public JsonNode ToJsonNode() if (MinimumQuality != null) node.Add("minimum_quality", MinimumQuality); if (Speed != null) node.Add("speed", Speed); if (MaximumDeflate != null) node.Add("maximum_deflate", MaximumDeflate); - return new JsonObject(){{"pngquant", node}}; + return new JsonObject() { { "pngquant", node } }; } - } + } public class LodePngEncoder : IEncoderPreset @@ -162,15 +165,15 @@ public LodePngEncoder SetMaximumDeflate(bool value) return this; } [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new {lodepng = new { maximum_deflate = MaximumDeflate}}; - + public object ToImageflowDynamic() => new { lodepng = new { maximum_deflate = MaximumDeflate } }; + public JsonNode ToJsonNode() { var node = new JsonObject(); if (MaximumDeflate != null) node.Add("maximum_deflate", MaximumDeflate); - return new JsonObject(){{"lodepng", node}}; + return new JsonObject() { { "lodepng", node } }; } - } + } /// @@ -182,12 +185,12 @@ public class LibJpegTurboEncoder : IEncoderPreset public int? Quality { get; set; } public bool? Progressive { get; set; } public bool? OptimizeHuffmanCoding { get; set; } - + public AnyColor? Matte { get; set; } - + [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new {libjpegturbo = new { quality = Quality, progressive = Progressive, optimize_huffman_coding = OptimizeHuffmanCoding, matte = Matte?.ToImageflowDynamic()}}; - + public object ToImageflowDynamic() => new { libjpegturbo = new { quality = Quality, progressive = Progressive, optimize_huffman_coding = OptimizeHuffmanCoding, matte = Matte?.ToImageflowDynamic() } }; + public JsonNode ToJsonNode() { var node = new JsonObject(); @@ -195,7 +198,7 @@ public JsonNode ToJsonNode() if (Progressive != null) node.Add("progressive", Progressive); if (OptimizeHuffmanCoding != null) node.Add("optimize_huffman_coding", OptimizeHuffmanCoding); if (Matte != null) node.Add("matte", Matte?.ToJsonNode()); - return new JsonObject(){{"libjpegturbo", node}}; + return new JsonObject() { { "libjpegturbo", node } }; } } @@ -219,40 +222,40 @@ public MozJpegEncoder SetProgressive(bool progressive) Progressive = progressive; return this; } - + [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new {mozjpeg = new { quality = Quality, progressive = Progressive, matte = Matte?.ToImageflowDynamic()}}; - + public object ToImageflowDynamic() => new { mozjpeg = new { quality = Quality, progressive = Progressive, matte = Matte?.ToImageflowDynamic() } }; + public JsonNode ToJsonNode() { var node = new JsonObject(); if (Quality != null) node.Add("quality", Quality); if (Progressive != null) node.Add("progressive", Progressive); if (Matte != null) node.Add("matte", Matte?.ToJsonNode()); - return new JsonObject(){{"mozjpeg", node}}; + return new JsonObject() { { "mozjpeg", node } }; } } - + public class WebPLossyEncoder(float quality) : IEncoderPreset { public float? Quality { get; set; } = quality; [Obsolete("Use ToJsonNode() instead")] public object ToImageflowDynamic() => new { webplossy = new { quality = Quality } }; - + public JsonNode ToJsonNode() { var node = new JsonObject(); if (Quality != null) node.Add("quality", Quality); - return new JsonObject(){{"webplossy", node}}; + return new JsonObject() { { "webplossy", node } }; } } public class WebPLosslessEncoder : IEncoderPreset { [Obsolete("Use ToJsonNode() instead")] public object ToImageflowDynamic() => new { webplossless = (string?)null }; - - public JsonNode ToJsonNode() => new JsonObject(){ {"webplossless", (string?)null}}; + + public JsonNode ToJsonNode() => new JsonObject() { { "webplossless", (string?)null } }; } } diff --git a/src/Imageflow/Fluent/IOutputDestination.cs b/src/Imageflow/Fluent/IOutputDestination.cs index 3262990..13a4636 100644 --- a/src/Imageflow/Fluent/IOutputDestination.cs +++ b/src/Imageflow/Fluent/IOutputDestination.cs @@ -1,4 +1,4 @@ -namespace Imageflow.Fluent +namespace Imageflow.Fluent { public interface IOutputDestination : IDisposable { @@ -10,4 +10,4 @@ public interface IOutputDestination : IDisposable // ReSharper disable once InconsistentNaming -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/IOutputDestinationExtensions.cs b/src/Imageflow/Fluent/IOutputDestinationExtensions.cs index 7632fa1..cc8d6b6 100644 --- a/src/Imageflow/Fluent/IOutputDestinationExtensions.cs +++ b/src/Imageflow/Fluent/IOutputDestinationExtensions.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Runtime.InteropServices; namespace Imageflow.Fluent; @@ -20,7 +20,7 @@ internal static void AdaptiveWriteAll(this IOutputDestination dest, ReadOnlyMemo dest.FlushAsync(default).Wait(); } } - + internal static async ValueTask AdaptiveWriteAllAsync(this IOutputDestination dest, ReadOnlyMemory data, CancellationToken cancellationToken) { if (dest is IAsyncOutputSink sink) @@ -34,8 +34,8 @@ internal static async ValueTask AdaptiveWriteAllAsync(this IOutputDestination de await dest.AdaptedWriteAsync(data, cancellationToken); await dest.FlushAsync(cancellationToken); } - - + + internal static async ValueTask AdaptedWriteAsync(this IOutputDestination dest, ReadOnlyMemory data, CancellationToken cancellationToken) { if (MemoryMarshal.TryGetArray(data, out ArraySegment segment)) @@ -43,8 +43,8 @@ internal static async ValueTask AdaptedWriteAsync(this IOutputDestination dest, await dest.WriteAsync(segment, cancellationToken).ConfigureAwait(false); return; } - - var rent = ArrayPool.Shared.Rent(Math.Min(81920,data.Length)); + + var rent = ArrayPool.Shared.Rent(Math.Min(81920, data.Length)); try { for (int i = 0; i < data.Length; i += rent.Length) @@ -58,12 +58,12 @@ internal static async ValueTask AdaptedWriteAsync(this IOutputDestination dest, { ArrayPool.Shared.Return(rent); } - + } internal static void AdaptedWrite(this IOutputDestination dest, ReadOnlySpan data) { - var rent = ArrayPool.Shared.Rent(Math.Min(81920,data.Length)); + var rent = ArrayPool.Shared.Rent(Math.Min(81920, data.Length)); try { for (int i = 0; i < data.Length; i += rent.Length) @@ -78,8 +78,8 @@ internal static void AdaptedWrite(this IOutputDestination dest, ReadOnlySpan.Shared.Return(rent); } } - - + + // internal static IAsyncOutputSink ToAsyncOutputSink(this IOutputDestination dest, bool disposeUnderlying = true) // { // if (dest is IAsyncOutputSink sink) return sink; @@ -89,13 +89,13 @@ internal static void AdaptedWrite(this IOutputDestination dest, ReadOnlySpan await dest.CopyFromStreamAsyncInternal(stream, cancellationToken); - + internal static async Task CopyFromStreamAsyncInternal(this IOutputDestination dest, Stream stream, CancellationToken cancellationToken) { if (stream is { CanRead: true, CanSeek: true }) { - await dest.RequestCapacityAsync((int) stream.Length); + await dest.RequestCapacityAsync((int)stream.Length); } const int bufferSize = 81920; @@ -111,4 +111,4 @@ await dest.WriteAsync(new ArraySegment(buffer, 0, bytesRead), cancellation await dest.FlushAsync(cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/IOutputSink.cs b/src/Imageflow/Fluent/IOutputSink.cs index 6b5bf18..4942207 100644 --- a/src/Imageflow/Fluent/IOutputSink.cs +++ b/src/Imageflow/Fluent/IOutputSink.cs @@ -1,4 +1,4 @@ -namespace Imageflow.Fluent; +namespace Imageflow.Fluent; internal interface IOutputSink : IDisposable { @@ -22,4 +22,4 @@ public static async ValueTask WriteAllAsync(this IAsyncOutputSink sink, ReadOnly await sink.FastWriteAsync(data, cancellationToken); await sink.FastFlushAsync(cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/IPreparedFilesystemJob.cs b/src/Imageflow/Fluent/IPreparedFilesystemJob.cs index 11e8130..ccc0218 100644 --- a/src/Imageflow/Fluent/IPreparedFilesystemJob.cs +++ b/src/Imageflow/Fluent/IPreparedFilesystemJob.cs @@ -1,8 +1,8 @@ -namespace Imageflow.Fluent +namespace Imageflow.Fluent { public interface IPreparedFilesystemJob : IDisposable { string JsonPath { get; } IReadOnlyDictionary OutputFiles { get; } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/IWatermarkConstraintBox.cs b/src/Imageflow/Fluent/IWatermarkConstraintBox.cs index 889df41..f56d7b1 100644 --- a/src/Imageflow/Fluent/IWatermarkConstraintBox.cs +++ b/src/Imageflow/Fluent/IWatermarkConstraintBox.cs @@ -8,4 +8,4 @@ public interface IWatermarkConstraintBox object ToImageflowDynamic(); JsonNode ToJsonNode(); } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/ImageJob.cs b/src/Imageflow/Fluent/ImageJob.cs index 9f36b65..902b7c5 100644 --- a/src/Imageflow/Fluent/ImageJob.cs +++ b/src/Imageflow/Fluent/ImageJob.cs @@ -1,8 +1,9 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; + using Imageflow.Bindings; using Imageflow.Internal.Helpers; using Imageflow.IO; @@ -11,8 +12,8 @@ namespace Imageflow.Fluent { [Obsolete("Use ImageJob instead")] public class FluentBuildJob : ImageJob; - - public class ImageJob: IDisposable + + public class ImageJob : IDisposable { private bool _disposed; private readonly Dictionary _inputs = new Dictionary(2); @@ -35,43 +36,43 @@ internal void AddOutput(int ioId, IOutputDestination destination) [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] public BuildNode Decode(IBytesSource source, DecodeCommands commands) => Decode(source.ToMemorySource(), GenerateIoId(), commands); - + [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] public BuildNode Decode(IBytesSource source, int ioId) => Decode(source.ToMemorySource(), ioId, null); - + [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] - public BuildNode Decode(IBytesSource source) => Decode( source, GenerateIoId()); + public BuildNode Decode(IBytesSource source) => Decode(source, GenerateIoId()); + - [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] public BuildNode Decode(IBytesSource source, int ioId, DecodeCommands? commands) { return Decode(source.ToMemorySource(), ioId, commands); } - - public BuildNode Decode(Stream source, bool disposeStream) => Decode( source, disposeStream, GenerateIoId()); - - public BuildNode Decode(Stream source, bool disposeStream, int ioId) => - Decode( disposeStream ? BufferedStreamSource.UseEntireStreamAndDisposeWithSource(source) + + public BuildNode Decode(Stream source, bool disposeStream) => Decode(source, disposeStream, GenerateIoId()); + + public BuildNode Decode(Stream source, bool disposeStream, int ioId) => + Decode(disposeStream ? BufferedStreamSource.UseEntireStreamAndDisposeWithSource(source) : BufferedStreamSource.BorrowEntireStream(source), ioId); - + [Obsolete("Use Decode(MemorySource.Borrow(arraySegment, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed)) instead")] - public BuildNode Decode(ArraySegment source) => Decode( MemorySource.Borrow(source, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), GenerateIoId()); - + public BuildNode Decode(ArraySegment source) => Decode(MemorySource.Borrow(source, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), GenerateIoId()); + [Obsolete("Use Decode(MemorySource.Borrow(arraySegment, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), ioId) instead")] - public BuildNode Decode(ArraySegment source, int ioId) => Decode( MemorySource.Borrow(source, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), ioId); - - public BuildNode Decode(byte[] source) => Decode( new MemorySource(source), GenerateIoId()); - public BuildNode Decode(byte[] source, int ioId) => Decode( new MemorySource(source), ioId); + public BuildNode Decode(ArraySegment source, int ioId) => Decode(MemorySource.Borrow(source, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), ioId); + + public BuildNode Decode(byte[] source) => Decode(new MemorySource(source), GenerateIoId()); + public BuildNode Decode(byte[] source, int ioId) => Decode(new MemorySource(source), ioId); public BuildNode Decode(T source) where T : IAsyncMemorySource { return Decode(source, GenerateIoId(), null); } - public BuildNode Decode(T source, DecodeCommands commands) where T:IAsyncMemorySource => + public BuildNode Decode(T source, DecodeCommands commands) where T : IAsyncMemorySource => Decode(source, GenerateIoId(), commands); - + public BuildNode Decode(T source, int ioId) where T : IAsyncMemorySource { return Decode(source, ioId, null); @@ -81,7 +82,7 @@ public BuildNode Decode(T source, int ioId) where T : IAsyncMemorySource /// Commands to the decoder, such as JPEG or WebP block-wise downscaling for performance, or to discard the color profile or ignore color profile errors /// /// - public BuildNode Decode(T source, int ioId, DecodeCommands? commands) where T:IAsyncMemorySource + public BuildNode Decode(T source, int ioId, DecodeCommands? commands) where T : IAsyncMemorySource { AddInput(ioId, source); if (commands == null) @@ -94,7 +95,7 @@ public BuildNode Decode(T source, int ioId, DecodeCommands? commands) where T // io_id = ioId // } // }); - new JsonObject() {{"decode", new JsonObject() {{"io_id", ioId}}}}); + new JsonObject() { { "decode", new JsonObject() { { "io_id", ioId } } } }); } return BuildNode.StartNode(this, // new @@ -105,48 +106,48 @@ public BuildNode Decode(T source, int ioId, DecodeCommands? commands) where T // commands = commands.ToImageflowDynamic() // } // }); - new JsonObject() {{"decode", new JsonObject() {{"io_id", ioId}, {"commands", commands.ToJsonNode()}}}}); + new JsonObject() { { "decode", new JsonObject() { { "io_id", ioId }, { "commands", commands.ToJsonNode() } } } }); } - + public BuildNode CreateCanvasBgra32(uint w, uint h, AnyColor color) => CreateCanvas(w, h, color, PixelFormat.Bgra_32); - + public BuildNode CreateCanvasBgr32(uint w, uint h, AnyColor color) => CreateCanvas(w, h, color, PixelFormat.Bgr_32); - - private BuildNode CreateCanvas(uint w, uint h, AnyColor color, PixelFormat format) => - BuildNode.StartNode(this, - //new {create_canvas = new {w, h, color = color.ToImageflowDynamic() - new JsonObject() {{"create_canvas", new JsonObject() - {{"w", w}, {"h", h}, - {"color", color.ToJsonNode()}}}, - {"format", format.ToString().ToLowerInvariant()}}); + + private BuildNode CreateCanvas(uint w, uint h, AnyColor color, PixelFormat format) => + BuildNode.StartNode(this, + //new {create_canvas = new {w, h, color = color.ToImageflowDynamic() + new JsonObject() {{"create_canvas", new JsonObject() + {{"w", w}, {"h", h}, + {"color", color.ToJsonNode()}}}, + {"format", format.ToString().ToLowerInvariant()}}); + - [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString) => BuildCommandString(source, null, dest, null, commandString); [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, int? destIoId, string commandString) => BuildCommandString(source, sourceIoId, dest, destIoId, commandString, null); - + [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString, ICollection? watermarks) => BuildCommandString(source, null, dest, null, commandString, watermarks); - + [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, int? destIoId, string commandString, ICollection? watermarks) { return BuildCommandString(source.ToMemorySource(), sourceIoId, dest, destIoId, commandString, watermarks); } - + public BuildEndpoint BuildCommandString(byte[] source, IOutputDestination dest, string commandString) => BuildCommandString(new MemorySource(source), dest, commandString); /// @@ -167,7 +168,7 @@ public BuildEndpoint BuildCommandString(IAsyncMemorySource source, IOutputDestin ICollection? watermarks) => BuildCommandString(source, null, dest, null, commandString, watermarks); - + public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIoId, IOutputDestination dest, int? destIoId, string commandString, ICollection? watermarks) { @@ -175,7 +176,7 @@ public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIo AddInput(sourceIoId.Value, source); destIoId = destIoId ?? GenerateIoId(); AddOutput(destIoId.Value, dest); - + if (watermarks != null) { foreach (var w in watermarks) @@ -185,7 +186,7 @@ public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIo if (w.Source != null) AddInput(w.IoId.Value, w.Source); } } - + // dynamic nodeData = new // { // command_string = new @@ -210,9 +211,9 @@ public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIo }} }; return new BuildEndpoint(this, nodeData, null, null); - + } - + /// /// Complete the job and set execution options /// @@ -225,17 +226,17 @@ public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIo [Obsolete("Use .Finish().SetCancellationToken(t).InProcessAsync()")] public Task FinishAsync(CancellationToken cancellationToken) => Finish().SetCancellationToken(cancellationToken).InProcessAsync(); - + [Obsolete("Use .Finish().SetCancellationToken(cancellationToken).InSubprocessAsync(imageflowToolPath, outputBufferCapacity)")] public Task FinishInSubprocessAsync(CancellationToken cancellationToken, string imageflowToolPath, long? outputBufferCapacity = null) => Finish().SetCancellationToken(cancellationToken) .InSubprocessAsync(imageflowToolPath, outputBufferCapacity); - + [Obsolete("Use .Finish().SetCancellationToken(cancellationToken).WriteJsonJobAndInputs(deleteFilesOnDispose)")] public Task WriteJsonJobAndInputs(CancellationToken cancellationToken, bool deleteFilesOnDispose) => Finish().SetCancellationToken(cancellationToken).WriteJsonJobAndInputs(deleteFilesOnDispose); - + [Obsolete("Use .Finish().SetCancellationToken(cancellationToken).InProcessAndDisposeAsync()")] public Task FinishAndDisposeAsync(CancellationToken cancellationToken) => Finish().SetCancellationToken(cancellationToken).InProcessAndDisposeAsync(); @@ -254,16 +255,16 @@ internal JsonObject CreateJsonNodeForFramewiseWithSecurityOptions(SecurityOption ["security"] = securityOptions?.ToJsonNode() }; } - + internal string ToJsonDebug(SecurityOptions? securityOptions = default) { - return CreateJsonNodeForFramewiseWithSecurityOptions(securityOptions).ToJsonString(); + return CreateJsonNodeForFramewiseWithSecurityOptions(securityOptions).ToJsonString(); } internal async Task FinishAsync(JobExecutionOptions executionOptions, SecurityOptions? securityOptions, CancellationToken cancellationToken) { var inputByteArrays = await Task.WhenAll( - _inputs.Select( async pair => new KeyValuePair>(pair.Key, await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken)))); + _inputs.Select(async pair => new KeyValuePair>(pair.Key, await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken)))); using (var ctx = new JobContext()) { foreach (var pair in inputByteArrays) @@ -273,18 +274,18 @@ internal async Task FinishAsync(JobExecutionOptions executionOpt { ctx.AddOutputBuffer(outId); } - + //TODO: Use a Semaphore to limit concurrency var message = CreateJsonNodeForFramewiseWithSecurityOptions(securityOptions); - + var response = executionOptions.OffloadCpuToThreadPool ? await Task.Run(() => ctx.InvokeExecute(message), cancellationToken) : ctx.InvokeExecute(message); - + // TODO: Should we handle failure before copying out the buffers?? using (response) { - + foreach (var pair in _outputs) { using var memOwner = ctx.BorrowOutputBufferMemoryAndAddReference(pair.Key); @@ -305,7 +306,7 @@ public StreamJsonSpanProvider(int statusCode, MemoryStream ms) { ImageflowErrorCode = statusCode; _ms = ms; - } + } public void Dispose() => _ms.Dispose(); public Stream GetStream() => _ms; public ReadOnlySpan BorrowBytes() @@ -348,9 +349,9 @@ private static ITemporaryFileProvider SystemTempProvider() { return RuntimeFileLocator.IsUnix ? TemporaryFile.CreateProvider() : TemporaryMemoryFile.CreateProvider(); } - - - class SubprocessFilesystemJob: IPreparedFilesystemJob + + + class SubprocessFilesystemJob : IPreparedFilesystemJob { public SubprocessFilesystemJob(ITemporaryFileProvider provider) { @@ -359,7 +360,7 @@ public SubprocessFilesystemJob(ITemporaryFileProvider provider) internal ITemporaryFileProvider Provider { get; } public string JsonPath { get; set; } = ""; public IReadOnlyDictionary OutputFiles { get; internal set; } = new ReadOnlyDictionary(new Dictionary()); - + internal JsonNode? JobMessage { get; set; } internal List Cleanup { get; } = new List(); internal List>? Outputs { get; set; } @@ -383,7 +384,7 @@ public void Dispose() d.Dispose(); } Cleanup.Clear(); - + } ~SubprocessFilesystemJob() { @@ -403,22 +404,22 @@ private async Task PrepareForSubprocessAsync(Cancellati var file = job.Provider.Create(cleanupFiles, bytes.Length); job.Cleanup.Add(file); return (io_id: pair.Key, direction: "in", - io: new JsonObject {{"file", file.Path}}, + io: new JsonObject { { "file", file.Path } }, bytes, bytes.Length, File: file); - + }))).ToArray(); - + var outputCapacity = outputBufferCapacity ?? inputFiles.Max(v => v.Length) * 2; var outputFiles = _outputs.Select(pair => { var file = job.Provider.Create(cleanupFiles, outputCapacity); job.Cleanup.Add(file); - return (io_id: pair.Key, direction: "out", - io: new JsonObject {{"file", file.Path}}, + return (io_id: pair.Key, direction: "out", + io: new JsonObject { { "file", file.Path } }, Length: outputCapacity, File: file, Dest: pair.Value); }).ToArray(); - - + + foreach (var f in inputFiles) { using var accessor = f.File.WriteFromBeginning(); @@ -455,7 +456,7 @@ private async Task PrepareForSubprocessAsync(Cancellati }, ["framewise"] = ToFramewise() }; - + var outputFilenames = new Dictionary(); foreach (var f in outputFiles) { @@ -463,7 +464,7 @@ private async Task PrepareForSubprocessAsync(Cancellati } job.OutputFiles = new ReadOnlyDictionary(outputFilenames); - + job.Outputs = outputFiles .Select(f => new KeyValuePair(f.File, f.Dest)).ToList(); @@ -477,16 +478,16 @@ private async Task PrepareForSubprocessAsync(Cancellati writer.Flush(); stream.Flush(); stream.Dispose(); - + job.JsonPath = jsonFile.Path; - + return job; } catch { job.Dispose(); - throw; + throw; } } @@ -495,7 +496,7 @@ private async Task PrepareForSubprocessAsync(Cancellati - internal async Task FinishInSubprocessAsync(SecurityOptions? securityOptions, + internal async Task FinishInSubprocessAsync(SecurityOptions? securityOptions, string? imageflowToolPath, long? outputBufferCapacity = null, CancellationToken cancellationToken = default(CancellationToken)) { if (imageflowToolPath == null) @@ -509,7 +510,7 @@ internal async Task FinishInSubprocessAsync(SecurityOptions? sec using (var job = await PrepareForSubprocessAsync(cancellationToken, securityOptions, true, outputBufferCapacity)) { - + var startInfo = new ProcessStartInfo { StandardErrorEncoding = Encoding.UTF8, @@ -544,22 +545,22 @@ internal async Task FinishInSubprocessAsync(SecurityOptions? sec return BuildJobResult.From(new MemoryJsonResponse(results.ExitCode, outputMemory), _outputs); } } - - internal async Task WriteJsonJobAndInputs(CancellationToken cancellationToken, SecurityOptions? securityOptions, bool deleteFilesOnDispose) + + internal async Task WriteJsonJobAndInputs(CancellationToken cancellationToken, SecurityOptions? securityOptions, bool deleteFilesOnDispose) { return await PrepareForSubprocessAsync(cancellationToken, securityOptions, deleteFilesOnDispose); } - - + + private readonly List _nodesCreated = new List(10); - - + + internal void AddNode(BuildItemBase n) { AssertReady(); - + if (_nodesCreated.Contains(n)) { throw new ImageflowAssertionFailed("Cannot add duplicate node"); @@ -569,7 +570,7 @@ internal void AddNode(BuildItemBase n) { throw new ImageflowAssertionFailed("You cannot use a canvas node from a different ImageJob"); } - if (n.Input != null && !_nodesCreated.Contains(n.Input)) + if (n.Input != null && !_nodesCreated.Contains(n.Input)) { throw new ImageflowAssertionFailed("You cannot use an input node from a different ImageJob"); } @@ -584,7 +585,8 @@ private enum EdgeKind private ICollection CollectUnique() => _nodesCreated; - private static IEnumerable> CollectEdges(ICollection forUniqueNodes){ + private static IEnumerable> CollectEdges(ICollection forUniqueNodes) + { var edges = new List>(forUniqueNodes.Count); foreach (var n in forUniqueNodes) { @@ -626,12 +628,12 @@ private JsonNode ToFramewiseGraph(ICollection uniqueNodes) ["to"] = t.Item2 - lowestUid, ["kind"] = t.Item3.ToString().ToLowerInvariant() }).ToArray(); - - + + var nodes = new JsonObject(); foreach (var n in uniqueNodes) { - nodes.Add((n.Uid - lowestUid).ToString(), n.NodeData ); + nodes.Add((n.Uid - lowestUid).ToString(), n.NodeData); } // return new // { @@ -681,7 +683,7 @@ protected virtual void Dispose(bool disposing) _disposed = true; } - public int GenerateIoId() =>_inputs.Keys.Concat(_outputs.Keys).DefaultIfEmpty(-1).Max() + 1; + public int GenerateIoId() => _inputs.Keys.Concat(_outputs.Keys).DefaultIfEmpty(-1).Max() + 1; /// /// Returns dimensions and format of the provided image stream or byte array @@ -691,8 +693,8 @@ protected virtual void Dispose(bool disposing) [Obsolete("Use GetImageInfoAsync(IMemorySource, DataLifetime) instead; this method is less efficient and lacks clarity on disposing the source.")] public static Task GetImageInfo(IBytesSource image) => GetImageInfo(image, CancellationToken.None); - - + + /// /// Returns dimensions and format of the provided image stream or byte array /// @@ -732,7 +734,8 @@ public static ImageInfo GetImageInfo(IMemorySource image, SourceLifetime dispose using var ctx = new JobContext(); ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); return ctx.GetImageInfo(0); - }finally + } + finally { if (disposeSource != SourceLifetime.Borrowed) { @@ -760,7 +763,8 @@ public static async ValueTask GetImageInfoAsync(IAsyncMemorySource im using var ctx = new JobContext(); ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); return ctx.GetImageInfo(0); - }finally + } + finally { if (disposeSource != SourceLifetime.Borrowed) { diff --git a/src/Imageflow/Fluent/InputWatermark.cs b/src/Imageflow/Fluent/InputWatermark.cs index 7bd792a..31d3725 100644 --- a/src/Imageflow/Fluent/InputWatermark.cs +++ b/src/Imageflow/Fluent/InputWatermark.cs @@ -23,28 +23,28 @@ public InputWatermark(IAsyncMemorySource source, WatermarkOptions watermark) Source = source; Watermark = watermark; } - + public InputWatermark(IAsyncMemorySource source, int ioId, WatermarkOptions watermark) { Source = source; IoId = ioId; Watermark = watermark; } - + public IAsyncMemorySource Source { get; set; } public int? IoId { get; set; } - public WatermarkOptions Watermark { get; set; } + public WatermarkOptions Watermark { get; set; } [Obsolete("Use ToJsonNode() methods instead")] public object ToImageflowDynamic() { return Watermark.ToImageflowDynamic(IoId ?? throw new InvalidOperationException("InputWatermark.ToImageflowDynamic() cannot be called without an IoId value assigned")); } - + internal JsonNode ToJsonNode() { return Watermark.ToJsonNode(IoId ?? throw new InvalidOperationException("InputWatermark.ToJson() cannot be called without an IoId value assigned")); } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/JobExecutionOptions.cs b/src/Imageflow/Fluent/JobExecutionOptions.cs index e7fbb0a..ae31a95 100644 --- a/src/Imageflow/Fluent/JobExecutionOptions.cs +++ b/src/Imageflow/Fluent/JobExecutionOptions.cs @@ -4,4 +4,4 @@ internal class JobExecutionOptions { internal bool OffloadCpuToThreadPool { get; set; } = false; } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/MagicBytes.cs b/src/Imageflow/Fluent/MagicBytes.cs index 87b0411..fe02d93 100644 --- a/src/Imageflow/Fluent/MagicBytes.cs +++ b/src/Imageflow/Fluent/MagicBytes.cs @@ -60,12 +60,12 @@ internal enum ImageFormat /// private static ImageFormat? GetImageFormat(byte[] first12Bytes) { - + // Useful resources: https://chromium.googlesource.com/chromium/src/+/HEAD/net/base/mime_sniffer.cc // https://github.com/velocityzen/FileType/blob/master/Sources/FileType/FileTypeMatch.swift // https://en.wikipedia.org/wiki/List_of_file_signatures // https://mimetype.io/ - + // We may want to support these from Chrome's sniffer, at some point, after research // MAGIC_MASK("video/mpeg", "\x00\x00\x01\xB0", "\xFF\xFF\xFF\xF0"), // MAGIC_MASK("audio/mpeg", "\xFF\xE0", "\xFF\xE0"), @@ -74,15 +74,15 @@ internal enum ImageFormat // MAGIC_NUMBER("application/x-shockwave-flash", "FWS"), // MAGIC_NUMBER("video/x-flv", "FLV"), - + // Choosing not to detect mime types for text, svg, javascript, or executable formats // 00 61 73 6D (WebAssembly) - + // With just 12 bytes, we also can't tell PNG from APNG, Ogg audio from video, or what's in a matroska or mpeg container - - + + var bytes = first12Bytes; - if (bytes.Length < 12) throw new ArgumentException("The byte array must contain at least 12 bytes", + if (bytes.Length < 12) throw new ArgumentException("The byte array must contain at least 12 bytes", nameof(first12Bytes)); if (bytes[0] == 0xff && bytes[1] == 0xd8 && bytes[2] == 0xff) @@ -129,12 +129,13 @@ internal enum ImageFormat return ImageFormat.Tiff; } - if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x01 && bytes[3] == 0x00) + if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x01 && bytes[3] == 0x00) { return ImageFormat.Ico; } - if (bytes[0] == 'w' && bytes[1] == 'O' && bytes[2] == 'F'){ + if (bytes[0] == 'w' && bytes[1] == 'O' && bytes[2] == 'F') + { if (bytes[3] == 'F') return ImageFormat.Woff; if (bytes[3] == '2') return ImageFormat.Woff2; } @@ -143,17 +144,17 @@ internal enum ImageFormat { return ImageFormat.OpenTypeFont; } - + if (bytes[0] == 'f' && bytes[1] == 'L' && bytes[2] == 'a' && bytes[3] == 'C') { return ImageFormat.Flac; } - if (bytes[0] == 0x00 && bytes[1] == 0x01 && bytes[2] == 0x00 && bytes[3] == 0x00) + if (bytes[0] == 0x00 && bytes[1] == 0x01 && bytes[2] == 0x00 && bytes[3] == 0x00) { return ImageFormat.TrueTypeFont; } - - + + if (bytes[0] == 0x1A && bytes[1] == 0x45 && bytes[2] == 0xDF && bytes[3] == 0xA3) { return ImageFormat.MatroskaOrWebM; @@ -162,21 +163,21 @@ internal enum ImageFormat if (bytes[0] == '%' && bytes[1] == 'P' && bytes[2] == 'D' && bytes[3] == 'F' && bytes[4] == '-') { return ImageFormat.Pdf; - } + } if (bytes[0] == '%' && bytes[1] == '!' && bytes[2] == 'P' && bytes[3] == 'S' && bytes[4] == '-' && bytes[5] == 'A' && bytes[6] == 'd' && bytes[7] == 'o' && bytes[8] == 'b' && bytes[9] == 'e' && bytes[10] == '-') { return ImageFormat.PostScript; } - + if (bytes[0] == 'F' && bytes[1] == 'L' && bytes[2] == 'I' && bytes[3] == 'F') { return ImageFormat.FLIF; } - + if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x00) { - if (bytes[3] == 0x0C + if (bytes[3] == 0x0C && bytes[7] == 0x20 && bytes[8] == 0x0D && bytes[9] == 0x0A && bytes[10] == 0x87 && bytes[11] == 0x0A) { @@ -190,21 +191,21 @@ internal enum ImageFormat return ImageFormat.Jpeg2000; } } - - + + } if (bytes[0] == 0xFF && bytes[1] == 0x0A) { return ImageFormat.JpegXL; } - + if (bytes[0] == 'B' && bytes[1] == 'M') { return ImageFormat.Bitmap; } - + if (bytes[0] == 'O' && bytes[1] == 'g' && bytes[2] == 'g' && bytes[3] == 'S' && bytes[4] == 0x00) { return ImageFormat.OggContainer; // Could be audio or video, no idea, Chrome presumes audio @@ -223,8 +224,8 @@ internal enum ImageFormat { if ((bytes[4] & 0xF1) == 0x21) { - // MPEG-PS, MPEG-1 Part 1 - return ImageFormat.Mpeg1; + // MPEG-PS, MPEG-1 Part 1 + return ImageFormat.Mpeg1; } if ((bytes[4] & 0xC4) == 0x44) @@ -233,13 +234,13 @@ internal enum ImageFormat return ImageFormat.Mpeg2; } } - - + + if (bytes[0] == 0xFF && (bytes[1] == 0xF1 || bytes[1] == 0xF9)) { return ImageFormat.Aac; } - + if (bytes[0] == 'F' && bytes[1] == 'O' && bytes[2] == 'R' && bytes[3] == 'M') { return ImageFormat.AIFF; @@ -369,9 +370,9 @@ internal enum ImageFormat return ImageFormat.ThreeGPP; } } - + //TODO: Add 3GP -> https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers#3gp - + return null; } @@ -470,13 +471,13 @@ internal enum ImageFormat case null: break; - + default: throw new ArgumentOutOfRangeException(); } return null; } - + /// /// Returns true if Imageflow can likely decode the image based on the given file header /// @@ -500,4 +501,4 @@ internal static bool IsDecodable(byte[] first12Bytes) } } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/MemoryLifetimePromise.cs b/src/Imageflow/Fluent/MemoryLifetimePromise.cs index 3d1d479..92b0185 100644 --- a/src/Imageflow/Fluent/MemoryLifetimePromise.cs +++ b/src/Imageflow/Fluent/MemoryLifetimePromise.cs @@ -1,6 +1,6 @@ -namespace Imageflow.Fluent; +namespace Imageflow.Fluent; -public enum MemoryLifetimePromise:byte +public enum MemoryLifetimePromise : byte { /// /// The caller guarantees that the provided ReadOnlyMemory<byte> will remain valid until after the job is disposed, across @@ -15,4 +15,4 @@ public enum MemoryLifetimePromise:byte /// The caller guarantees that the provided ReadOnlyMemory is "owner-less" or owned by the garbage collector; no IMemoryOwner reference exists /// MemoryIsOwnedByRuntime -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/MemorySource.cs b/src/Imageflow/Fluent/MemorySource.cs index b70d47d..a14d98f 100644 --- a/src/Imageflow/Fluent/MemorySource.cs +++ b/src/Imageflow/Fluent/MemorySource.cs @@ -1,4 +1,5 @@ -using System.Buffers; +using System.Buffers; + using Imageflow.Internal.Helpers; namespace Imageflow.Fluent; @@ -19,7 +20,7 @@ public static IAsyncMemorySource TakeOwnership(IMemoryOwner ownedMemory, M return new MemorySource(null, ownedMemory, promise); } - + private MemorySource(ReadOnlyMemory? borrowedMemory, IMemoryOwner? ownedMemory, MemoryLifetimePromise promise) @@ -57,8 +58,8 @@ internal MemorySource(ArraySegment bytes) { _borrowedMemory = new ReadOnlyMemory(bytes.Array, bytes.Offset, bytes.Count); } - - + + public static IAsyncMemorySource Borrow(ReadOnlyMemory borrowedMemory, MemoryLifetimePromise promise) { if (promise == MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource) @@ -69,16 +70,16 @@ public static IAsyncMemorySource Borrow(ReadOnlyMemory borrowedMemory, Mem return new MemorySource(borrowedMemory, null, promise); } - + public static IAsyncMemorySource Borrow(byte[] borrowedMemory, MemoryLifetimePromise promise) => Borrow(borrowedMemory.AsMemory(), promise); - + public static IAsyncMemorySource Borrow(byte[] borrowedMemory) => Borrow(borrowedMemory.AsMemory(), MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); - + public static IAsyncMemorySource Borrow(ArraySegment borrowedMemory, MemoryLifetimePromise promise) { - return new MemorySource(borrowedMemory, null, promise); + return new MemorySource(borrowedMemory, null, promise); } public static IAsyncMemorySource Borrow(byte[] borrowedMemory, int offset, int length, MemoryLifetimePromise promise) { @@ -95,11 +96,11 @@ public ValueTask> BorrowReadOnlyMemoryAsync(CancellationTok { return new ValueTask>(_borrowedMemory ?? _ownedMemory!.Memory); } - + public ReadOnlyMemory BorrowReadOnlyMemory() { return _borrowedMemory ?? _ownedMemory!.Memory; } public bool AsyncPreferred => false; -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/PerformanceDetails.cs b/src/Imageflow/Fluent/PerformanceDetails.cs index aa08321..085fe8f 100644 --- a/src/Imageflow/Fluent/PerformanceDetails.cs +++ b/src/Imageflow/Fluent/PerformanceDetails.cs @@ -9,7 +9,7 @@ internal PerformanceDetails(JsonNode? perf) { var obj = perf?.AsObject(); if (obj == null) return; - + // foreach (var f in perf.frames) // { // frames.Add(new PerformanceDetailsFrame(f)); @@ -31,7 +31,8 @@ public string GetFirstFrameSummary() if (Frames.Count > 1) { sb.Append($"First of {Frames.Count} frames: "); - }else if (Frames.Count == 0) + } + else if (Frames.Count == 0) { sb.Append("No frames found"); } @@ -47,4 +48,4 @@ public string GetFirstFrameSummary() return sb.ToString(); } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/PerformanceDetailsFrame.cs b/src/Imageflow/Fluent/PerformanceDetailsFrame.cs index f502d31..448b76b 100644 --- a/src/Imageflow/Fluent/PerformanceDetailsFrame.cs +++ b/src/Imageflow/Fluent/PerformanceDetailsFrame.cs @@ -1,4 +1,5 @@ using System.Text.Json.Nodes; + using Imageflow.Bindings; namespace Imageflow.Fluent @@ -25,7 +26,7 @@ internal PerformanceDetailsFrame(JsonNode? frame) var name = n.AsObject().TryGetPropertyValue("name", out var nameValue) ? nameValue?.GetValue() : throw new ImageflowAssertionFailed("PerformanceDetailsFrame node name is null"); - + var microseconds = n.AsObject().TryGetPropertyValue("wall_microseconds", out var microsecondsValue) ? microsecondsValue?.GetValue() : throw new ImageflowAssertionFailed("PerformanceDetailsFrame node wall_microseconds is null"); @@ -42,4 +43,4 @@ internal PerformanceDetailsFrame(JsonNode? frame) public ICollection Nodes => _nodes; } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/PerformanceDetailsNode.cs b/src/Imageflow/Fluent/PerformanceDetailsNode.cs index 75967cc..7c3a07b 100644 --- a/src/Imageflow/Fluent/PerformanceDetailsNode.cs +++ b/src/Imageflow/Fluent/PerformanceDetailsNode.cs @@ -2,8 +2,8 @@ namespace Imageflow.Fluent { public record struct PerformanceDetailsNode { - + public string Name { get; internal init; } public long WallMicroseconds { get; internal init; } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/ResampleHints.cs b/src/Imageflow/Fluent/ResampleHints.cs index 47b3815..4adaeb4 100644 --- a/src/Imageflow/Fluent/ResampleHints.cs +++ b/src/Imageflow/Fluent/ResampleHints.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; namespace Imageflow.Fluent { @@ -6,7 +6,7 @@ public class ResampleHints { public SharpenWhen? SharpenWhen { get; set; } public ResampleWhen? ResampleWhen { get; set; } - + public ScalingFloatspace? InterpolationColorspace { get; set; } public InterpolationFilter? UpFilter { get; set; } public InterpolationFilter? DownFilter { get; set; } @@ -48,7 +48,7 @@ public ResampleHints SetSharpenWhen(SharpenWhen? sharpenWhen) SharpenWhen = sharpenWhen; return this; } - + [Obsolete("Use SetResampleFilters instead")] public ResampleHints ResampleFilter(InterpolationFilter? downFilter, InterpolationFilter? upFilter) { @@ -56,15 +56,15 @@ public ResampleHints ResampleFilter(InterpolationFilter? downFilter, Interpolati UpFilter = upFilter; return this; } - + public ResampleHints SetResampleFilters(InterpolationFilter? downFilter, InterpolationFilter? upFilter) { DownFilter = downFilter; UpFilter = upFilter; return this; } - - public ResampleHints SetUpSamplingFilter( InterpolationFilter? upFilter) + + public ResampleHints SetUpSamplingFilter(InterpolationFilter? upFilter) { UpFilter = upFilter; return this; @@ -88,15 +88,15 @@ public ResampleHints SetResampleWhen(ResampleWhen? resampleWhen) ResampleWhen = resampleWhen; return this; } - + [Obsolete("Use SetInterpolationColorspace instead")] - public ResampleHints ResampleColorspace( ScalingFloatspace? interpolationColorspace) + public ResampleHints ResampleColorspace(ScalingFloatspace? interpolationColorspace) { InterpolationColorspace = interpolationColorspace; return this; } - - public ResampleHints SetInterpolationColorspace( ScalingFloatspace? interpolationColorspace) + + public ResampleHints SetInterpolationColorspace(ScalingFloatspace? interpolationColorspace) { InterpolationColorspace = interpolationColorspace; return this; @@ -130,4 +130,4 @@ public JsonNode ToJsonNode() } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/SecurityOptions.cs b/src/Imageflow/Fluent/SecurityOptions.cs index 772ab8c..07ede61 100644 --- a/src/Imageflow/Fluent/SecurityOptions.cs +++ b/src/Imageflow/Fluent/SecurityOptions.cs @@ -6,7 +6,7 @@ public class SecurityOptions { public FrameSizeLimit? MaxDecodeSize { get; set; } - + public FrameSizeLimit? MaxFrameSize { get; set; } public FrameSizeLimit? MaxEncodeSize { get; set; } @@ -14,17 +14,17 @@ public class SecurityOptions public SecurityOptions SetMaxDecodeSize(FrameSizeLimit? limit) { MaxDecodeSize = limit; - return this; + return this; } public SecurityOptions SetMaxFrameSize(FrameSizeLimit? limit) { MaxFrameSize = limit; - return this; + return this; } public SecurityOptions SetMaxEncodeSize(FrameSizeLimit? limit) { MaxEncodeSize = limit; - return this; + return this; } [Obsolete("Use ToJsonNode() instead")] @@ -47,4 +47,4 @@ internal JsonNode ToJsonNode() return node; } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/SourceLifetime.cs b/src/Imageflow/Fluent/SourceLifetime.cs index c6ff4b2..cd6dfbc 100644 --- a/src/Imageflow/Fluent/SourceLifetime.cs +++ b/src/Imageflow/Fluent/SourceLifetime.cs @@ -1,4 +1,4 @@ -namespace Imageflow.Fluent; +namespace Imageflow.Fluent; public enum SourceLifetime { @@ -14,4 +14,4 @@ public enum SourceLifetime /// You swear not to close, dispose, or reuse the data source object or its underlying memory/stream until after the job is disposed. You remain responsible for disposing and cleaning up the source after the job is disposed. /// Borrowed -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/SrgbColor.cs b/src/Imageflow/Fluent/SrgbColor.cs index 2a7a6dc..260b54e 100644 --- a/src/Imageflow/Fluent/SrgbColor.cs +++ b/src/Imageflow/Fluent/SrgbColor.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System.Globalization; + using Imageflow.Bindings; namespace Imageflow.Fluent @@ -15,22 +16,22 @@ public readonly struct SrgbColor(byte r, byte g, byte b, byte a) private static byte Mask8(uint v, int index) { - var shift = index * 8; + var shift = index * 8; var mask = 0xff << shift; var result = (v & mask) >> shift; if (result > 255) throw new ImageflowAssertionFailed("Integer overflow in color parsing"); - return (byte) result; + return (byte)result; } private static byte Expand4(uint v, int index) { - var shift = index * 4; + var shift = index * 4; var mask = 0xf << shift; var result = (v & mask) >> shift; result = result | result << 4; // Duplicate lower 4 bits into upper if (result > 255) throw new ImageflowAssertionFailed("Integer overflow in color parsing"); - return (byte) result; + return (byte)result; } - + /// /// Parses a hexadecimal color in the form RGB, RGBA, RRGGBB, or RRGGBBAA /// @@ -41,7 +42,8 @@ public static SrgbColor FromHex(string s) { s = s.TrimStart('#'); var v = uint.Parse(s, NumberStyles.HexNumber); - switch (s.Length){ + switch (s.Length) + { case 3: return RGBA(Expand4(v, 2), Expand4(v, 1), Expand4(v, 0), 0xff); case 6: return RGBA(Mask8(v, 2), Mask8(v, 1), Mask8(v, 0), 0xff); case 4: return RGBA(Expand4(v, 3), Expand4(v, 2), Expand4(v, 1), Expand4(v, 0)); @@ -53,12 +55,12 @@ public static SrgbColor FromHex(string s) public string ToHexUnprefixed() => A == 0xff ? $"{R:x2}{G:x2}{B:x2}" : $"{R:x2}{G:x2}{B:x2}{A:x2}"; public static SrgbColor BGRA(byte b, byte g, byte r, byte a) => - new SrgbColor(){ A = a, R = r, G = g, B = b}; + new SrgbColor() { A = a, R = r, G = g, B = b }; public static SrgbColor RGBA(byte r, byte g, byte b, byte a) => - new SrgbColor(){ A = a, R = r, G = g, B = b}; + new SrgbColor() { A = a, R = r, G = g, B = b }; public static SrgbColor RGB(byte r, byte g, byte b) => - new SrgbColor(){ A = 255, R = r, G = g, B = b}; - + new SrgbColor() { A = 255, R = r, G = g, B = b }; + } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/StreamDestination.cs b/src/Imageflow/Fluent/StreamDestination.cs index 589233c..763f398 100644 --- a/src/Imageflow/Fluent/StreamDestination.cs +++ b/src/Imageflow/Fluent/StreamDestination.cs @@ -1,4 +1,4 @@ -using Imageflow.Bindings; +using Imageflow.Bindings; using Imageflow.Internal.Helpers; namespace Imageflow.Fluent; @@ -21,12 +21,12 @@ public Task WriteAsync(ArraySegment bytes, CancellationToken cancellationT if (bytes.Array == null) throw new ImageflowAssertionFailed("StreamDestination.WriteAsync called with null array"); return underlying.WriteAsync(bytes.Array, bytes.Offset, bytes.Count, cancellationToken); } - + public ValueTask WriteAsync(ReadOnlyMemory bytes, CancellationToken cancellationToken) { return underlying.WriteMemoryAsync(bytes, cancellationToken); } - + public void Write(ReadOnlySpan bytes) { underlying.WriteSpan(bytes); @@ -34,9 +34,9 @@ public void Write(ReadOnlySpan bytes) public Task FlushAsync(CancellationToken cancellationToken) { - if (underlying is { CanSeek: true, CanWrite: true } + if (underlying is { CanSeek: true, CanWrite: true } && underlying.Position < underlying.Length) underlying.SetLength(underlying.Position); return underlying.FlushAsync(cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/StreamSource.cs b/src/Imageflow/Fluent/StreamSource.cs index 2d5c625..15d23e3 100644 --- a/src/Imageflow/Fluent/StreamSource.cs +++ b/src/Imageflow/Fluent/StreamSource.cs @@ -1,4 +1,5 @@ -using Imageflow.Internal.Helpers; +using Imageflow.Internal.Helpers; + using Microsoft.IO; namespace Imageflow.Fluent; @@ -33,11 +34,11 @@ public async Task> GetBytesAsync(CancellationToken cancellati if (_copy != null) { return new ArraySegment(_copy.GetBuffer(), 0, - (int) _copy.Length); + (int)_copy.Length); } var length = underlying.CanSeek ? underlying.Length : 0; if (length >= int.MaxValue) throw new OverflowException("Streams cannot exceed 2GB"); - + if (underlying is MemoryStream underlyingMemoryStream && underlyingMemoryStream.TryGetBufferSliceAllWrittenData(out var underlyingBuffer)) { @@ -46,12 +47,12 @@ public async Task> GetBytesAsync(CancellationToken cancellati if (_copy == null) { - _copy = new RecyclableMemoryStream(Mgr,"StreamSource: IBytesSource", length); - await underlying.CopyToAsync(_copy,81920, cancellationToken); + _copy = new RecyclableMemoryStream(Mgr, "StreamSource: IBytesSource", length); + await underlying.CopyToAsync(_copy, 81920, cancellationToken); } - + return new ArraySegment(_copy.GetBuffer(), 0, - (int) _copy.Length); + (int)_copy.Length); } internal bool AsyncPreferred => _copy != null && underlying is not MemoryStream && underlying is not UnmanagedMemoryStream; @@ -60,4 +61,4 @@ public static implicit operator BytesSourceAdapter(StreamSource source) { return new BytesSourceAdapter(source); } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/WatermarkFitBox.cs b/src/Imageflow/Fluent/WatermarkFitBox.cs index b3e829e..f4616c9 100644 --- a/src/Imageflow/Fluent/WatermarkFitBox.cs +++ b/src/Imageflow/Fluent/WatermarkFitBox.cs @@ -5,9 +5,9 @@ namespace Imageflow.Fluent /// /// Defines /// - public class WatermarkFitBox: IWatermarkConstraintBox + public class WatermarkFitBox : IWatermarkConstraintBox { - public WatermarkFitBox(){} + public WatermarkFitBox() { } public WatermarkFitBox(WatermarkAlign relativeTo, float x1, float y1, float x2, float y2) { @@ -18,9 +18,9 @@ public WatermarkFitBox(WatermarkAlign relativeTo, float x1, float y1, float x2, Y2 = y2; } public WatermarkAlign RelativeTo { get; set; } = WatermarkAlign.Image; - + public float X1 { get; set; } - public float Y1 { get; set; } + public float Y1 { get; set; } public float X2 { get; set; } = 100; public float Y2 { get; set; } = 100; @@ -50,7 +50,8 @@ public object ToImageflowDynamic() case WatermarkAlign.Canvas: return new { - canvas_percentage = new { + canvas_percentage = new + { x1 = X1, y1 = Y1, x2 = X2, @@ -61,7 +62,8 @@ public object ToImageflowDynamic() case WatermarkAlign.Image: return new { - image_percentage = new { + image_percentage = new + { x1 = X1, y1 = Y1, x2 = X2, @@ -103,4 +105,4 @@ public JsonNode ToJsonNode() return node; } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/WatermarkMargins.cs b/src/Imageflow/Fluent/WatermarkMargins.cs index bc498f2..ee88cb7 100644 --- a/src/Imageflow/Fluent/WatermarkMargins.cs +++ b/src/Imageflow/Fluent/WatermarkMargins.cs @@ -39,7 +39,7 @@ public WatermarkMargins SetRelativeTo(WatermarkAlign relativeTo) RelativeTo = relativeTo; return this; } - + public WatermarkMargins SetLeft(uint pixels) { Left = pixels; @@ -60,7 +60,7 @@ public WatermarkMargins SetBottom(uint pixels) Bottom = pixels; return this; } - + [Obsolete("Use ToJsonNode() instead")] public object ToImageflowDynamic() { @@ -69,7 +69,8 @@ public object ToImageflowDynamic() case WatermarkAlign.Canvas: return new { - canvas_margins = new { + canvas_margins = new + { left = Left, top = Top, right = Right, @@ -80,7 +81,8 @@ public object ToImageflowDynamic() case WatermarkAlign.Image: return new { - image_margins = new { + image_margins = new + { left = Left, top = Top, right = Right, @@ -122,4 +124,4 @@ public JsonNode ToJsonNode() return node; } } -} \ No newline at end of file +} diff --git a/src/Imageflow/Fluent/WatermarkOptions.cs b/src/Imageflow/Fluent/WatermarkOptions.cs index e1b713f..ee809f4 100644 --- a/src/Imageflow/Fluent/WatermarkOptions.cs +++ b/src/Imageflow/Fluent/WatermarkOptions.cs @@ -4,17 +4,17 @@ namespace Imageflow.Fluent { public class WatermarkOptions { - + public ConstraintGravity? Gravity { get; set; } public IWatermarkConstraintBox? FitBox { get; set; } public WatermarkConstraintMode? FitMode { get; set; } - + /// /// Range 0..1, where 1 is fully opaque and 0 is transparent. /// public float? Opacity { get; set; } public ResampleHints? Hints { get; set; } - + public uint? MinCanvasWidth { get; set; } public uint? MinCanvasHeight { get; set; } @@ -22,7 +22,7 @@ public class WatermarkOptions public WatermarkOptions WithHints(ResampleHints hints) { Hints = hints; - return this; + return this; } /// @@ -85,7 +85,7 @@ public WatermarkOptions LayoutWithMargins(WatermarkMargins margins, WatermarkCon /// /// /// - [Obsolete("Use Set___ methods instead")] + [Obsolete("Use Set___ methods instead")] public WatermarkOptions WithMinCanvasSize(uint? minWidth, uint? minHeight) { MinCanvasWidth = minWidth; @@ -93,10 +93,10 @@ public WatermarkOptions WithMinCanvasSize(uint? minWidth, uint? minHeight) return this; } - public WatermarkOptions SetHints(ResampleHints hints) + public WatermarkOptions SetHints(ResampleHints hints) { Hints = hints; - return this; + return this; } /// @@ -115,7 +115,7 @@ public WatermarkOptions SetMargins(WatermarkMargins margins) FitBox = margins; return this; } - + public WatermarkOptions SetFitBox(WatermarkFitBox fitBox) { FitBox = fitBox; @@ -142,7 +142,7 @@ public WatermarkOptions SetFitBoxLayout(WatermarkFitBox fitBox, WatermarkConstra Gravity = gravity; return this; } - + public WatermarkOptions SetMarginsLayout(WatermarkMargins margins, WatermarkConstraintMode fitMode, ConstraintGravity gravity) { diff --git a/src/Imageflow/GlobalSuppressions.cs b/src/Imageflow/GlobalSuppressions.cs index f77a6e1..36d7545 100644 --- a/src/Imageflow/GlobalSuppressions.cs +++ b/src/Imageflow/GlobalSuppressions.cs @@ -1,4 +1,4 @@ -// This file is used by Code Analysis to maintain SuppressMessage +// This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. @@ -11,4 +11,4 @@ // internals visible to Imageflow.Test -[assembly: InternalsVisibleTo("Imageflow.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("Imageflow.Test")] diff --git a/src/Imageflow/IO/ProcessEx.cs b/src/Imageflow/IO/ProcessEx.cs index f4d20a8..1b4e722 100644 --- a/src/Imageflow/IO/ProcessEx.cs +++ b/src/Imageflow/IO/ProcessEx.cs @@ -1,5 +1,6 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Text; + using Microsoft.IO; namespace Imageflow.IO @@ -45,14 +46,14 @@ static MemoryStream GetStreamForStrings(string[] strings) internal static class ProcessEx { - + public static Task RunAsync(string fileName) => RunAsync(new ProcessStartInfo(fileName)); public static Task RunAsync(string fileName, string arguments) => RunAsync(new ProcessStartInfo(fileName, arguments)); - - public static Task RunAsync(ProcessStartInfo processStartInfo) + + public static Task RunAsync(ProcessStartInfo processStartInfo) => RunAsync(processStartInfo, CancellationToken.None); public static async Task RunAsync(ProcessStartInfo processStartInfo, CancellationToken cancellationToken) @@ -67,13 +68,15 @@ public static async Task RunAsync(ProcessStartInfo processStartI var standardOutput = new List(); var standardError = new List(); - var process = new Process { + var process = new Process + { StartInfo = processStartInfo, EnableRaisingEvents = true }; var standardOutputResults = new TaskCompletionSource(); - process.OutputDataReceived += (_, args) => { + process.OutputDataReceived += (_, args) => + { if (args.Data != null) standardOutput.Add(args.Data); else @@ -81,27 +84,33 @@ public static async Task RunAsync(ProcessStartInfo processStartI }; var standardErrorResults = new TaskCompletionSource(); - process.ErrorDataReceived += (_, args) => { + process.ErrorDataReceived += (_, args) => + { if (args.Data != null) standardError.Add(args.Data); else standardErrorResults.SetResult(standardError.ToArray()); }; - process.Exited += (sender, args) => { + process.Exited += (sender, args) => + { // Since the Exited event can happen asynchronously to the output and error events, // we use the task results for stdout/stderr to ensure they both closed tcs.TrySetResult(new ProcessResults(process, standardOutputResults.Task.Result, standardErrorResults.Task.Result)); }; using (cancellationToken.Register( - callback: () => { + callback: () => + { tcs.TrySetCanceled(); - try { + try + { if (!process.HasExited) process.Kill(); - } catch (InvalidOperationException) { } - })) { + } + catch (InvalidOperationException) { } + })) + { cancellationToken.ThrowIfCancellationRequested(); if (process.Start() == false) @@ -114,4 +123,4 @@ public static async Task RunAsync(ProcessStartInfo processStartI } } } -} \ No newline at end of file +} diff --git a/src/Imageflow/IO/TemporaryFileProviders.cs b/src/Imageflow/IO/TemporaryFileProviders.cs index 850a0fa..f7a200a 100644 --- a/src/Imageflow/IO/TemporaryFileProviders.cs +++ b/src/Imageflow/IO/TemporaryFileProviders.cs @@ -1,4 +1,4 @@ -using System.IO.MemoryMappedFiles; +using System.IO.MemoryMappedFiles; namespace Imageflow.IO { @@ -54,20 +54,20 @@ public static ITemporaryFileProvider CreateProvider() { return new TemporaryMemoryFileProvider(); } - + public void Dispose() { _file?.Dispose(); } } - + internal class TemporaryFileProvider : ITemporaryFileProvider { public ITemporaryFile Create(bool cleanup, long capacity) { return new TemporaryFile(Path.GetTempFileName(), cleanup); } - + } internal class TemporaryFile : ITemporaryFile @@ -97,7 +97,7 @@ public Stream ReadFromBeginning() return fs; } - + public static ITemporaryFileProvider CreateProvider() { diff --git a/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs b/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs index a3a0b9d..fde52b8 100644 --- a/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs +++ b/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs @@ -39,7 +39,7 @@ internal static void Throw(string? paramName) => internal static class ObjectDisposedHelper { - public static void ThrowIf([DoesNotReturnIf(true)]bool condition, object instance) + public static void ThrowIf([DoesNotReturnIf(true)] bool condition, object instance) { #if NET8_0_OR_GREATER diff --git a/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs b/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs index 2c7f849..ddb6e01 100644 --- a/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs +++ b/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs @@ -1,4 +1,4 @@ -namespace Imageflow.Internal.Helpers; +namespace Imageflow.Internal.Helpers; internal static class ArraySegmentExtensions { @@ -30,9 +30,9 @@ internal static bool TryGetBufferSliceAllWrittenData(this MemoryStream stream, o segment = default; return false; } - + internal static ReadOnlyMemory GetWrittenMemory(this MemoryStream stream) { return stream.TryGetBufferSliceAllWrittenData(out var segment) ? new ReadOnlyMemory(segment.Array, segment.Offset, segment.Count) : stream.ToArray(); } -} \ No newline at end of file +} diff --git a/src/Imageflow/Internal.Helpers/IAssertReady.cs b/src/Imageflow/Internal.Helpers/IAssertReady.cs index dcc1f40..e919c6d 100644 --- a/src/Imageflow/Internal.Helpers/IAssertReady.cs +++ b/src/Imageflow/Internal.Helpers/IAssertReady.cs @@ -1,4 +1,4 @@ -namespace Imageflow.Internal.Helpers +namespace Imageflow.Internal.Helpers { internal interface IAssertReady { diff --git a/src/Imageflow/Internal.Helpers/OutputDestinationWrapper.cs b/src/Imageflow/Internal.Helpers/OutputDestinationWrapper.cs index d437ad0..42cf26b 100644 --- a/src/Imageflow/Internal.Helpers/OutputDestinationWrapper.cs +++ b/src/Imageflow/Internal.Helpers/OutputDestinationWrapper.cs @@ -1,4 +1,4 @@ -// using System.Buffers; +// using System.Buffers; // using System.Runtime.InteropServices; // // namespace Imageflow.Fluent; diff --git a/src/Imageflow/Internal.Helpers/PinBox.cs b/src/Imageflow/Internal.Helpers/PinBox.cs index 180ee57..7ef996b 100644 --- a/src/Imageflow/Internal.Helpers/PinBox.cs +++ b/src/Imageflow/Internal.Helpers/PinBox.cs @@ -1,4 +1,4 @@ -using System.Runtime.ConstrainedExecution; +using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; namespace Imageflow.Internal.Helpers diff --git a/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs b/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs index 098db8b..928d825 100644 --- a/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs +++ b/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -13,7 +13,7 @@ internal sealed unsafe class SafeHandleMemoryManager : MemoryManager private IntPtr _pointer; private readonly SafeHandle _outerHandle; private readonly SafeHandle? _innerHandle; - + /// /// Use this to create a MemoryManager that keeps a handle forced open until the MemoryManager is disposed. /// @@ -23,7 +23,7 @@ internal sealed unsafe class SafeHandleMemoryManager : MemoryManager /// internal static MemoryManager BorrowFromHandle(SafeHandle handle, IntPtr pointer, uint length) { - return new SafeHandleMemoryManager(handle,null, pointer, length, true); + return new SafeHandleMemoryManager(handle, null, pointer, length, true); } /// /// Use this to create a MemoryManager that keeps two handles forced open until the MemoryManager is disposed. @@ -37,7 +37,7 @@ internal static MemoryManager BorrowFromHandle(SafeHandle handle, IntPtr p /// internal static MemoryManager BorrowFromHandles(SafeHandle outerHandle, SafeHandle innerHandle, IntPtr pointer, uint length) { - return new SafeHandleMemoryManager(outerHandle,innerHandle, pointer, length, true); + return new SafeHandleMemoryManager(outerHandle, innerHandle, pointer, length, true); } private SafeHandleMemoryManager(SafeHandle outerHandle, SafeHandle? innerHandle, IntPtr pointer, uint length, bool addGcPressure) { @@ -107,12 +107,13 @@ protected override void Dispose(bool disposing) // Now release the handle(s) _innerHandle?.DangerousRelease(); _outerHandle.DangerousRelease(); - - if (_length != 0){ + + if (_length != 0) + { GC.RemoveMemoryPressure(_length); } } } - - + + } diff --git a/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs b/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs index c2c54b6..9cb4c80 100644 --- a/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs +++ b/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Runtime.InteropServices; namespace Imageflow.Internal.Helpers; @@ -67,7 +67,7 @@ internal static void WriteMemory(this Stream stream, ReadOnlyMemory buffer } #endif } - + internal static void WriteSpan(this Stream stream, ReadOnlySpan buffer) { #if NETSTANDARD2_1_OR_GREATER @@ -85,4 +85,4 @@ internal static void WriteSpan(this Stream stream, ReadOnlySpan buffer) } #endif } -} \ No newline at end of file +} diff --git a/src/Imageflow/Internal.Helpers/TextHelpers.cs b/src/Imageflow/Internal.Helpers/TextHelpers.cs index d7e9613..dea6467 100644 --- a/src/Imageflow/Internal.Helpers/TextHelpers.cs +++ b/src/Imageflow/Internal.Helpers/TextHelpers.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace Imageflow.Internal.Helpers; @@ -9,16 +9,16 @@ internal static string Utf8ToString(this ReadOnlySpan utf8) #if NETSTANDARD2_1_OR_GREATER return Encoding.UTF8.GetString(utf8); #else - unsafe + unsafe + { + fixed (byte* ptr = utf8) { - fixed (byte* ptr = utf8) - { - return Encoding.UTF8.GetString(ptr, utf8.Length); - } + return Encoding.UTF8.GetString(ptr, utf8.Length); } + } #endif } - + /// /// Returns false if the text contains non-ASCII characters or nulls, or if the buffer is too small (it should be greater than the number of chars). /// @@ -51,7 +51,7 @@ internal static bool TryEncodeAsciiNullTerminatedIntoBuffer(ReadOnlySpan t bytesUsed = 0; return false; } - buffer[i] = (byte) text[i]; + buffer[i] = (byte)text[i]; } buffer[^1] = 0; bytesUsed = text.Length + 1; @@ -59,5 +59,5 @@ internal static bool TryEncodeAsciiNullTerminatedIntoBuffer(ReadOnlySpan t } - -} \ No newline at end of file + +} diff --git a/src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs b/src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs index aa40c46..460274a 100644 --- a/src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs +++ b/src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Runtime.CompilerServices; namespace Imageflow.Internal.Helpers; diff --git a/tests/Imageflow.Test/TestApi.cs b/tests/Imageflow.Test/TestApi.cs index d7466ef..06dc773 100644 --- a/tests/Imageflow.Test/TestApi.cs +++ b/tests/Imageflow.Test/TestApi.cs @@ -1,8 +1,10 @@ -using System.Drawing; -using Xunit; +using System.Drawing; + using Imageflow.Bindings; using Imageflow.Fluent; - using Xunit.Abstractions; + +using Xunit; +using Xunit.Abstractions; namespace Imageflow.Test { @@ -23,14 +25,14 @@ public async void TestGetImageInfoLegacy() "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); var info = await ImageJob.GetImageInfo(new BytesSource(imageBytes)); - + Assert.Equal(1, info.ImageWidth); Assert.Equal(1, info.ImageHeight); Assert.Equal("png", info.PreferredExtension); Assert.Equal("image/png", info.PreferredMimeType); Assert.Equal(PixelFormat.Bgra_32, info.FrameDecodesInto); } - + // test the new GetImageInfoAsync [Fact] public async void TestGetImageInfoAsync() @@ -38,8 +40,8 @@ public async void TestGetImageInfoAsync() var imageBytes = Convert.FromBase64String( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); - var info = await ImageJob.GetImageInfoAsync(new MemorySource(imageBytes),SourceLifetime.NowOwnedAndDisposedByTask); - + var info = await ImageJob.GetImageInfoAsync(new MemorySource(imageBytes), SourceLifetime.NowOwnedAndDisposedByTask); + Assert.Equal(1, info.ImageWidth); Assert.Equal(1, info.ImageHeight); Assert.Equal("png", info.PreferredExtension); @@ -54,8 +56,8 @@ public void TestGetImageInfoSync() var imageBytes = Convert.FromBase64String( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="); - var info = ImageJob.GetImageInfo(new MemorySource(imageBytes),SourceLifetime.NowOwnedAndDisposedByTask); - + var info = ImageJob.GetImageInfo(new MemorySource(imageBytes), SourceLifetime.NowOwnedAndDisposedByTask); + Assert.Equal(1, info.ImageWidth); Assert.Equal(1, info.ImageHeight); Assert.Equal("png", info.PreferredExtension); @@ -81,7 +83,7 @@ public async Task TestBuildJob() Assert.True(r.First.TryGetBytes().HasValue); } } - + [Fact] public async Task TestAllJob() { @@ -98,9 +100,9 @@ public async Task TestAllJob() .Transpose() .CropWhitespace(80, 0.5f) .Distort(30, 20) - .Crop(0,0,10,10) - .Region(-5,-5,10,10, AnyColor.Black) - .RegionPercent(-10f, -10f, 110f, 110f, AnyColor.Transparent) + .Crop(0, 0, 10, 10) + .Region(-5, -5, 10, 10, AnyColor.Black) + .RegionPercent(-10f, -10f, 110f, 110f, AnyColor.Transparent) .BrightnessSrgb(-1f) .ContrastSrgb(1f) .SaturationSrgb(1f) @@ -111,22 +113,22 @@ public async Task TestAllJob() .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Flat) .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Ntsc) .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Ry) - .ExpandCanvas(5,5,5,5,AnyColor.FromHexSrgb("FFEECCFF")) - .FillRectangle(2,2,8,8, AnyColor.Black) + .ExpandCanvas(5, 5, 5, 5, AnyColor.FromHexSrgb("FFEECCFF")) + .FillRectangle(2, 2, 8, 8, AnyColor.Black) .ResizerCommands("width=10&height=10&mode=crop") .RoundAllImageCornersPercent(100, AnyColor.Black) .RoundAllImageCorners(1, AnyColor.Transparent) .ConstrainWithin(5, 5) - .Watermark(new MemorySource(imageBytes), + .Watermark(new MemorySource(imageBytes), new WatermarkOptions() .SetMarginsLayout( - new WatermarkMargins(WatermarkAlign.Image, 1,1,1,1), - WatermarkConstraintMode.Within, - new ConstraintGravity(90,90)) + new WatermarkMargins(WatermarkAlign.Image, 1, 1, 1, 1), + WatermarkConstraintMode.Within, + new ConstraintGravity(90, 90)) .SetOpacity(0.5f) .SetHints(new ResampleHints().SetSharpen(15f, SharpenWhen.Always)) - .SetMinCanvasSize(1,1)) - .EncodeToBytes(new MozJpegEncoder(80,true)) + .SetMinCanvasSize(1, 1)) + .EncodeToBytes(new MozJpegEncoder(80, true)) .Finish().InProcessAsync(); Assert.Equal(5, r.First!.Width); @@ -140,7 +142,7 @@ public async Task TestConstraints() using (var b = new ImageJob()) { var r = await b.Decode(imageBytes). - Constrain(new Constraint(ConstraintMode.Fit_Crop,10,20) + Constrain(new Constraint(ConstraintMode.Fit_Crop, 10, 20) { CanvasColor = null, H = 20, @@ -150,7 +152,7 @@ public async Task TestConstraints() InterpolationColorspace = ScalingFloatspace.Linear, DownFilter = InterpolationFilter.Mitchell, ResampleWhen = ResampleWhen.Size_Differs_Or_Sharpening_Requested, - SharpenWhen = SharpenWhen.Always, + SharpenWhen = SharpenWhen.Always, SharpenPercent = 15, UpFilter = InterpolationFilter.Ginseng }, @@ -176,8 +178,8 @@ public async Task TestMultipleOutputs() .Branch(f => f.ConstrainWithin(40, 30).EncodeToBytes(new WebPLossyEncoder(50))) .EncodeToBytes(new LodePngEncoder()) .Finish().InProcessAsync(); - - + + Assert.Equal(60, r.TryGet(1)!.Width); Assert.Equal(30, r.TryGet(2)!.Width); Assert.Equal(120, r.TryGet(3)!.Width); @@ -194,13 +196,13 @@ public async Task TestMultipleInputs() var canvas = b.Decode(imageBytes) .Distort(30, 30); - + var r = await b.Decode(imageBytes) .Distort(20, 20) .FillRectangle(5, 5, 15, 15, AnyColor.FromHexSrgb("FFEECC")) .TransparencySrgb(0.5f) - .DrawImageExactTo(canvas, - new Rectangle(5,5,25,25), + .DrawImageExactTo(canvas, + new Rectangle(5, 5, 25, 25), new ResampleHints(), CompositingMode.Compose) .EncodeToBytes(new LodePngEncoder()) @@ -212,7 +214,7 @@ public async Task TestMultipleInputs() } - + @@ -230,7 +232,7 @@ public async Task TestJobWithCommandString() } } - + [Fact] public async Task TestEncodeSizeLimit() { @@ -245,13 +247,13 @@ await b.Decode(imageBytes) .Finish() .SetSecurityOptions(new SecurityOptions().SetMaxEncodeSize(new FrameSizeLimit(1, 1, 1))) .InProcessAsync(); - + }); - Assert.StartsWith("ArgumentInvalid: SizeLimitExceeded: Frame width 3 exceeds max_encode_size.w",e.Message); + Assert.StartsWith("ArgumentInvalid: SizeLimitExceeded: Frame width 3 exceeds max_encode_size.w", e.Message); } } - + [Fact] public async Task TestBuildCommandString() @@ -260,7 +262,7 @@ public async Task TestBuildCommandString() // We wrap the job in a using() statement to free memory faster using (var b = new ImageJob()) { - + var r = await b.BuildCommandString( new MemorySource(imageBytes), // or new StreamSource(Stream stream, bool disposeStream) new BytesDestination(), // or new StreamDestination @@ -273,7 +275,7 @@ public async Task TestBuildCommandString() Assert.True(r.First.TryGetBytes().HasValue); } } - + [Fact] [Obsolete("Obsolete")] public async Task TestBuildCommandStringWithWatermarksLegacy() @@ -283,12 +285,12 @@ public async Task TestBuildCommandStringWithWatermarksLegacy() { var watermarks = new List(); watermarks.Add(new InputWatermark(new BytesSource(imageBytes), new WatermarkOptions())); - watermarks.Add(new InputWatermark(new BytesSource(imageBytes), new WatermarkOptions().SetGravity(new ConstraintGravity(100,100)))); - + watermarks.Add(new InputWatermark(new BytesSource(imageBytes), new WatermarkOptions().SetGravity(new ConstraintGravity(100, 100)))); + var r = await b.BuildCommandString( - new BytesSource(imageBytes), - new BytesDestination(), - "width=3&height=2&mode=stretch&scale=both&format=webp",watermarks).Finish().InProcessAsync(); + new BytesSource(imageBytes), + new BytesDestination(), + "width=3&height=2&mode=stretch&scale=both&format=webp", watermarks).Finish().InProcessAsync(); Assert.Equal(3, r.First!.Width); Assert.Equal("webp", r.First.PreferredExtension); @@ -296,7 +298,7 @@ public async Task TestBuildCommandStringWithWatermarksLegacy() } } - + [Fact] public async Task TestBuildCommandStringWithWatermarks() { @@ -305,12 +307,12 @@ public async Task TestBuildCommandStringWithWatermarks() { var watermarks = new List(); watermarks.Add(new InputWatermark(new MemorySource(imageBytes), new WatermarkOptions())); - watermarks.Add(new InputWatermark(new MemorySource(imageBytes), new WatermarkOptions().SetGravity(new ConstraintGravity(100,100)))); - + watermarks.Add(new InputWatermark(new MemorySource(imageBytes), new WatermarkOptions().SetGravity(new ConstraintGravity(100, 100)))); + var r = await b.BuildCommandString( - new MemorySource(imageBytes), - new BytesDestination(), - "width=3&height=2&mode=stretch&scale=both&format=webp",watermarks).Finish().InProcessAsync(); + new MemorySource(imageBytes), + new BytesDestination(), + "width=3&height=2&mode=stretch&scale=both&format=webp", watermarks).Finish().InProcessAsync(); Assert.Equal(3, r.First!.Width); Assert.Equal("webp", r.First.PreferredExtension); @@ -333,13 +335,13 @@ public async Task TestBuildCommandStringWithStreamsAndWatermarksLegacy() using (var b = new ImageJob()) { var watermarks = new List(); - watermarks.Add(new InputWatermark(new StreamSource(stream1,true), new WatermarkOptions())); - watermarks.Add(new InputWatermark(new StreamSource(stream2,true), new WatermarkOptions().SetGravity(new ConstraintGravity(100,100)))); - + watermarks.Add(new InputWatermark(new StreamSource(stream1, true), new WatermarkOptions())); + watermarks.Add(new InputWatermark(new StreamSource(stream2, true), new WatermarkOptions().SetGravity(new ConstraintGravity(100, 100)))); + var r = await b.BuildCommandString( - new StreamSource(stream3,true), - new BytesDestination(), - "width=3&height=2&mode=stretch&scale=both&format=webp",watermarks).Finish().InProcessAsync(); + new StreamSource(stream3, true), + new BytesDestination(), + "width=3&height=2&mode=stretch&scale=both&format=webp", watermarks).Finish().InProcessAsync(); Assert.Equal(3, r.First!.Width); Assert.Equal("webp", r.First.PreferredExtension); @@ -347,7 +349,7 @@ public async Task TestBuildCommandStringWithStreamsAndWatermarksLegacy() } } - + [Fact] public async Task TestBuildCommandStringWithStreamsAndWatermarks() { @@ -362,12 +364,12 @@ public async Task TestBuildCommandStringWithStreamsAndWatermarks() using var b = new ImageJob(); var watermarks = new List(); watermarks.Add(new InputWatermark(BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream1), new WatermarkOptions())); - watermarks.Add(new InputWatermark(BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream2), new WatermarkOptions().SetGravity(new ConstraintGravity(100,100)))); - + watermarks.Add(new InputWatermark(BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream2), new WatermarkOptions().SetGravity(new ConstraintGravity(100, 100)))); + var r = await b.BuildCommandString( - BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream3), - new BytesDestination(), - "width=3&height=2&mode=stretch&scale=both&format=webp",watermarks).Finish().InProcessAsync(); + BufferedStreamSource.UseEntireStreamAndDisposeWithSource(stream3), + new BytesDestination(), + "width=3&height=2&mode=stretch&scale=both&format=webp", watermarks).Finish().InProcessAsync(); Assert.Equal(3, r.First!.Width); Assert.Equal("webp", r.First.PreferredExtension); @@ -441,7 +443,7 @@ public async Task TestBuildJobSubprocess() .Finish() .WithCancellationTimeout(2000) .InSubprocessAsync(imageflowTool); - + // ExecutableLocator.FindExecutable("imageflow_tool", new [] {"/home/n/Documents/imazen/imageflow/target/release/"}) Assert.Equal(5, r.First!.Width); @@ -449,7 +451,7 @@ public async Task TestBuildJobSubprocess() } } } - + [Fact] public async Task TestCustomDownscalingAndDecodeEncodeResults() { @@ -473,7 +475,7 @@ public async Task TestCustomDownscalingAndDecodeEncodeResults() Assert.Equal(1, r.DecodeResults.First().Height); Assert.Equal("png", r.DecodeResults.First().PreferredExtension); Assert.Equal("image/png", r.DecodeResults.First().PreferredMimeType); - + Assert.Equal(5, r.EncodeResults.First().Width); Assert.Equal(3, r.EncodeResults.First().Height); Assert.Equal("png", r.EncodeResults.First().PreferredExtension); @@ -481,8 +483,8 @@ public async Task TestCustomDownscalingAndDecodeEncodeResults() } } - - + + [Fact] public void TestContentTypeDetection() { @@ -491,23 +493,23 @@ public void TestContentTypeDetection() Assert.Equal("image/png", ImageJob.GetContentTypeForBytes(pngBytes)); Assert.True(ImageJob.CanDecodeBytes(pngBytes)); - - var jpegBytes = new byte[] {0xFF,0xD8,0xFF,0,0,0,0,0,0,0,0,0}; + + var jpegBytes = new byte[] { 0xFF, 0xD8, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; Assert.Equal("image/jpeg", ImageJob.GetContentTypeForBytes(jpegBytes)); Assert.True(ImageJob.CanDecodeBytes(jpegBytes)); - var gifBytes = new byte[] {(byte)'G', (byte)'I', (byte)'F',(byte)'8', (byte)'9', (byte)'a',0,0,0,0,0,0,0}; + var gifBytes = new byte[] { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a', 0, 0, 0, 0, 0, 0, 0 }; Assert.Equal("image/gif", ImageJob.GetContentTypeForBytes(gifBytes)); Assert.True(ImageJob.CanDecodeBytes(gifBytes)); - - var webpBytes = new byte[] {(byte)'R',(byte)'I',(byte)'F',(byte)'F',0,0,0,0,(byte)'W',(byte)'E',(byte)'B',(byte)'P'}; + + var webpBytes = new byte[] { (byte)'R', (byte)'I', (byte)'F', (byte)'F', 0, 0, 0, 0, (byte)'W', (byte)'E', (byte)'B', (byte)'P' }; Assert.Equal("image/webp", ImageJob.GetContentTypeForBytes(webpBytes)); Assert.True(ImageJob.CanDecodeBytes(webpBytes)); - - var nonsenseBytes = new byte[] {(byte)'A', (byte)'B', (byte)'C',(byte)'D', (byte)'E', (byte)'F',0,0,0,0,0,0,0}; + + var nonsenseBytes = new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', 0, 0, 0, 0, 0, 0, 0 }; Assert.Null(ImageJob.GetContentTypeForBytes(nonsenseBytes)); Assert.False(ImageJob.CanDecodeBytes(nonsenseBytes)); #pragma warning restore CS0618 // Type or member is obsolete } } -} \ No newline at end of file +} diff --git a/tests/Imageflow.Test/TestExamples.cs b/tests/Imageflow.Test/TestExamples.cs index f7b2b66..ac49ece 100644 --- a/tests/Imageflow.Test/TestExamples.cs +++ b/tests/Imageflow.Test/TestExamples.cs @@ -2,6 +2,6 @@ namespace Imageflow.Test { public class TestExamples { - + } -} \ No newline at end of file +} diff --git a/tests/Imageflow.Test/TestJobContext.cs b/tests/Imageflow.Test/TestJobContext.cs index 70205d9..2d853c3 100644 --- a/tests/Imageflow.Test/TestJobContext.cs +++ b/tests/Imageflow.Test/TestJobContext.cs @@ -1,10 +1,14 @@ using System.Text; using System.Text.Json.Nodes; -using Xunit; + using Imageflow.Bindings; using Imageflow.Fluent; + using Newtonsoft.Json; + +using Xunit; using Xunit.Abstractions; + using JsonNamingPolicy = System.Text.Json.JsonNamingPolicy; using JsonSerializerOptions = System.Text.Json.JsonSerializerOptions; #pragma warning disable CS0618 // Type or member is obsolete @@ -15,29 +19,29 @@ public static class IJsonResponseProviderExtensions { - [Obsolete("Use DeserializeJsonNode() instead")] - public static T? Deserialize(this IJsonResponseProvider p) where T : class - { - using var readStream = p.GetStream(); - using var ms = new MemoryStream(readStream.CanSeek ? (int)readStream.Length : 0); - readStream.CopyTo(ms); - var allBytes = ms.ToArray(); - var options = new JsonSerializerOptions - { + [Obsolete("Use DeserializeJsonNode() instead")] + public static T? Deserialize(this IJsonResponseProvider p) where T : class + { + using var readStream = p.GetStream(); + using var ms = new MemoryStream(readStream.CanSeek ? (int)readStream.Length : 0); + readStream.CopyTo(ms); + var allBytes = ms.ToArray(); + var options = new JsonSerializerOptions + { #if NET8_0_OR_GREATER - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower -#endif - }; - var v = System.Text.Json.JsonSerializer.Deserialize(allBytes, options); - return v; + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower +#endif + }; + var v = System.Text.Json.JsonSerializer.Deserialize(allBytes, options); + return v; + } + + [Obsolete("Use Deserialize or DeserializeJsonNode() instead")] + public static dynamic? DeserializeDynamic(this IJsonResponseProvider p) + { + using var reader = new StreamReader(p.GetStream(), Encoding.UTF8); + return JsonSerializer.Create().Deserialize(new JsonTextReader(reader)); } - - [Obsolete("Use Deserialize or DeserializeJsonNode() instead")] - public static dynamic? DeserializeDynamic(this IJsonResponseProvider p) - { - using var reader = new StreamReader(p.GetStream(), Encoding.UTF8); - return JsonSerializer.Create().Deserialize(new JsonTextReader(reader)); - } } public class TestContext @@ -57,8 +61,8 @@ public void TestCreateDestroyContext() c.AssertReady(); } } - - [Fact] + + [Fact] public void TestGetImageInfoMessage() { using (var c = new JobContext()) @@ -66,19 +70,19 @@ public void TestGetImageInfoMessage() c.AddInputBytesPinned(0, Convert.FromBase64String( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==")); - + var response = c.SendMessage("v0.1/get_image_info", //new {io_id = 0}); new JsonObject() { ["io_id"] = 0 - }); + }); dynamic data = response.DeserializeDynamic()!; _output.WriteLine(response.GetString()); - Assert.Equal(200, (int)data.code ); + Assert.Equal(200, (int)data.code); Assert.True((bool)data.success); Assert.Equal(1, (int)data.data.image_info.image_width); Assert.Equal(1, (int)data.data.image_info.image_height); @@ -86,7 +90,7 @@ public void TestGetImageInfoMessage() Assert.Equal("png", (string)data.data.image_info.preferred_extension); } } - + [Fact] public void TestGetImageInfo() { @@ -103,7 +107,7 @@ public void TestGetImageInfo() Assert.Equal(PixelFormat.Bgra_32, result.FrameDecodesInto); } } - + [Fact] public void TestGetVersionInfo() { @@ -118,10 +122,10 @@ public void TestGetVersionInfo() var unused2 = info.GitTag; Assert.NotNull(info.GitTag); var unused3 = info.DirtyWorkingTree; - + } } - + [Fact] public void TestExecute() { @@ -130,9 +134,9 @@ public void TestExecute() c.AddInputBytesPinned(0, Convert.FromBase64String( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==")); - + c.AddOutputBuffer(1); - + var message = new { framewise = new @@ -164,7 +168,7 @@ public void TestExecute() } } }; - + var response = c.SendMessage("v0.1/execute", message); dynamic data = response.DeserializeDynamic()!; @@ -175,8 +179,8 @@ public void TestExecute() Assert.True((bool)data.success); } } - - + + [Fact] public void TestIr4Execute() { @@ -196,7 +200,7 @@ public void TestIr4Execute() Assert.True((bool)data.success); } } - + [Fact] public void TestIr4Build() { @@ -247,10 +251,10 @@ public void TestIr4Build() _output.WriteLine(response.GetString()); - Assert.Equal(200, (int) data.code); - Assert.True((bool) data.success); + Assert.Equal(200, (int)data.code); + Assert.True((bool)data.success); } } - + } } diff --git a/tests/Imageflow.Test/TestJson.cs b/tests/Imageflow.Test/TestJson.cs index c402dfb..db9e07f 100644 --- a/tests/Imageflow.Test/TestJson.cs +++ b/tests/Imageflow.Test/TestJson.cs @@ -1,5 +1,7 @@ -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; + using Imageflow.Fluent; + using Xunit; using Xunit.Abstractions; @@ -15,133 +17,134 @@ public TestJson(ITestOutputHelper testOutputHelper) } [Fact] - public void TestAllJob() - { - - - var b = new ImageJob(); - var jsonStr = b.Decode(Array.Empty()) - .FlipVertical() - .FlipHorizontal() - .Rotate90() - .Rotate180() - .Rotate270() - .Transpose() - .CropWhitespace(80, 0.5f) - .Distort(30, 20) - .Crop(0, 0, 10, 10) - .Region(-5, -5, 10, 10, AnyColor.Black) - .RegionPercent(-10f, -10f, 110f, 110f, AnyColor.Transparent) - .BrightnessSrgb(-1f) - .ContrastSrgb(1f) - .SaturationSrgb(1f) - .WhiteBalanceSrgb(80) - .ColorFilterSrgb(ColorFilterSrgb.Invert) - .ColorFilterSrgb(ColorFilterSrgb.Sepia) - .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Bt709) - .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Flat) - .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Ntsc) - .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Ry) - .ExpandCanvas(5, 5, 5, 5, AnyColor.FromHexSrgb("FFEECCFF")) - .FillRectangle(2, 2, 8, 8, AnyColor.Black) - .ResizerCommands("width=10&height=10&mode=crop") - .RoundAllImageCornersPercent(100, AnyColor.Black) - .RoundAllImageCorners(1, AnyColor.Transparent) - .ConstrainWithin(5, 5) - .Watermark(new MemorySource(new byte[]{}), - new WatermarkOptions() - .SetMarginsLayout( - new WatermarkMargins(WatermarkAlign.Image, 1, 1, 1, 1), - WatermarkConstraintMode.Within, - new ConstraintGravity(90, 90)) - .SetOpacity(0.5f) - .SetHints(new ResampleHints().SetSharpen(15f, SharpenWhen.Always)) - .SetMinCanvasSize(1, 1)) - .EncodeToBytes(new MozJpegEncoder(80, true)) - .Finish().ToJsonDebug(); - var expected = - """{"security":null,"framewise":{"graph":{"edges":[{"from":0,"to":1,"kind":"input"},{"from":1,"to":2,"kind":"input"},{"from":2,"to":3,"kind":"input"},{"from":3,"to":4,"kind":"input"},{"from":4,"to":5,"kind":"input"},{"from":5,"to":6,"kind":"input"},{"from":6,"to":7,"kind":"input"},{"from":7,"to":8,"kind":"input"},{"from":8,"to":9,"kind":"input"},{"from":9,"to":10,"kind":"input"},{"from":10,"to":11,"kind":"input"},{"from":11,"to":12,"kind":"input"},{"from":12,"to":13,"kind":"input"},{"from":13,"to":14,"kind":"input"},{"from":14,"to":15,"kind":"input"},{"from":15,"to":16,"kind":"input"},{"from":16,"to":17,"kind":"input"},{"from":17,"to":18,"kind":"input"},{"from":18,"to":19,"kind":"input"},{"from":19,"to":20,"kind":"input"},{"from":20,"to":21,"kind":"input"},{"from":21,"to":22,"kind":"input"},{"from":22,"to":23,"kind":"input"},{"from":23,"to":24,"kind":"input"},{"from":24,"to":25,"kind":"input"},{"from":25,"to":26,"kind":"input"},{"from":26,"to":27,"kind":"input"},{"from":27,"to":28,"kind":"input"},{"from":28,"to":29,"kind":"input"}],"nodes":{"0":{"decode":{"io_id":0}},"1":{"flip_v":null},"2":{"flip_h":null},"3":{"rotate_90":null},"4":{"rotate_180":null},"5":{"rotate_270":null},"6":{"transpose":null},"7":{"crop_whitespace":{"threshold":80,"percent_padding":0.5}},"8":{"resample_2d":{"w":30,"h":20,"hints":null}},"9":{"crop":{"x1":0,"y1":0,"x2":10,"y2":10}},"10":{"region":{"x1":-5,"y1":-5,"x2":10,"y2":10,"background_color":{"black":null}}},"11":{"region_percent":{"x1":-10.0,"y1":-10.0,"x2":110.0,"y2":110.0,"background_color":{"transparent":null}}},"12":{"color_filter_srgb":{"brightness":-1.0}},"13":{"color_filter_srgb":{"contrast":1.0}},"14":{"color_filter_srgb":{"saturation":1.0}},"15":{"white_balance_histogram_area_threshold_srgb":{"threshold":80}},"16":{"color_filter_srgb":"invert"},"17":{"color_filter_srgb":"sepia"},"18":{"color_filter_srgb":"grayscale_bt709"},"19":{"color_filter_srgb":"grayscale_flat"},"20":{"color_filter_srgb":"grayscale_ntsc"},"21":{"color_filter_srgb":"grayscale_ry"},"22":{"expand_canvas":{"left":5,"top":5,"right":5,"bottom":5,"color":{"srgb":{"hex":"ffeecc"}}}},"23":{"fill_rect":{"x1":2,"y1":2,"x2":8,"y2":8,"color":{"black":null}}},"24":{"command_string":{"kind":"ir4","value":"width=10&height=10&mode=crop"}},"25":{"round_image_corners":{"radius":{"percentage":100.0},"background_color":{"black":null}}},"26":{"round_image_corners":{"radius":{"pixels":1},"background_color":{"transparent":null}}},"27":{"constrain":{"mode":"within","w":5,"h":5}},"28":{"watermark":{"io_id":1,"gravity":{"percentage":{"x":90.0,"y":90.0}},"fit_box":{"image_margins":{"left":1,"top":1,"right":1,"bottom":1}},"fit_mode":"within","min_canvas_width":1,"min_canvas_height":1,"opacity":0.5,"hints":{"sharpen_percent":15.0,"down_filter":null,"up_filter":null,"scaling_colorspace":null,"resample_when":null,"sharpen_when":"always"}}},"29":{"encode":{"io_id":2,"preset":{"mozjpeg":{"quality":80,"progressive":true,"matte":null}}}}}}}}"""; - // parse and reformat both before comparing - - var expectedJson = SortPropertiesRecursive(JsonNode.Parse(expected))!.ToString(); - var actualJson = SortPropertiesRecursive(JsonNode.Parse(jsonStr))!.ToString(); - try - { - Assert.Equal(expectedJson, actualJson); - }catch (Exception e) - { - Console.Error.WriteLine("Expected: " + expectedJson); - Console.Error.WriteLine("Actual: " + actualJson); - // Don't throw on CI - if (Environment.GetEnvironmentVariable("CI") == null) - throw; - else - Console.Error.WriteLine(e.ToString()); - } - - // For using SystemTextJson.JsonDiffPatch, which fails to diff rn, and also requires Json 8.0 - // var expectedJson = - // JsonNode.Parse(SortPropertiesRecursive(JsonNode.Parse(expected))!.ToString()); //JsonNode.Parse(expected); - // var actualJson = - // JsonNode.Parse(SortPropertiesRecursive(JsonNode.Parse(jsonStr))!.ToString()); //JsonNode.Parse(jsonStr); - // try - // { - // JsonAssert.Equal(expectedJson, actualJson); - // - // } - // catch (Exception e) - // { - // Console.Error.WriteLine("Expected: " + expectedJson); - // Console.Error.WriteLine("Actual: " + actualJson); - // // Don't throw on CI - // if (Environment.GetEnvironmentVariable("CI") == null) - // throw; - // else - // Console.Error.WriteLine(e.ToString()); - // } + public void TestAllJob() + { + + + var b = new ImageJob(); + var jsonStr = b.Decode(Array.Empty()) + .FlipVertical() + .FlipHorizontal() + .Rotate90() + .Rotate180() + .Rotate270() + .Transpose() + .CropWhitespace(80, 0.5f) + .Distort(30, 20) + .Crop(0, 0, 10, 10) + .Region(-5, -5, 10, 10, AnyColor.Black) + .RegionPercent(-10f, -10f, 110f, 110f, AnyColor.Transparent) + .BrightnessSrgb(-1f) + .ContrastSrgb(1f) + .SaturationSrgb(1f) + .WhiteBalanceSrgb(80) + .ColorFilterSrgb(ColorFilterSrgb.Invert) + .ColorFilterSrgb(ColorFilterSrgb.Sepia) + .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Bt709) + .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Flat) + .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Ntsc) + .ColorFilterSrgb(ColorFilterSrgb.Grayscale_Ry) + .ExpandCanvas(5, 5, 5, 5, AnyColor.FromHexSrgb("FFEECCFF")) + .FillRectangle(2, 2, 8, 8, AnyColor.Black) + .ResizerCommands("width=10&height=10&mode=crop") + .RoundAllImageCornersPercent(100, AnyColor.Black) + .RoundAllImageCorners(1, AnyColor.Transparent) + .ConstrainWithin(5, 5) + .Watermark(new MemorySource(new byte[] { }), + new WatermarkOptions() + .SetMarginsLayout( + new WatermarkMargins(WatermarkAlign.Image, 1, 1, 1, 1), + WatermarkConstraintMode.Within, + new ConstraintGravity(90, 90)) + .SetOpacity(0.5f) + .SetHints(new ResampleHints().SetSharpen(15f, SharpenWhen.Always)) + .SetMinCanvasSize(1, 1)) + .EncodeToBytes(new MozJpegEncoder(80, true)) + .Finish().ToJsonDebug(); + var expected = + """{"security":null,"framewise":{"graph":{"edges":[{"from":0,"to":1,"kind":"input"},{"from":1,"to":2,"kind":"input"},{"from":2,"to":3,"kind":"input"},{"from":3,"to":4,"kind":"input"},{"from":4,"to":5,"kind":"input"},{"from":5,"to":6,"kind":"input"},{"from":6,"to":7,"kind":"input"},{"from":7,"to":8,"kind":"input"},{"from":8,"to":9,"kind":"input"},{"from":9,"to":10,"kind":"input"},{"from":10,"to":11,"kind":"input"},{"from":11,"to":12,"kind":"input"},{"from":12,"to":13,"kind":"input"},{"from":13,"to":14,"kind":"input"},{"from":14,"to":15,"kind":"input"},{"from":15,"to":16,"kind":"input"},{"from":16,"to":17,"kind":"input"},{"from":17,"to":18,"kind":"input"},{"from":18,"to":19,"kind":"input"},{"from":19,"to":20,"kind":"input"},{"from":20,"to":21,"kind":"input"},{"from":21,"to":22,"kind":"input"},{"from":22,"to":23,"kind":"input"},{"from":23,"to":24,"kind":"input"},{"from":24,"to":25,"kind":"input"},{"from":25,"to":26,"kind":"input"},{"from":26,"to":27,"kind":"input"},{"from":27,"to":28,"kind":"input"},{"from":28,"to":29,"kind":"input"}],"nodes":{"0":{"decode":{"io_id":0}},"1":{"flip_v":null},"2":{"flip_h":null},"3":{"rotate_90":null},"4":{"rotate_180":null},"5":{"rotate_270":null},"6":{"transpose":null},"7":{"crop_whitespace":{"threshold":80,"percent_padding":0.5}},"8":{"resample_2d":{"w":30,"h":20,"hints":null}},"9":{"crop":{"x1":0,"y1":0,"x2":10,"y2":10}},"10":{"region":{"x1":-5,"y1":-5,"x2":10,"y2":10,"background_color":{"black":null}}},"11":{"region_percent":{"x1":-10.0,"y1":-10.0,"x2":110.0,"y2":110.0,"background_color":{"transparent":null}}},"12":{"color_filter_srgb":{"brightness":-1.0}},"13":{"color_filter_srgb":{"contrast":1.0}},"14":{"color_filter_srgb":{"saturation":1.0}},"15":{"white_balance_histogram_area_threshold_srgb":{"threshold":80}},"16":{"color_filter_srgb":"invert"},"17":{"color_filter_srgb":"sepia"},"18":{"color_filter_srgb":"grayscale_bt709"},"19":{"color_filter_srgb":"grayscale_flat"},"20":{"color_filter_srgb":"grayscale_ntsc"},"21":{"color_filter_srgb":"grayscale_ry"},"22":{"expand_canvas":{"left":5,"top":5,"right":5,"bottom":5,"color":{"srgb":{"hex":"ffeecc"}}}},"23":{"fill_rect":{"x1":2,"y1":2,"x2":8,"y2":8,"color":{"black":null}}},"24":{"command_string":{"kind":"ir4","value":"width=10&height=10&mode=crop"}},"25":{"round_image_corners":{"radius":{"percentage":100.0},"background_color":{"black":null}}},"26":{"round_image_corners":{"radius":{"pixels":1},"background_color":{"transparent":null}}},"27":{"constrain":{"mode":"within","w":5,"h":5}},"28":{"watermark":{"io_id":1,"gravity":{"percentage":{"x":90.0,"y":90.0}},"fit_box":{"image_margins":{"left":1,"top":1,"right":1,"bottom":1}},"fit_mode":"within","min_canvas_width":1,"min_canvas_height":1,"opacity":0.5,"hints":{"sharpen_percent":15.0,"down_filter":null,"up_filter":null,"scaling_colorspace":null,"resample_when":null,"sharpen_when":"always"}}},"29":{"encode":{"io_id":2,"preset":{"mozjpeg":{"quality":80,"progressive":true,"matte":null}}}}}}}}"""; + // parse and reformat both before comparing + + var expectedJson = SortPropertiesRecursive(JsonNode.Parse(expected))!.ToString(); + var actualJson = SortPropertiesRecursive(JsonNode.Parse(jsonStr))!.ToString(); + try + { + Assert.Equal(expectedJson, actualJson); } + catch (Exception e) + { + Console.Error.WriteLine("Expected: " + expectedJson); + Console.Error.WriteLine("Actual: " + actualJson); + // Don't throw on CI + if (Environment.GetEnvironmentVariable("CI") == null) + throw; + else + Console.Error.WriteLine(e.ToString()); + } + + // For using SystemTextJson.JsonDiffPatch, which fails to diff rn, and also requires Json 8.0 + // var expectedJson = + // JsonNode.Parse(SortPropertiesRecursive(JsonNode.Parse(expected))!.ToString()); //JsonNode.Parse(expected); + // var actualJson = + // JsonNode.Parse(SortPropertiesRecursive(JsonNode.Parse(jsonStr))!.ToString()); //JsonNode.Parse(jsonStr); + // try + // { + // JsonAssert.Equal(expectedJson, actualJson); + // + // } + // catch (Exception e) + // { + // Console.Error.WriteLine("Expected: " + expectedJson); + // Console.Error.WriteLine("Actual: " + actualJson); + // // Don't throw on CI + // if (Environment.GetEnvironmentVariable("CI") == null) + // throw; + // else + // Console.Error.WriteLine(e.ToString()); + // } + } - private static JsonNode? SortPropertiesRecursive(JsonNode? n) + private static JsonNode? SortPropertiesRecursive(JsonNode? n) + { + if (n is JsonObject o) { - if (n is JsonObject o) + var sorted = new SortedDictionary(); + foreach (var pair in o) { - var sorted = new SortedDictionary(); - foreach (var pair in o) - { - var v = SortPropertiesRecursive(pair.Value); - if (v != null) sorted.Add(pair.Key, v); - } - - return new JsonObject(sorted); + var v = SortPropertiesRecursive(pair.Value); + if (v != null) sorted.Add(pair.Key, v); } - if (n is JsonArray a) - { - var sorted = new List(); - foreach (var value in a) - { - var v = SortPropertiesRecursive(value); - if (v != null) sorted.Add(v); - } + return new JsonObject(sorted); + } - return new JsonArray(sorted.ToArray()); - } - if (n is JsonValue v2) + if (n is JsonArray a) + { + var sorted = new List(); + foreach (var value in a) { - // if a string, trim ".0" - if (v2.TryGetValue(out var v)) - { - // if even, convert to int - if (v % 1 == 0) - return JsonValue.Create((int)v); - } - return JsonNode.Parse(v2.ToJsonString()); + var v = SortPropertiesRecursive(value); + if (v != null) sorted.Add(v); } - if (n is null) + + return new JsonArray(sorted.ToArray()); + } + if (n is JsonValue v2) + { + // if a string, trim ".0" + if (v2.TryGetValue(out var v)) { - return null; + // if even, convert to int + if (v % 1 == 0) + return JsonValue.Create((int)v); } - throw new Exception("Unexpected node type: " + n?.GetType()); + return JsonNode.Parse(v2.ToJsonString()); + } + if (n is null) + { + return null; } - -} \ No newline at end of file + throw new Exception("Unexpected node type: " + n?.GetType()); + } + +} diff --git a/tests/Imageflow.Test/TestSrgbColor.cs b/tests/Imageflow.Test/TestSrgbColor.cs index cead1f6..6480e98 100644 --- a/tests/Imageflow.Test/TestSrgbColor.cs +++ b/tests/Imageflow.Test/TestSrgbColor.cs @@ -1,14 +1,15 @@ -using Xunit; using Imageflow.Fluent; + +using Xunit; namespace Imageflow.Test { public class TestSrgbColor { - + [Fact] public void TestFromHex() { - foreach (var color in new [] {"1234", "11223344"}) + foreach (var color in new[] { "1234", "11223344" }) { var parsed = SrgbColor.FromHex(color); Assert.Equal("11", $"{parsed.R:x2}"); @@ -17,7 +18,7 @@ public void TestFromHex() Assert.Equal("44", $"{parsed.A:x2}"); } - foreach (var color in new [] {"123", "112233"}) + foreach (var color in new[] { "123", "112233" }) { var parsed = SrgbColor.FromHex(color); Assert.Equal("11", $"{parsed.R:x2}"); @@ -26,7 +27,7 @@ public void TestFromHex() Assert.Equal("ff", $"{parsed.A:x2}"); } } - + [Fact] public void TestRoundTrip() { @@ -36,4 +37,4 @@ public void TestRoundTrip() Assert.Equal("112233", SrgbColor.FromHex("123").ToHexUnprefixed()); } } -} \ No newline at end of file +} diff --git a/tests/Imageflow.Test/TestStreams.cs b/tests/Imageflow.Test/TestStreams.cs index d771f60..3038d06 100644 --- a/tests/Imageflow.Test/TestStreams.cs +++ b/tests/Imageflow.Test/TestStreams.cs @@ -1,7 +1,9 @@ -namespace Imageflow.Test; +namespace Imageflow.Test; using System; using System.IO; + using Microsoft.IO; + using Xunit; public class MemoryStreamTests @@ -51,4 +53,3 @@ public void TryGetBuffer_DoesNotReturnMoreBytesThanWritten(Type streamType) Assert.Equal(data.Length, buffer.Count); // Ensures no more bytes are reported than actually written } } - diff --git a/tests/Imageflow.TestDotNetFull/TestDotNetClassicLibraryLoading.cs b/tests/Imageflow.TestDotNetFull/TestDotNetClassicLibraryLoading.cs index b503542..cc3ca03 100644 --- a/tests/Imageflow.TestDotNetFull/TestDotNetClassicLibraryLoading.cs +++ b/tests/Imageflow.TestDotNetFull/TestDotNetClassicLibraryLoading.cs @@ -1,4 +1,4 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Imageflow.TestDotNetFull { diff --git a/tests/Imageflow.TestDotNetFullPackageReference/TestDotNetClassicPackageReferenceLibraryLoading.cs b/tests/Imageflow.TestDotNetFullPackageReference/TestDotNetClassicPackageReferenceLibraryLoading.cs index f1e2125..49298c3 100644 --- a/tests/Imageflow.TestDotNetFullPackageReference/TestDotNetClassicPackageReferenceLibraryLoading.cs +++ b/tests/Imageflow.TestDotNetFullPackageReference/TestDotNetClassicPackageReferenceLibraryLoading.cs @@ -1,4 +1,4 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Imageflow.TestDotNetFullPackageReference { diff --git a/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json b/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json index e5ac0f2..003caf4 100644 --- a/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json +++ b/tests/Imageflow.TestDotNetFullPackageReference/packages.lock.json @@ -221,94 +221,6 @@ "System.Text.Json": "[6.*, )" } } - }, - ".NETFramework,Version=v4.7.2/win": { - "Imageflow.NativeRuntime.osx-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" - }, - "Imageflow.NativeRuntime.ubuntu-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" - }, - "Imageflow.NativeRuntime.win-x86": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" - }, - "Imageflow.NativeRuntime.win-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" - } - }, - ".NETFramework,Version=v4.7.2/win-arm64": { - "Imageflow.NativeRuntime.osx-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" - }, - "Imageflow.NativeRuntime.ubuntu-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" - }, - "Imageflow.NativeRuntime.win-x86": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" - }, - "Imageflow.NativeRuntime.win-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" - } - }, - ".NETFramework,Version=v4.7.2/win-x64": { - "Imageflow.NativeRuntime.osx-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" - }, - "Imageflow.NativeRuntime.ubuntu-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" - }, - "Imageflow.NativeRuntime.win-x86": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" - }, - "Imageflow.NativeRuntime.win-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" - } - }, - ".NETFramework,Version=v4.7.2/win-x86": { - "Imageflow.NativeRuntime.osx-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "3wEglrMqVzlnAVBTdK6qcRySdo/4ajBP5ASRuK3yHfBqPp3ld4ke6guxuSZbgDTObIxai7KTLlsIvQZhusymUA==" - }, - "Imageflow.NativeRuntime.ubuntu-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "H8K5kZqcM3IliDRZD3H8BN6TbeLgcW+6FsDZ3EvlqBvu41s+Lv9vxE+c3m1cUQhsYBs76udUhgJFNR1D6x3U5g==" - }, - "Imageflow.NativeRuntime.win-x86": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "WunIva5NZ2iMPKCyz8ZTkN7SRaW3szBijMg5YK7jaSFZHw8Xiky/GFfghc0XgWTuILxwO4YbY86e8QvW8CBigQ==" - }, - "Imageflow.NativeRuntime.win-x86_64": { - "type": "Transitive", - "resolved": "2.0.0-preview8", - "contentHash": "1rY6C9Hjj7U9toa7FlnveiSBKccZlvCaHwdxPRQS0vDpAZZCJrTA/H7VYdreifpnIDInYcf0i/3oEKzEnj884w==" - } } } } \ No newline at end of file diff --git a/tests/Imageflow.TestWebAOT/Program.cs b/tests/Imageflow.TestWebAOT/Program.cs index 5d34544..c895161 100644 --- a/tests/Imageflow.TestWebAOT/Program.cs +++ b/tests/Imageflow.TestWebAOT/Program.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Text.Json.Serialization; + using Imageflow.Bindings; using Imageflow.Fluent; @@ -21,8 +22,8 @@ var vi = c.GetVersionInfo(); return Results.Ok(vi); }); -imageflowApi.MapGet("/resize/width/{width}", async(int width) => -{ +imageflowApi.MapGet("/resize/width/{width}", async (int width) => +{ var resultMemory = await Helpers.SizeIcon(width); return Results.Bytes(resultMemory, "image/jpeg"); }); From 388e618dbfe225b24058fe46a2b6decd2a5b39bf Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 22:32:32 -0700 Subject: [PATCH 13/22] Update AUTHORS.txt --- AUTHORS.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index b82c529..f659ed5 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -1,3 +1,3 @@ Imageflow.NET contains code contributed by: -Nathanael Jones \ No newline at end of file +Lilith River From d928b31133f4eb3baa6b61725f049e19a685263a Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 22:34:52 -0700 Subject: [PATCH 14/22] dotnet format style --- src/Imageflow/Bindings/ImageInfo.cs | 93 +- .../Bindings/ImageflowAssertionFailed.cs | 15 +- src/Imageflow/Bindings/ImageflowException.cs | 109 +- .../Bindings/ImageflowUnmanagedReadStream.cs | 124 +- src/Imageflow/Bindings/JobContext.cs | 1167 +++++++------- src/Imageflow/Bindings/JobContextHandle.cs | 96 +- src/Imageflow/Bindings/JsonResponse.cs | 343 +++-- src/Imageflow/Bindings/JsonResponseHandle.cs | 72 +- .../Bindings/NativeLibraryLoading.cs | 857 ++++++----- src/Imageflow/Bindings/NativeMethods.cs | 231 ++- src/Imageflow/Bindings/VersionInfo.cs | 123 +- src/Imageflow/Fluent/AnyColor.cs | 75 +- src/Imageflow/Fluent/BufferedStreamSource.cs | 6 +- src/Imageflow/Fluent/BuildDecodeResult.cs | 18 +- src/Imageflow/Fluent/BuildEncodeResult.cs | 77 +- src/Imageflow/Fluent/BuildEndpoint.cs | 41 +- src/Imageflow/Fluent/BuildItemBase.cs | 49 +- src/Imageflow/Fluent/BuildJobResult.cs | 313 ++-- src/Imageflow/Fluent/BuildNode.cs | 1365 ++++++++--------- src/Imageflow/Fluent/BytesDestination.cs | 41 +- src/Imageflow/Fluent/Constraint.cs | 135 +- src/Imageflow/Fluent/ConstraintGravity.cs | 82 +- src/Imageflow/Fluent/DecodeCommands.cs | 184 ++- src/Imageflow/Fluent/Enumerations.cs | 337 ++-- src/Imageflow/Fluent/FinishJobBuilder.cs | 175 ++- src/Imageflow/Fluent/FrameSizeLimit.cs | 47 +- src/Imageflow/Fluent/IBytesSource.cs | 88 +- src/Imageflow/Fluent/IEncoderPreset.cs | 440 +++--- src/Imageflow/Fluent/IOutputDestination.cs | 19 +- .../Fluent/IOutputDestinationExtensions.cs | 2 - .../Fluent/IPreparedFilesystemJob.cs | 11 +- .../Fluent/IWatermarkConstraintBox.cs | 13 +- src/Imageflow/Fluent/ImageJob.cs | 1228 +++++++-------- src/Imageflow/Fluent/InputWatermark.cs | 85 +- src/Imageflow/Fluent/JobExecutionOptions.cs | 9 +- src/Imageflow/Fluent/MagicBytes.cs | 806 +++++----- src/Imageflow/Fluent/MemorySource.cs | 2 - src/Imageflow/Fluent/PerformanceDetails.cs | 72 +- .../Fluent/PerformanceDetailsFrame.cs | 69 +- .../Fluent/PerformanceDetailsNode.cs | 11 +- src/Imageflow/Fluent/ResampleHints.cs | 218 +-- src/Imageflow/Fluent/SecurityOptions.cs | 75 +- src/Imageflow/Fluent/SrgbColor.cs | 104 +- src/Imageflow/Fluent/StreamDestination.cs | 20 +- src/Imageflow/Fluent/StreamSource.cs | 5 +- src/Imageflow/Fluent/WatermarkFitBox.cs | 179 ++- src/Imageflow/Fluent/WatermarkMargins.cs | 211 ++- src/Imageflow/Fluent/WatermarkOptions.cs | 347 +++-- src/Imageflow/GlobalSuppressions.cs | 1 - src/Imageflow/IO/ProcessEx.cs | 189 +-- src/Imageflow/IO/TemporaryFileProviders.cs | 177 ++- .../ArraySegmentExtensions.cs | 15 +- .../Internal.Helpers/IAssertReady.cs | 9 +- src/Imageflow/Internal.Helpers/PinBox.cs | 48 +- .../SafeHandleMemoryManager.cs | 1 - .../StreamMemoryExtensionsPolyfills.cs | 1 - src/Imageflow/Internal.Helpers/TextHelpers.cs | 2 - .../TestDotNetClassicLibraryLoading.cs | 15 +- ...etClassicPackageReferenceLibraryLoading.cs | 17 +- 59 files changed, 5486 insertions(+), 5178 deletions(-) diff --git a/src/Imageflow/Bindings/ImageInfo.cs b/src/Imageflow/Bindings/ImageInfo.cs index 42bd872..64fd16b 100644 --- a/src/Imageflow/Bindings/ImageInfo.cs +++ b/src/Imageflow/Bindings/ImageInfo.cs @@ -2,55 +2,54 @@ using Imageflow.Fluent; -namespace Imageflow.Bindings +namespace Imageflow.Bindings; + +public class ImageInfo { - public class ImageInfo + private ImageInfo(JsonNode imageInfo) { - private ImageInfo(JsonNode imageInfo) - { - var obj = imageInfo.AsObject(); - // ImageWidth = imageInfo.image_width.Value; - // ImageHeight = imageInfo.image_height.Value; - // PreferredMimeType = imageInfo.preferred_mime_type.Value; - // PreferredExtension = imageInfo.preferred_extension.Value; - // FrameDecodesInto = Enum.Parse(typeof(Fluent.PixelFormat), imageInfo.frame_decodes_into.Value, - // true); - const string widthMsg = "Imageflow get_image_info responded with null image_info.image_width"; - ImageWidth = obj.TryGetPropertyValue("image_width", out var imageWidthValue) - ? imageWidthValue?.GetValue() ?? throw new ImageflowAssertionFailed(widthMsg) - : throw new ImageflowAssertionFailed(widthMsg); - - const string heightMsg = "Imageflow get_image_info responded with null image_info.image_height"; - ImageHeight = obj.TryGetPropertyValue("image_height", out var imageHeightValue) - ? imageHeightValue?.GetValue() ?? throw new ImageflowAssertionFailed(heightMsg) - : throw new ImageflowAssertionFailed(heightMsg); - - const string mimeMsg = "Imageflow get_image_info responded with null image_info.preferred_mime_type"; - PreferredMimeType = obj.TryGetPropertyValue("preferred_mime_type", out var preferredMimeTypeValue) - ? preferredMimeTypeValue?.GetValue() ?? throw new ImageflowAssertionFailed(mimeMsg) - : throw new ImageflowAssertionFailed(mimeMsg); - - const string extMsg = "Imageflow get_image_info responded with null image_info.preferred_extension"; - PreferredExtension = obj.TryGetPropertyValue("preferred_extension", out var preferredExtensionValue) - ? preferredExtensionValue?.GetValue() ?? throw new ImageflowAssertionFailed(extMsg) - : throw new ImageflowAssertionFailed(extMsg); - - const string frameMsg = "Imageflow get_image_info responded with null image_info.frame_decodes_into"; - FrameDecodesInto = obj.TryGetPropertyValue("frame_decodes_into", out var frameDecodesIntoValue) - ? PixelFormatParser.Parse(frameDecodesIntoValue?.GetValue() ?? throw new ImageflowAssertionFailed(frameMsg)) - : throw new ImageflowAssertionFailed(frameMsg); - - } - internal static ImageInfo FromDynamic(JsonNode imageInfo) - { - return new ImageInfo(imageInfo); - } - - public PixelFormat FrameDecodesInto { get; private init; } - public long ImageWidth { get; private init; } - public long ImageHeight { get; private init; } - public string PreferredMimeType { get; private init; } - public string PreferredExtension { get; private init; } + var obj = imageInfo.AsObject(); + // ImageWidth = imageInfo.image_width.Value; + // ImageHeight = imageInfo.image_height.Value; + // PreferredMimeType = imageInfo.preferred_mime_type.Value; + // PreferredExtension = imageInfo.preferred_extension.Value; + // FrameDecodesInto = Enum.Parse(typeof(Fluent.PixelFormat), imageInfo.frame_decodes_into.Value, + // true); + const string widthMsg = "Imageflow get_image_info responded with null image_info.image_width"; + ImageWidth = obj.TryGetPropertyValue("image_width", out var imageWidthValue) + ? imageWidthValue?.GetValue() ?? throw new ImageflowAssertionFailed(widthMsg) + : throw new ImageflowAssertionFailed(widthMsg); + + const string heightMsg = "Imageflow get_image_info responded with null image_info.image_height"; + ImageHeight = obj.TryGetPropertyValue("image_height", out var imageHeightValue) + ? imageHeightValue?.GetValue() ?? throw new ImageflowAssertionFailed(heightMsg) + : throw new ImageflowAssertionFailed(heightMsg); + + const string mimeMsg = "Imageflow get_image_info responded with null image_info.preferred_mime_type"; + PreferredMimeType = obj.TryGetPropertyValue("preferred_mime_type", out var preferredMimeTypeValue) + ? preferredMimeTypeValue?.GetValue() ?? throw new ImageflowAssertionFailed(mimeMsg) + : throw new ImageflowAssertionFailed(mimeMsg); + + const string extMsg = "Imageflow get_image_info responded with null image_info.preferred_extension"; + PreferredExtension = obj.TryGetPropertyValue("preferred_extension", out var preferredExtensionValue) + ? preferredExtensionValue?.GetValue() ?? throw new ImageflowAssertionFailed(extMsg) + : throw new ImageflowAssertionFailed(extMsg); + + const string frameMsg = "Imageflow get_image_info responded with null image_info.frame_decodes_into"; + FrameDecodesInto = obj.TryGetPropertyValue("frame_decodes_into", out var frameDecodesIntoValue) + ? PixelFormatParser.Parse(frameDecodesIntoValue?.GetValue() ?? throw new ImageflowAssertionFailed(frameMsg)) + : throw new ImageflowAssertionFailed(frameMsg); } + internal static ImageInfo FromDynamic(JsonNode imageInfo) + { + return new ImageInfo(imageInfo); + } + + public PixelFormat FrameDecodesInto { get; private init; } + public long ImageWidth { get; private init; } + public long ImageHeight { get; private init; } + public string PreferredMimeType { get; private init; } + public string PreferredExtension { get; private init; } + } diff --git a/src/Imageflow/Bindings/ImageflowAssertionFailed.cs b/src/Imageflow/Bindings/ImageflowAssertionFailed.cs index 6815ae4..70e843c 100644 --- a/src/Imageflow/Bindings/ImageflowAssertionFailed.cs +++ b/src/Imageflow/Bindings/ImageflowAssertionFailed.cs @@ -1,8 +1,7 @@ -namespace Imageflow.Bindings -{ - /// - /// - /// For bugs - /// - public class ImageflowAssertionFailed(string message) : Exception(message); -} +namespace Imageflow.Bindings; + +/// +/// +/// For bugs +/// +public class ImageflowAssertionFailed(string message) : Exception(message); diff --git a/src/Imageflow/Bindings/ImageflowException.cs b/src/Imageflow/Bindings/ImageflowException.cs index dc421b2..7704e76 100644 --- a/src/Imageflow/Bindings/ImageflowException.cs +++ b/src/Imageflow/Bindings/ImageflowException.cs @@ -1,76 +1,75 @@ using System.Runtime.InteropServices; using System.Text; -namespace Imageflow.Bindings +namespace Imageflow.Bindings; + +public class ImageflowException : Exception { - public class ImageflowException : Exception - { - private const int MaxBufferSize = 8096 * 4; + private const int MaxBufferSize = 8096 * 4; - internal ImageflowException(string message) : base(message) - { + internal ImageflowException(string message) : base(message) + { - } + } - private enum ErrorFetchResult + private enum ErrorFetchResult + { + BufferTooSmall, + ContextInvalid, + NoError, + Success + } + private static ErrorFetchResult TryGetErrorString(JobContextHandle c, ulong bufferSize, out string? message) + { + message = null; + if (c.IsClosed || c.IsInvalid) { - BufferTooSmall, - ContextInvalid, - NoError, - Success + return ErrorFetchResult.ContextInvalid; } - private static ErrorFetchResult TryGetErrorString(JobContextHandle c, ulong bufferSize, out string? message) + if (!NativeMethods.imageflow_context_has_error(c)) { - message = null; - if (c.IsClosed || c.IsInvalid) - { - return ErrorFetchResult.ContextInvalid; - } - if (!NativeMethods.imageflow_context_has_error(c)) - { - return ErrorFetchResult.NoError; - } - var buffer = new byte[bufferSize]; - var pinned = GCHandle.Alloc(buffer, GCHandleType.Pinned); + return ErrorFetchResult.NoError; + } + var buffer = new byte[bufferSize]; + var pinned = GCHandle.Alloc(buffer, GCHandleType.Pinned); - try - { - var everythingWritten = NativeMethods.imageflow_context_error_write_to_buffer(c, - pinned.AddrOfPinnedObject(), new UIntPtr((ulong)buffer.LongLength), out var bytesWritten); + try + { + var everythingWritten = NativeMethods.imageflow_context_error_write_to_buffer(c, + pinned.AddrOfPinnedObject(), new UIntPtr((ulong)buffer.LongLength), out var bytesWritten); - message = bytesWritten.ToUInt64() > 0 - ? Encoding.UTF8.GetString(buffer, 0, (int)Math.Min(bytesWritten.ToUInt64(), bufferSize)) - : ""; + message = bytesWritten.ToUInt64() > 0 + ? Encoding.UTF8.GetString(buffer, 0, (int)Math.Min(bytesWritten.ToUInt64(), bufferSize)) + : ""; - return everythingWritten ? ErrorFetchResult.Success : ErrorFetchResult.BufferTooSmall; - } - finally - { - pinned.Free(); - } + return everythingWritten ? ErrorFetchResult.Success : ErrorFetchResult.BufferTooSmall; } + finally + { + pinned.Free(); + } + } - internal static ImageflowException FromContext(JobContextHandle c, ulong defaultBufferSize = 2048, string? additionalInfo = null) + internal static ImageflowException FromContext(JobContextHandle c, ulong defaultBufferSize = 2048, string? additionalInfo = null) + { + for (var bufferSize = defaultBufferSize; bufferSize < MaxBufferSize; bufferSize *= 2) { - for (var bufferSize = defaultBufferSize; bufferSize < MaxBufferSize; bufferSize *= 2) + var result = TryGetErrorString(c, bufferSize, out var message); + switch (result) { - var result = TryGetErrorString(c, bufferSize, out var message); - switch (result) - { - case ErrorFetchResult.Success: - return new ImageflowException((message ?? "Unknown Imageflow Error") + (additionalInfo != null ? $"\nAdditional info: {additionalInfo}" : "")); - case ErrorFetchResult.ContextInvalid: - return new ImageflowException("Imageflow context (JobContextHandle) is invalid"); - case ErrorFetchResult.NoError: - return new ImageflowException("Imageflow context has no error stored; cannot fetch error message"); - case ErrorFetchResult.BufferTooSmall: break; - default: - throw new ArgumentOutOfRangeException(); - } + case ErrorFetchResult.Success: + return new ImageflowException((message ?? "Unknown Imageflow Error") + (additionalInfo != null ? $"\nAdditional info: {additionalInfo}" : "")); + case ErrorFetchResult.ContextInvalid: + return new ImageflowException("Imageflow context (JobContextHandle) is invalid"); + case ErrorFetchResult.NoError: + return new ImageflowException("Imageflow context has no error stored; cannot fetch error message"); + case ErrorFetchResult.BufferTooSmall: break; + default: + throw new ArgumentOutOfRangeException(); } - - throw new ImageflowAssertionFailed( - $"Imageflow error and stacktrace exceeded {MaxBufferSize} bytes"); } + + throw new ImageflowAssertionFailed( + $"Imageflow error and stacktrace exceeded {MaxBufferSize} bytes"); } } diff --git a/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs b/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs index 6217456..2163c22 100644 --- a/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs +++ b/src/Imageflow/Bindings/ImageflowUnmanagedReadStream.cs @@ -2,82 +2,80 @@ using Imageflow.Internal.Helpers; -namespace Imageflow.Bindings +namespace Imageflow.Bindings; + +/// +/// An UnmanagedMemoryStream that checks that the underlying Imageflow context isn't in a disposed or errored state +/// +/// \ +/// +[Obsolete("This class will be removed in a future version; it has no benefit over Memory and IMemoryOwner")] +public sealed class ImageflowUnmanagedReadStream : UnmanagedMemoryStream { + private readonly IAssertReady _underlying; + private SafeHandle? _handle; + private int _handleReferenced; - /// - /// An UnmanagedMemoryStream that checks that the underlying Imageflow context isn't in a disposed or errored state - /// - /// \ - /// - [Obsolete("This class will be removed in a future version; it has no benefit over Memory and IMemoryOwner")] - public sealed class ImageflowUnmanagedReadStream : UnmanagedMemoryStream + internal unsafe ImageflowUnmanagedReadStream(IAssertReady underlying, SafeHandle handle, IntPtr buffer, UIntPtr length) : base((byte*)buffer.ToPointer(), (long)length.ToUInt64(), (long)length.ToUInt64(), FileAccess.Read) { - private readonly IAssertReady _underlying; - private SafeHandle? _handle; - private int _handleReferenced; - - internal unsafe ImageflowUnmanagedReadStream(IAssertReady underlying, SafeHandle handle, IntPtr buffer, UIntPtr length) : base((byte*)buffer.ToPointer(), (long)length.ToUInt64(), (long)length.ToUInt64(), FileAccess.Read) + _underlying = underlying; + _handle = handle; + var addRefSucceeded = false; + _handle.DangerousAddRef(ref addRefSucceeded); + _handleReferenced = addRefSucceeded ? 1 : 0; + if (!addRefSucceeded) { - _underlying = underlying; - _handle = handle; - var addRefSucceeded = false; - _handle.DangerousAddRef(ref addRefSucceeded); - _handleReferenced = addRefSucceeded ? 1 : 0; - if (!addRefSucceeded) - { - throw new ArgumentException("SafeHandle.DangerousAddRef failed", nameof(handle)); - } + throw new ArgumentException("SafeHandle.DangerousAddRef failed", nameof(handle)); } + } - private void CheckSafe() - { - _underlying.AssertReady(); - } - public override int Read(byte[] buffer, int offset, int count) - { - CheckSafe(); - return base.Read(buffer, offset, count); - } + private void CheckSafe() + { + _underlying.AssertReady(); + } + public override int Read(byte[] buffer, int offset, int count) + { + CheckSafe(); + return base.Read(buffer, offset, count); + } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - CheckSafe(); - return base.ReadAsync(buffer, offset, count, cancellationToken); - } + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + CheckSafe(); + return base.ReadAsync(buffer, offset, count, cancellationToken); + } - public override int ReadByte() - { - CheckSafe(); - return base.ReadByte(); - } + public override int ReadByte() + { + CheckSafe(); + return base.ReadByte(); + } - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) - { - CheckSafe(); - return base.BeginRead(buffer, offset, count, callback, state); - } + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) + { + CheckSafe(); + return base.BeginRead(buffer, offset, count, callback, state); + } - public override int EndRead(IAsyncResult asyncResult) - { - CheckSafe(); - return base.EndRead(asyncResult); - } + public override int EndRead(IAsyncResult asyncResult) + { + CheckSafe(); + return base.EndRead(asyncResult); + } - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - CheckSafe(); - return base.CopyToAsync(destination, bufferSize, cancellationToken); - } + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + CheckSafe(); + return base.CopyToAsync(destination, bufferSize, cancellationToken); + } - protected override void Dispose(bool disposing) + protected override void Dispose(bool disposing) + { + // Interlocked exchange to only release ref once + if (1 == Interlocked.Exchange(ref _handleReferenced, 0)) { - // Interlocked exchange to only release ref once - if (1 == Interlocked.Exchange(ref _handleReferenced, 0)) - { - _handle?.DangerousRelease(); - _handle = null; - } + _handle?.DangerousRelease(); + _handle = null; } } } diff --git a/src/Imageflow/Bindings/JobContext.cs b/src/Imageflow/Bindings/JobContext.cs index 75692ad..13105a3 100644 --- a/src/Imageflow/Bindings/JobContext.cs +++ b/src/Imageflow/Bindings/JobContext.cs @@ -7,517 +7,501 @@ using Imageflow.Fluent; using Imageflow.Internal.Helpers; -namespace Imageflow.Bindings +namespace Imageflow.Bindings; + +public sealed class JobContext : CriticalFinalizerObject, IDisposable, IAssertReady { + private readonly JobContextHandle _handle; + private List? _pinnedMemory; + private List? _toDispose; - public sealed class JobContext : CriticalFinalizerObject, IDisposable, IAssertReady + private JobContextHandle Handle { - private readonly JobContextHandle _handle; - private List? _pinnedMemory; - private List? _toDispose; - - private JobContextHandle Handle + get { - get + if (!_handle.IsValid) { - if (!_handle.IsValid) throw new ObjectDisposedException("Imageflow JobContext"); - return _handle; + throw new ObjectDisposedException("Imageflow JobContext"); } - } - private enum IoKind - { - InputBuffer, - OutputBuffer + return _handle; } + } - internal bool IsInput(int ioId) => _ioSet.ContainsKey(ioId) && _ioSet[ioId] == IoKind.InputBuffer; - internal bool IsOutput(int ioId) => _ioSet.ContainsKey(ioId) && _ioSet[ioId] == IoKind.OutputBuffer; - internal int LargestIoId => _ioSet.Keys.DefaultIfEmpty().Max(); + private enum IoKind + { + InputBuffer, + OutputBuffer + } - private readonly Dictionary _ioSet = new Dictionary(); + internal bool IsInput(int ioId) => _ioSet.ContainsKey(ioId) && _ioSet[ioId] == IoKind.InputBuffer; + internal bool IsOutput(int ioId) => _ioSet.ContainsKey(ioId) && _ioSet[ioId] == IoKind.OutputBuffer; + internal int LargestIoId => _ioSet.Keys.DefaultIfEmpty().Max(); - public JobContext() - { - _handle = new JobContextHandle(); - } + private readonly Dictionary _ioSet = new Dictionary(); - private void AddPinnedData(MemoryHandle handle) - { - _pinnedMemory ??= []; - _pinnedMemory.Add(handle); - } + public JobContext() + { + _handle = new JobContextHandle(); + } - public bool HasError => NativeMethods.imageflow_context_has_error(Handle); + private void AddPinnedData(MemoryHandle handle) + { + _pinnedMemory ??= []; + _pinnedMemory.Add(handle); + } - [Obsolete("Use SerializeNode instead for AOT compatibility")] + public bool HasError => NativeMethods.imageflow_context_has_error(Handle); - [RequiresUnreferencedCode("Use SerializeNode instead for AOT compatibility")] - [RequiresDynamicCode("Use SerializeNode instead for AOT compatibility")] - private static byte[] ObsoleteSerializeToJson(T obj) + [Obsolete("Use SerializeNode instead for AOT compatibility")] + + [RequiresUnreferencedCode("Use SerializeNode instead for AOT compatibility")] + [RequiresDynamicCode("Use SerializeNode instead for AOT compatibility")] + private static byte[] ObsoleteSerializeToJson(T obj) + { + // Use System.Text.Json for serialization + var options = new JsonSerializerOptions { - // Use System.Text.Json for serialization - var options = new JsonSerializerOptions - { - WriteIndented = true, - DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + WriteIndented = true, + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, #if NET8_0_OR_GREATER - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower #endif - }; - var ms = new MemoryStream(); - var utf8JsonWriter = new Utf8JsonWriter(ms, new JsonWriterOptions - { - Indented = true - }); - JsonSerializer.Serialize(utf8JsonWriter, obj, options); - utf8JsonWriter.Flush(); - return ms.ToArray(); - } + }; + var ms = new MemoryStream(); + var utf8JsonWriter = new Utf8JsonWriter(ms, new JsonWriterOptions + { + Indented = true + }); + JsonSerializer.Serialize(utf8JsonWriter, obj, options); + utf8JsonWriter.Flush(); + return ms.ToArray(); + } - internal static void WriteSerializedNode(IBufferWriter bufferWriter, JsonNode node, bool indented = true) + internal static void WriteSerializedNode(IBufferWriter bufferWriter, JsonNode node, bool indented = true) + { + // Use System.Text.Json for serialization + using var utf8JsonWriter = new Utf8JsonWriter(bufferWriter, new JsonWriterOptions { - // Use System.Text.Json for serialization - using var utf8JsonWriter = new Utf8JsonWriter(bufferWriter, new JsonWriterOptions - { - Indented = indented - }); - node.WriteTo(utf8JsonWriter); - // flushes on disposal - } + Indented = indented + }); + node.WriteTo(utf8JsonWriter); + // flushes on disposal + } - internal static byte[] SerializeNode(JsonNode node, bool indented = true) - { - // Use System.Text.Json for serialization - var ms = new MemoryStream(); - var utf8JsonWriter = new Utf8JsonWriter(ms, new JsonWriterOptions - { - Indented = indented - }); - node.WriteTo(utf8JsonWriter); - utf8JsonWriter.Flush(); - return ms.ToArray(); - } + internal static byte[] SerializeNode(JsonNode node, bool indented = true) + { + // Use System.Text.Json for serialization + var ms = new MemoryStream(); + var utf8JsonWriter = new Utf8JsonWriter(ms, new JsonWriterOptions + { + Indented = indented + }); + node.WriteTo(utf8JsonWriter); + utf8JsonWriter.Flush(); + return ms.ToArray(); + } + [Obsolete("Use SendMessage(JsonNode) instead for AOT compatibility")] + [RequiresUnreferencedCode("Use SendMessage(string method, JsonNode message) instead for AOT compatibility")] + [RequiresDynamicCode("Use SendMessage(string method, JsonNode message) instead for AOT compatibility")] + public IJsonResponseProvider SendMessage(string method, T message) + { + AssertReady(); + return InvokeInternal(method, ObsoleteSerializeToJson(message)); + } - [Obsolete("Use SendMessage(JsonNode) instead for AOT compatibility")] - [RequiresUnreferencedCode("Use SendMessage(string method, JsonNode message) instead for AOT compatibility")] - [RequiresDynamicCode("Use SendMessage(string method, JsonNode message) instead for AOT compatibility")] - public IJsonResponseProvider SendMessage(string method, T message) - { - AssertReady(); - return InvokeInternal(method, ObsoleteSerializeToJson(message)); - } + [Obsolete("Use ExecuteJsonNode instead for AOT compatibility")] + [RequiresUnreferencedCode("Use ExecuteJsonNode instead for AOT compatibility")] + [RequiresDynamicCode("Use ExecuteJsonNode instead for AOT compatibility")] + public IJsonResponseProvider Execute(T message) + { + AssertReady(); + return InvokeInternal(ImageflowMethods.Execute, ObsoleteSerializeToJson(message)); + } - [Obsolete("Use ExecuteJsonNode instead for AOT compatibility")] - [RequiresUnreferencedCode("Use ExecuteJsonNode instead for AOT compatibility")] - [RequiresDynamicCode("Use ExecuteJsonNode instead for AOT compatibility")] - public IJsonResponseProvider Execute(T message) - { - AssertReady(); - return InvokeInternal(ImageflowMethods.Execute, ObsoleteSerializeToJson(message)); - } + [Obsolete("Use Invoke(string method, JsonNode message) instead.")] + public IJsonResponseProvider SendMessage(string method, JsonNode message) + { + AssertReady(); + return InvokeInternal(method, message); + } - [Obsolete("Use Invoke(string method, JsonNode message) instead.")] - public IJsonResponseProvider SendMessage(string method, JsonNode message) - { - AssertReady(); - return InvokeInternal(method, message); - } + [Obsolete("Use .InvokeExecute(JsonNode message) instead")] + public IJsonResponseProvider ExecuteJsonNode(JsonNode message) + { + AssertReady(); + return InvokeInternal(ImageflowMethods.Execute, message); + } + [Obsolete("Use .Invoke(string method, ReadOnlySpan utf8Json) instead")] + public IJsonResponseProvider SendJsonBytes(string method, byte[] utf8Json) + => InvokeInternal(method, utf8Json.AsSpan()); - [Obsolete("Use .InvokeExecute(JsonNode message) instead")] - public IJsonResponseProvider ExecuteJsonNode(JsonNode message) + public ImageInfo GetImageInfo(int ioId) + { + var node = InvokeAndParse(ImageflowMethods.GetImageInfo, new JsonObject() { { "io_id", ioId } }); + if (node == null) { - AssertReady(); - return InvokeInternal(ImageflowMethods.Execute, message); + throw new ImageflowAssertionFailed("get_image_info response is null"); } - [Obsolete("Use .Invoke(string method, ReadOnlySpan utf8Json) instead")] - public IJsonResponseProvider SendJsonBytes(string method, byte[] utf8Json) - => InvokeInternal(method, utf8Json.AsSpan()); + var responseObj = node.AsObject(); + if (responseObj == null) + { + throw new ImageflowAssertionFailed("get_image_info response is not an object"); + } - public ImageInfo GetImageInfo(int ioId) + if (responseObj.TryGetPropertyValue("success", out var successValue)) { - var node = InvokeAndParse(ImageflowMethods.GetImageInfo, new JsonObject() { { "io_id", ioId } }); - if (node == null) throw new ImageflowAssertionFailed("get_image_info response is null"); - var responseObj = node.AsObject(); - if (responseObj == null) throw new ImageflowAssertionFailed("get_image_info response is not an object"); - if (responseObj.TryGetPropertyValue("success", out var successValue)) + if (successValue?.GetValue() != true) { - if (successValue?.GetValue() != true) - { - throw ImageflowException.FromContext(Handle); - } - - var dataValue = responseObj.TryGetPropertyValue("data", out var dataValueObj) ? dataValueObj : null; - if (dataValue == null) - throw new ImageflowAssertionFailed("get_image_info response does not have a data property"); - var imageInfoValue = - (dataValue.AsObject().TryGetPropertyValue("image_info", out var imageInfoValueObj)) - ? imageInfoValueObj - : null; - - if (imageInfoValue == null) - throw new ImageflowAssertionFailed( - "get_image_info response does not have an image_info property"); - return ImageInfo.FromDynamic(imageInfoValue); + throw ImageflowException.FromContext(Handle); } - else + + var dataValue = responseObj.TryGetPropertyValue("data", out var dataValueObj) ? dataValueObj : null; + if (dataValue == null) { - throw new ImageflowAssertionFailed("get_image_info response does not have a success property"); + throw new ImageflowAssertionFailed("get_image_info response does not have a data property"); } - } - public VersionInfo GetVersionInfo() - { - AssertReady(); + var imageInfoValue = + (dataValue.AsObject().TryGetPropertyValue("image_info", out var imageInfoValueObj)) + ? imageInfoValueObj + : null; - var node = InvokeAndParse(ImageflowMethods.GetVersionInfo); - if (node == null) throw new ImageflowAssertionFailed("get_version_info response is null"); - var responseObj = node.AsObject(); - if (responseObj == null) - throw new ImageflowAssertionFailed("get_version_info response is not an object"); - if (responseObj.TryGetPropertyValue("success", out var successValue)) + if (imageInfoValue == null) { - if (successValue?.GetValue() != true) - { - throw ImageflowException.FromContext(Handle); - } - - var dataValue = responseObj.TryGetPropertyValue("data", out var dataValueObj) ? dataValueObj : null; - if (dataValue == null) - throw new ImageflowAssertionFailed("get_version_info response does not have a data property"); - var versionInfoValue = - (dataValue.AsObject().TryGetPropertyValue("version_info", out var versionInfoValueObj)) - ? versionInfoValueObj - : null; - - if (versionInfoValue == null) - throw new ImageflowAssertionFailed( - "get_version_info response does not have an version_info property"); - return VersionInfo.FromNode(versionInfoValue); - } - else - { - throw new ImageflowAssertionFailed("get_version_info response does not have a success property"); + throw new ImageflowAssertionFailed( + "get_image_info response does not have an image_info property"); } - } + return ImageInfo.FromDynamic(imageInfoValue); + } + else + { + throw new ImageflowAssertionFailed("get_image_info response does not have a success property"); + } + } + public VersionInfo GetVersionInfo() + { + AssertReady(); - public IJsonResponse Invoke(string method, ReadOnlySpan utf8Json) + var node = InvokeAndParse(ImageflowMethods.GetVersionInfo); + if (node == null) { - return InvokeInternal(method, utf8Json); + throw new ImageflowAssertionFailed("get_version_info response is null"); } - public IJsonResponse Invoke(string method) + + var responseObj = node.AsObject(); + if (responseObj == null) { - return InvokeInternal(method, "{}"u8); + throw new ImageflowAssertionFailed("get_version_info response is not an object"); } - public JsonNode? InvokeAndParse(string method, JsonNode message) + if (responseObj.TryGetPropertyValue("success", out var successValue)) { - using var response = InvokeInternal(method, message); - return response.Parse(); + if (successValue?.GetValue() != true) + { + throw ImageflowException.FromContext(Handle); + } + + var dataValue = responseObj.TryGetPropertyValue("data", out var dataValueObj) ? dataValueObj : null; + if (dataValue == null) + { + throw new ImageflowAssertionFailed("get_version_info response does not have a data property"); + } + + var versionInfoValue = + (dataValue.AsObject().TryGetPropertyValue("version_info", out var versionInfoValueObj)) + ? versionInfoValueObj + : null; + + if (versionInfoValue == null) + { + throw new ImageflowAssertionFailed( + "get_version_info response does not have an version_info property"); + } + + return VersionInfo.FromNode(versionInfoValue); } - public JsonNode? InvokeAndParse(string method) + else { - using var response = InvokeInternal(method); - return response.Parse(); + throw new ImageflowAssertionFailed("get_version_info response does not have a success property"); } + } + public IJsonResponse Invoke(string method, ReadOnlySpan utf8Json) + { + return InvokeInternal(method, utf8Json); + } + public IJsonResponse Invoke(string method) + { + return InvokeInternal(method, "{}"u8); + } - public IJsonResponse InvokeExecute(JsonNode message) - { - AssertReady(); - return InvokeInternal(ImageflowMethods.Execute, message); - } + public JsonNode? InvokeAndParse(string method, JsonNode message) + { + using var response = InvokeInternal(method, message); + return response.Parse(); + } + public JsonNode? InvokeAndParse(string method) + { + using var response = InvokeInternal(method); + return response.Parse(); + } - private ImageflowJsonResponse InvokeInternal(string method) - { - AssertReady(); - return InvokeInternal(method, "{}"u8); - } + public IJsonResponse InvokeExecute(JsonNode message) + { + AssertReady(); + return InvokeInternal(ImageflowMethods.Execute, message); + } + private ImageflowJsonResponse InvokeInternal(string method) + { + AssertReady(); + return InvokeInternal(method, "{}"u8); + } - private ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTerminatedMethod, JsonNode message) - { - AssertReady(); + private ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTerminatedMethod, JsonNode message) + { + AssertReady(); #if NETSTANDARD2_1_OR_GREATER - // MAYBE: Use ArrayPoolBufferWriter instead? Adds CommunityToolkit.HighPerformance dependency - var writer = new ArrayBufferWriter(4096); - var utf8JsonWriter = new Utf8JsonWriter(writer, new JsonWriterOptions - { - Indented = true - }); - message.WriteTo(utf8JsonWriter); - utf8JsonWriter.Flush(); - return InvokeInternal(nullTerminatedMethod, writer.WrittenSpan); + // MAYBE: Use ArrayPoolBufferWriter instead? Adds CommunityToolkit.HighPerformance dependency + var writer = new ArrayBufferWriter(4096); + var utf8JsonWriter = new Utf8JsonWriter(writer, new JsonWriterOptions + { + Indented = true + }); + message.WriteTo(utf8JsonWriter); + utf8JsonWriter.Flush(); + return InvokeInternal(nullTerminatedMethod, writer.WrittenSpan); #else - // Use System.Text.Json for serialization - var ms = new MemoryStream(); - var utf8JsonWriter = new Utf8JsonWriter(ms, new JsonWriterOptions - { - Indented = true - }); - message.WriteTo(utf8JsonWriter); - utf8JsonWriter.Flush(); - return ms.TryGetBufferSliceAllWrittenData(out var buffer) ? - InvokeInternal(nullTerminatedMethod, buffer) : - InvokeInternal(nullTerminatedMethod, ms.ToArray()); + // Use System.Text.Json for serialization + var ms = new MemoryStream(); + var utf8JsonWriter = new Utf8JsonWriter(ms, new JsonWriterOptions + { + Indented = true + }); + message.WriteTo(utf8JsonWriter); + utf8JsonWriter.Flush(); + return ms.TryGetBufferSliceAllWrittenData(out var buffer) ? + InvokeInternal(nullTerminatedMethod, buffer) : + InvokeInternal(nullTerminatedMethod, ms.ToArray()); #endif + } + + private ImageflowJsonResponse InvokeInternal(string method, JsonNode message) + { + AssertReady(); + var methodBuffer = method.Length < 128 ? stackalloc byte[method.Length + 1] : new byte[method.Length + 1]; + if (!TextHelpers.TryEncodeAsciiNullTerminated(method.AsSpan(), methodBuffer, out var nullTerminatedBytes)) + { + throw new ArgumentException("Method must only contain ASCII characters", nameof(method)); } + return InvokeInternal(nullTerminatedBytes, message); + } - private ImageflowJsonResponse InvokeInternal(string method, JsonNode message) + private ImageflowJsonResponse InvokeInternal(string method, ReadOnlySpan utf8Json) + { + AssertReady(); + var methodBuffer = method.Length < 128 ? stackalloc byte[method.Length + 1] : new byte[method.Length + 1]; + if (!TextHelpers.TryEncodeAsciiNullTerminated(method.AsSpan(), methodBuffer, out var nullTerminatedBytes)) { - AssertReady(); - var methodBuffer = method.Length < 128 ? stackalloc byte[method.Length + 1] : new byte[method.Length + 1]; - if (!TextHelpers.TryEncodeAsciiNullTerminated(method.AsSpan(), methodBuffer, out var nullTerminatedBytes)) - { - throw new ArgumentException("Method must only contain ASCII characters", nameof(method)); - } - return InvokeInternal(nullTerminatedBytes, message); + throw new ArgumentException("Method must only contain ASCII characters", nameof(method)); } + return InvokeInternal(nullTerminatedBytes, utf8Json); + } - private ImageflowJsonResponse InvokeInternal(string method, ReadOnlySpan utf8Json) + private unsafe ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTerminatedMethod, ReadOnlySpan utf8Json) + { + if (utf8Json.Length < 0) { - AssertReady(); - var methodBuffer = method.Length < 128 ? stackalloc byte[method.Length + 1] : new byte[method.Length + 1]; - if (!TextHelpers.TryEncodeAsciiNullTerminated(method.AsSpan(), methodBuffer, out var nullTerminatedBytes)) - { - throw new ArgumentException("Method must only contain ASCII characters", nameof(method)); - } - return InvokeInternal(nullTerminatedBytes, utf8Json); + throw new ArgumentException("utf8Json cannot be empty", nameof(utf8Json)); } - private unsafe ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTerminatedMethod, ReadOnlySpan utf8Json) + if (nullTerminatedMethod.Length == 0) { - if (utf8Json.Length < 0) throw new ArgumentException("utf8Json cannot be empty", nameof(utf8Json)); - if (nullTerminatedMethod.Length == 0) throw new ArgumentException("Method cannot be empty", nameof(nullTerminatedMethod)); - if (nullTerminatedMethod[^1] != 0) throw new ArgumentException("Method must be null terminated", nameof(nullTerminatedMethod)); - fixed (byte* methodPtr = nullTerminatedMethod) + throw new ArgumentException("Method cannot be empty", nameof(nullTerminatedMethod)); + } + + if (nullTerminatedMethod[^1] != 0) + { + throw new ArgumentException("Method must be null terminated", nameof(nullTerminatedMethod)); + } + + fixed (byte* methodPtr = nullTerminatedMethod) + { + fixed (byte* jsonPtr = utf8Json) { - fixed (byte* jsonPtr = utf8Json) + AssertReady(); + var ptr = NativeMethods.imageflow_context_send_json(Handle, new IntPtr(methodPtr), new IntPtr(jsonPtr), + new UIntPtr((ulong)utf8Json.Length)); + // check HasError, throw exception with our input JSON too + if (HasError) { - AssertReady(); - var ptr = NativeMethods.imageflow_context_send_json(Handle, new IntPtr(methodPtr), new IntPtr(jsonPtr), - new UIntPtr((ulong)utf8Json.Length)); - // check HasError, throw exception with our input JSON too - if (HasError) throw ImageflowException.FromContext(Handle, 2048, "JSON:\n" + TextHelpers.Utf8ToString(utf8Json)); - - AssertReady(); - return new ImageflowJsonResponse(new JsonResponseHandle(_handle, ptr)); + throw ImageflowException.FromContext(Handle, 2048, "JSON:\n" + TextHelpers.Utf8ToString(utf8Json)); } + + AssertReady(); + return new ImageflowJsonResponse(new JsonResponseHandle(_handle, ptr)); } } + } - - - - public void AssertReady() + public void AssertReady() + { + if (!_handle.IsValid) { - if (!_handle.IsValid) throw new ObjectDisposedException("Imageflow JobContext"); - if (HasError) throw ImageflowException.FromContext(Handle); + throw new ObjectDisposedException("Imageflow JobContext"); } - [Obsolete("Obsolete: use the Fluent API instead")] - public IJsonResponseProvider ExecuteImageResizer4CommandString(int inputId, int outputId, string commands) + if (HasError) { - AssertReady(); - return ExecuteImageResizer4CommandStringInternal(inputId, outputId, commands); - } - internal ImageflowJsonResponse ExecuteImageResizer4CommandStringInternal(int inputId, int outputId, string commands) - { - - // var message = new - // { - // framewise = new - // { - // steps = new object[] - // { - // new - // { - // command_string = new - // { - // kind = "ir4", - // value = commands, - // decode = inputId, - // encode = outputId - // } - // } - // } - // } - // }; - var message = new JsonObject() + throw ImageflowException.FromContext(Handle); + } + } + + [Obsolete("Obsolete: use the Fluent API instead")] + public IJsonResponseProvider ExecuteImageResizer4CommandString(int inputId, int outputId, string commands) + { + AssertReady(); + return ExecuteImageResizer4CommandStringInternal(inputId, outputId, commands); + } + internal ImageflowJsonResponse ExecuteImageResizer4CommandStringInternal(int inputId, int outputId, string commands) + { + + // var message = new + // { + // framewise = new + // { + // steps = new object[] + // { + // new + // { + // command_string = new + // { + // kind = "ir4", + // value = commands, + // decode = inputId, + // encode = outputId + // } + // } + // } + // } + // }; + var message = new JsonObject() + { + {"framewise", new JsonObject() { - {"framewise", new JsonObject() + {"steps", new JsonArray() { - {"steps", new JsonArray() + (JsonNode)new JsonObject() { - (JsonNode)new JsonObject() + {"command_string", new JsonObject() { - {"command_string", new JsonObject() - { - {"kind", "ir4"}, - {"value", commands}, - {"decode", inputId}, - {"encode", outputId} - }} - } - }} + {"kind", "ir4"}, + {"value", commands}, + {"decode", inputId}, + {"encode", outputId} + }} + } }} - }; - - return InvokeInternal(ImageflowMethods.Execute, message); - } - - /// - /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. - /// - /// - /// - public void AddInputBytes(int ioId, byte[] buffer) - { - AddInputBytes(ioId, buffer.AsSpan()); - } - /// - /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. - /// - /// - /// - /// - public void AddInputBytes(int ioId, ArraySegment buffer) - { - if (buffer.Array == null) throw new ArgumentNullException(nameof(buffer), "Array cannot be null"); - AddInputBytes(ioId, buffer.Array, buffer.Offset, buffer.Count); - } - /// - /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. - /// - /// - /// - /// - /// - /// - /// - public void AddInputBytes(int ioId, byte[] buffer, long offset, long count) - { - if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - if (offset > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(offset), "offset must be less than or equal to int.MaxValue"); - if (count > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(count), " count must be less than or equal to int.MaxValue"); - if (offset < 0 || offset > buffer.LongLength - 1) throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be within array bounds"); - if (count < 0 || offset + count > buffer.LongLength) throw new ArgumentOutOfRangeException(nameof(count), count, "offset + count must be within array bounds. count cannot be negative"); - - AddInputBytes(ioId, buffer.AsSpan((int)offset, (int)count)); - } - /// - /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. - /// - /// - /// - /// - /// - public void AddInputBytes(int ioId, ReadOnlySpan data) + }} + }; + + return InvokeInternal(ImageflowMethods.Execute, message); + } + + /// + /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. + /// + /// + /// + public void AddInputBytes(int ioId, byte[] buffer) + { + AddInputBytes(ioId, buffer.AsSpan()); + } + /// + /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. + /// + /// + /// + /// + public void AddInputBytes(int ioId, ArraySegment buffer) + { + if (buffer.Array == null) { - AssertReady(); - if (ContainsIoId(ioId)) throw new ArgumentException($"ioId {ioId} already in use", nameof(ioId)); + throw new ArgumentNullException(nameof(buffer), "Array cannot be null"); + } - var length = (ulong)data.Length; - unsafe - { - fixed (byte* ptr = data) - { - // OutlivesFunctionCall tells imageflow to copy the data. - if (!NativeMethods.imageflow_context_add_input_buffer(Handle, ioId, new IntPtr(ptr), - new UIntPtr(length), - NativeMethods.Lifetime.OutlivesFunctionCall)) - { - AssertReady(); - throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); - } + AddInputBytes(ioId, buffer.Array, buffer.Offset, buffer.Count); + } + /// + /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. + /// + /// + /// + /// + /// + /// + /// + public void AddInputBytes(int ioId, byte[] buffer, long offset, long count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } - _ioSet.Add(ioId, IoKind.InputBuffer); - } - } + if (offset > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(offset), "offset must be less than or equal to int.MaxValue"); } - /// - /// Pins the given data in managed memory and gives Imageflow a pointer to it. The data must not be modified until after the job is disposed. - /// - /// - /// - /// - public void AddInputBytesPinned(int ioId, byte[] buffer) - { - if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - AddInputBytesPinned(ioId, new ReadOnlyMemory(buffer), MemoryLifetimePromise.MemoryIsOwnedByRuntime); - } - /// - /// Pins the given data in managed memory and gives Imageflow a pointer to it. The data must not be modified until after the job is disposed. - /// - /// - /// - /// - public void AddInputBytesPinned(int ioId, ArraySegment buffer) - { - if (buffer.Array == null) throw new ArgumentNullException(nameof(buffer)); - AddInputBytesPinned(ioId, buffer.Array, buffer.Offset, buffer.Count); - } - /// - /// Pins the given data in managed memory and gives Imageflow a pointer to it. The data must not be modified until after the job is disposed. - /// - /// - /// - /// - /// - /// - /// - public void AddInputBytesPinned(int ioId, byte[] buffer, long offset, long count) - { - if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - if (offset > int.MaxValue) - throw new ArgumentOutOfRangeException(nameof(offset), "offset must be less than or equal to int.MaxValue"); - if (count > int.MaxValue) - throw new ArgumentOutOfRangeException(nameof(count), " count must be less than or equal to int.MaxValue"); - - if (offset < 0 || offset > buffer.LongLength - 1) - throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be within array bounds"); - if (count < 0 || offset + count > buffer.LongLength) - throw new ArgumentOutOfRangeException(nameof(count), count, - "offset + count must be within array bounds. count cannot be negative"); - - - var rom = new ReadOnlyMemory(buffer, (int)offset, (int)count); - AddInputBytesPinned(ioId, rom, MemoryLifetimePromise.MemoryIsOwnedByRuntime); - } - - /// - /// Pines the given Memory and gives Imageflow a pointer to it. You must promise that the - /// memory will remain valid until after the JobContext is disposed. - /// - /// - /// - /// - /// - /// - public unsafe void AddInputBytesPinned(int ioId, ReadOnlyMemory data, MemoryLifetimePromise callerPromise) - { - if (callerPromise == MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource) - throw new ArgumentException("callerPromise cannot be MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource", nameof(callerPromise)); - AssertReady(); - if (ContainsIoId(ioId)) throw new ArgumentException($"ioId {ioId} already in use", nameof(ioId)); + if (count > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(count), " count must be less than or equal to int.MaxValue"); + } - var pinned = data.Pin(); - try - { - var length = (ulong)data.Length; - AddPinnedData(pinned); + if (offset < 0 || offset > buffer.LongLength - 1) + { + throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be within array bounds"); + } + + if (count < 0 || offset + count > buffer.LongLength) + { + throw new ArgumentOutOfRangeException(nameof(count), count, "offset + count must be within array bounds. count cannot be negative"); + } - var addr = new IntPtr(pinned.Pointer); - if (!NativeMethods.imageflow_context_add_input_buffer(Handle, ioId, addr, new UIntPtr(length), - NativeMethods.Lifetime.OutlivesContext)) + AddInputBytes(ioId, buffer.AsSpan((int)offset, (int)count)); + } + /// + /// Copies the given data into Imageflow's memory. Use AddInputBytesPinned to avoid copying. + /// + /// + /// + /// + /// + public void AddInputBytes(int ioId, ReadOnlySpan data) + { + AssertReady(); + if (ContainsIoId(ioId)) + { + throw new ArgumentException($"ioId {ioId} already in use", nameof(ioId)); + } + + var length = (ulong)data.Length; + unsafe + { + fixed (byte* ptr = data) + { + // OutlivesFunctionCall tells imageflow to copy the data. + if (!NativeMethods.imageflow_context_add_input_buffer(Handle, ioId, new IntPtr(ptr), + new UIntPtr(length), + NativeMethods.Lifetime.OutlivesFunctionCall)) { AssertReady(); throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); @@ -525,161 +509,284 @@ public unsafe void AddInputBytesPinned(int ioId, ReadOnlyMemory data, Memo _ioSet.Add(ioId, IoKind.InputBuffer); } - catch - { - _pinnedMemory?.Remove(pinned); - pinned.Dispose(); - throw; - } } + } + /// + /// Pins the given data in managed memory and gives Imageflow a pointer to it. The data must not be modified until after the job is disposed. + /// + /// + /// + /// + public void AddInputBytesPinned(int ioId, byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } - public void AddOutputBuffer(int ioId) + AddInputBytesPinned(ioId, new ReadOnlyMemory(buffer), MemoryLifetimePromise.MemoryIsOwnedByRuntime); + } + /// + /// Pins the given data in managed memory and gives Imageflow a pointer to it. The data must not be modified until after the job is disposed. + /// + /// + /// + /// + public void AddInputBytesPinned(int ioId, ArraySegment buffer) + { + if (buffer.Array == null) { - AssertReady(); - if (ContainsIoId(ioId)) throw new ArgumentException($"ioId {ioId} already in use", nameof(ioId)); - if (!NativeMethods.imageflow_context_add_output_buffer(Handle, ioId)) - { - AssertReady(); - throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); - } - _ioSet.Add(ioId, IoKind.OutputBuffer); + throw new ArgumentNullException(nameof(buffer)); } - public bool ContainsIoId(int ioId) => _ioSet.ContainsKey(ioId); + AddInputBytesPinned(ioId, buffer.Array, buffer.Offset, buffer.Count); + } + /// + /// Pins the given data in managed memory and gives Imageflow a pointer to it. The data must not be modified until after the job is disposed. + /// + /// + /// + /// + /// + /// + /// + public void AddInputBytesPinned(int ioId, byte[] buffer, long offset, long count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } - /// - /// Will raise an unrecoverable exception if this is not an output buffer. - /// Stream is not valid after the JobContext is disposed - /// - /// - [Obsolete("Use a higher-level wrapper like the Fluent API instead; they can use faster code paths")] - public Stream GetOutputBuffer(int ioId) + if (offset > int.MaxValue) { - if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) - { - throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); - } - AssertReady(); - if (!NativeMethods.imageflow_context_get_output_buffer_by_id(Handle, ioId, out var buffer, - out var bufferSize)) - { - AssertReady(); - throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); - } - return new ImageflowUnmanagedReadStream(this, this._handle, buffer, bufferSize); + throw new ArgumentOutOfRangeException(nameof(offset), "offset must be less than or equal to int.MaxValue"); } - /// - /// The memory remains valid only until the JobContext is disposed. - /// - /// - /// - /// - /// - internal unsafe Span BorrowOutputBuffer(int ioId) + if (count > int.MaxValue) { - if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) - { - throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); - } - AssertReady(); - if (!NativeMethods.imageflow_context_get_output_buffer_by_id(Handle, ioId, out var buffer, - out var bufferSize)) + throw new ArgumentOutOfRangeException(nameof(count), " count must be less than or equal to int.MaxValue"); + } + + if (offset < 0 || offset > buffer.LongLength - 1) + { + throw new ArgumentOutOfRangeException(nameof(offset), offset, "Offset must be within array bounds"); + } + + if (count < 0 || offset + count > buffer.LongLength) + { + throw new ArgumentOutOfRangeException(nameof(count), count, + "offset + count must be within array bounds. count cannot be negative"); + } + + var rom = new ReadOnlyMemory(buffer, (int)offset, (int)count); + AddInputBytesPinned(ioId, rom, MemoryLifetimePromise.MemoryIsOwnedByRuntime); + } + + /// + /// Pines the given Memory and gives Imageflow a pointer to it. You must promise that the + /// memory will remain valid until after the JobContext is disposed. + /// + /// + /// + /// + /// + /// + public unsafe void AddInputBytesPinned(int ioId, ReadOnlyMemory data, MemoryLifetimePromise callerPromise) + { + if (callerPromise == MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource) + { + throw new ArgumentException("callerPromise cannot be MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource", nameof(callerPromise)); + } + + AssertReady(); + if (ContainsIoId(ioId)) + { + throw new ArgumentException($"ioId {ioId} already in use", nameof(ioId)); + } + + var pinned = data.Pin(); + try + { + var length = (ulong)data.Length; + AddPinnedData(pinned); + + var addr = new IntPtr(pinned.Pointer); + if (!NativeMethods.imageflow_context_add_input_buffer(Handle, ioId, addr, new UIntPtr(length), + NativeMethods.Lifetime.OutlivesContext)) { AssertReady(); throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); } - return new Span((void*)buffer, (int)bufferSize); + + _ioSet.Add(ioId, IoKind.InputBuffer); } + catch + { + _pinnedMemory?.Remove(pinned); + pinned.Dispose(); + throw; + } + } - /// - /// Returns an IMemoryOwner<byte> that will keep the Imageflow Job in memory until both it and the JobContext are disposed. - /// The memory should be treated as read-only. - /// - /// - /// - /// - /// - internal IMemoryOwner BorrowOutputBufferMemoryAndAddReference(int ioId) + public void AddOutputBuffer(int ioId) + { + AssertReady(); + if (ContainsIoId(ioId)) + { + throw new ArgumentException($"ioId {ioId} already in use", nameof(ioId)); + } + + if (!NativeMethods.imageflow_context_add_output_buffer(Handle, ioId)) { - if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) - { - throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); - } AssertReady(); - if (!NativeMethods.imageflow_context_get_output_buffer_by_id(Handle, ioId, out var buffer, - out var bufferSize)) - { - AssertReady(); - throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); - } - return SafeHandleMemoryManager.BorrowFromHandle(_handle, buffer, (uint)bufferSize); + throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); } + _ioSet.Add(ioId, IoKind.OutputBuffer); + } - private int _refCount; - internal void AddRef() + public bool ContainsIoId(int ioId) => _ioSet.ContainsKey(ioId); + + /// + /// Will raise an unrecoverable exception if this is not an output buffer. + /// Stream is not valid after the JobContext is disposed + /// + /// + [Obsolete("Use a higher-level wrapper like the Fluent API instead; they can use faster code paths")] + public Stream GetOutputBuffer(int ioId) + { + if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) + { + throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); + } + AssertReady(); + if (!NativeMethods.imageflow_context_get_output_buffer_by_id(Handle, ioId, out var buffer, + out var bufferSize)) { - Interlocked.Increment(ref _refCount); + AssertReady(); + throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); } + return new ImageflowUnmanagedReadStream(this, this._handle, buffer, bufferSize); + } - internal void RemoveRef() + /// + /// The memory remains valid only until the JobContext is disposed. + /// + /// + /// + /// + /// + internal unsafe Span BorrowOutputBuffer(int ioId) + { + if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) { - Interlocked.Decrement(ref _refCount); + throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); } + AssertReady(); + if (!NativeMethods.imageflow_context_get_output_buffer_by_id(Handle, ioId, out var buffer, + out var bufferSize)) + { + AssertReady(); + throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); + } + return new Span((void*)buffer, (int)bufferSize); + } - public bool IsDisposed => !_handle.IsValid; - public void Dispose() + /// + /// Returns an IMemoryOwner<byte> that will keep the Imageflow Job in memory until both it and the JobContext are disposed. + /// The memory should be treated as read-only. + /// + /// + /// + /// + /// + internal IMemoryOwner BorrowOutputBufferMemoryAndAddReference(int ioId) + { + if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) { - if (IsDisposed) throw new ObjectDisposedException("Imageflow JobContext"); + throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); + } + AssertReady(); + if (!NativeMethods.imageflow_context_get_output_buffer_by_id(Handle, ioId, out var buffer, + out var bufferSize)) + { + AssertReady(); + throw new ImageflowAssertionFailed("AssertReady should raise an exception if method fails"); + } + return SafeHandleMemoryManager.BorrowFromHandle(_handle, buffer, (uint)bufferSize); + } - if (Interlocked.Exchange(ref _refCount, 0) > 0) - { - throw new InvalidOperationException("Cannot dispose a JobContext that is still in use. "); - } + private int _refCount; + internal void AddRef() + { + Interlocked.Increment(ref _refCount); + } - // Do not allocate or throw exceptions unless (disposing) - Exception? e = null; - try - { - e = _handle.DisposeAllowingException(); - } - finally - { - UnpinAll(); + internal void RemoveRef() + { + Interlocked.Decrement(ref _refCount); + } - //Dispose all managed data held for context lifetime - if (_toDispose != null) - { - foreach (var active in _toDispose) - active.Dispose(); - _toDispose = null; - } - GC.SuppressFinalize(this); - if (e != null) throw e; - } + public bool IsDisposed => !_handle.IsValid; + public void Dispose() + { + if (IsDisposed) + { + throw new ObjectDisposedException("Imageflow JobContext"); } - private void UnpinAll() + if (Interlocked.Exchange(ref _refCount, 0) > 0) { - //Unpin - if (_pinnedMemory != null) + throw new InvalidOperationException("Cannot dispose a JobContext that is still in use. "); + } + + // Do not allocate or throw exceptions unless (disposing) + Exception? e = null; + try + { + e = _handle.DisposeAllowingException(); + } + finally + { + UnpinAll(); + + //Dispose all managed data held for context lifetime + if (_toDispose != null) { - var toDispose = _pinnedMemory; - _pinnedMemory = null; - foreach (var active in toDispose) + foreach (var active in _toDispose) { active.Dispose(); } + + _toDispose = null; + } + GC.SuppressFinalize(this); + if (e != null) + { + throw e; } } + } - ~JobContext() + private void UnpinAll() + { + //Unpin + if (_pinnedMemory != null) { - //Don't dispose managed objects; they have their own finalizers - // _handle specifically handles it's own disposal and finalizer - UnpinAll(); + var toDispose = _pinnedMemory; + _pinnedMemory = null; + foreach (var active in toDispose) + { + active.Dispose(); + } } + } + ~JobContext() + { + //Don't dispose managed objects; they have their own finalizers + // _handle specifically handles it's own disposal and finalizer + UnpinAll(); } + } diff --git a/src/Imageflow/Bindings/JobContextHandle.cs b/src/Imageflow/Bindings/JobContextHandle.cs index dd7078c..377ea7e 100644 --- a/src/Imageflow/Bindings/JobContextHandle.cs +++ b/src/Imageflow/Bindings/JobContextHandle.cs @@ -4,70 +4,74 @@ using Microsoft.Win32.SafeHandles; -namespace Imageflow.Bindings -{ +namespace Imageflow.Bindings; - /// - /// The handle is ready even if there is an error condition stored in the context. - /// - /// AddRef and Release should be called. - /// - internal sealed class JobContextHandle : SafeHandleZeroOrMinusOneIsInvalid, IAssertReady +/// +/// The handle is ready even if there is an error condition stored in the context. +/// +/// AddRef and Release should be called. +/// +internal sealed class JobContextHandle : SafeHandleZeroOrMinusOneIsInvalid, IAssertReady +{ + public JobContextHandle() + : base(true) { - public JobContextHandle() - : base(true) + //var timer = Stopwatch.StartNew(); + var ptr = NativeLibraryLoader.FixDllNotFoundException("imageflow", () => NativeMethods.imageflow_context_create(NativeMethods.ABI_MAJOR, NativeMethods.ABI_MINOR)); + //timer.Stop(); + //Debug.WriteLine($"{timer.ElapsedMilliseconds}ms"); //4ms (when pinvoke 'just' works) to 27ms (when we have to go looking for the binary) + if (ptr == IntPtr.Zero) { - //var timer = Stopwatch.StartNew(); - var ptr = NativeLibraryLoader.FixDllNotFoundException("imageflow", () => NativeMethods.imageflow_context_create(NativeMethods.ABI_MAJOR, NativeMethods.ABI_MINOR)); - //timer.Stop(); - //Debug.WriteLine($"{timer.ElapsedMilliseconds}ms"); //4ms (when pinvoke 'just' works) to 27ms (when we have to go looking for the binary) - if (ptr == IntPtr.Zero) - { - if (NativeMethods.imageflow_abi_compatible(NativeMethods.ABI_MAJOR, NativeMethods.ABI_MINOR)) - { - throw new OutOfMemoryException("Failed to create Imageflow JobContext"); - } - var major = NativeMethods.imageflow_abi_version_major(); - var minor = NativeMethods.imageflow_abi_version_minor(); - throw new Exception( - $".NET Imageflow bindings only support ABI {NativeMethods.ABI_MAJOR}.{NativeMethods.ABI_MINOR}. libimageflow ABI {major}.{minor} is loaded."); + if (NativeMethods.imageflow_abi_compatible(NativeMethods.ABI_MAJOR, NativeMethods.ABI_MINOR)) + { + throw new OutOfMemoryException("Failed to create Imageflow JobContext"); } - SetHandle(ptr); + var major = NativeMethods.imageflow_abi_version_major(); + var minor = NativeMethods.imageflow_abi_version_minor(); + throw new Exception( + $".NET Imageflow bindings only support ABI {NativeMethods.ABI_MAJOR}.{NativeMethods.ABI_MINOR}. libimageflow ABI {major}.{minor} is loaded."); } + SetHandle(ptr); + } - public bool IsValid => !IsInvalid && !IsClosed; + public bool IsValid => !IsInvalid && !IsClosed; - public void AssertReady() + public void AssertReady() + { + if (!IsValid) { - if (!IsValid) throw new ObjectDisposedException("Imageflow JobContextHandle"); + throw new ObjectDisposedException("Imageflow JobContextHandle"); } + } - public ImageflowException? DisposeAllowingException() + public ImageflowException? DisposeAllowingException() + { + if (!IsValid) { - if (!IsValid) return null; + return null; + } - try - { - if (!NativeMethods.imageflow_context_begin_terminate(this)) - { - return ImageflowException.FromContext(this); - } - } - finally + try + { + if (!NativeMethods.imageflow_context_begin_terminate(this)) { - Dispose(); + return ImageflowException.FromContext(this); } - return null; } + finally + { + Dispose(); + } + return null; + } #pragma warning disable SYSLIB0004 - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] #pragma warning restore SYSLIB0004 - protected override bool ReleaseHandle() - { - NativeMethods.imageflow_context_destroy(handle); - return true; - } + protected override bool ReleaseHandle() + { + NativeMethods.imageflow_context_destroy(handle); + return true; } } diff --git a/src/Imageflow/Bindings/JsonResponse.cs b/src/Imageflow/Bindings/JsonResponse.cs index c5b9f2e..2c984f4 100644 --- a/src/Imageflow/Bindings/JsonResponse.cs +++ b/src/Imageflow/Bindings/JsonResponse.cs @@ -6,232 +6,243 @@ using Utf8JsonReader = System.Text.Json.Utf8JsonReader; -namespace Imageflow.Bindings +namespace Imageflow.Bindings; + +[Obsolete("Use Imageflow.Bindings.IJsonResponse instead")] +public interface IJsonResponseProvider : IDisposable +{ + [Obsolete("Do not read the JSON directly; utilize helper methods instead")] + Stream GetStream(); +} + +public interface IJsonResponse : IDisposable +{ + public int ImageflowErrorCode { get; } + public string CopyString(); + public JsonNode? Parse(); + public byte[] CopyBytes(); +} + +internal interface IJsonResponseSpanProvider : IDisposable +{ + /// + /// This data is only valid until the JsonResponse or JobContext is disposed. + /// + /// + ReadOnlySpan BorrowBytes(); +} + +internal sealed class MemoryJsonResponse : IJsonResponse, IJsonResponseSpanProvider { - [Obsolete("Use Imageflow.Bindings.IJsonResponse instead")] - public interface IJsonResponseProvider : IDisposable + internal MemoryJsonResponse(int statusCode, ReadOnlyMemory memory) { - [Obsolete("Do not read the JSON directly; utilize helper methods instead")] - Stream GetStream(); + ImageflowErrorCode = statusCode; + _memory = memory; } - public interface IJsonResponse : IDisposable + private readonly ReadOnlyMemory _memory; + + public int ImageflowErrorCode { get; } + + public string CopyString() { - public int ImageflowErrorCode { get; } - public string CopyString(); - public JsonNode? Parse(); - public byte[] CopyBytes(); + return _memory.Span.Utf8ToString(); } - internal interface IJsonResponseSpanProvider : IDisposable + public JsonNode? Parse() { - /// - /// This data is only valid until the JsonResponse or JobContext is disposed. - /// - /// - ReadOnlySpan BorrowBytes(); + return _memory.Span.ParseJsonNode(); } - internal sealed class MemoryJsonResponse : IJsonResponse, IJsonResponseSpanProvider + public byte[] CopyBytes() { - internal MemoryJsonResponse(int statusCode, ReadOnlyMemory memory) - { - ImageflowErrorCode = statusCode; - _memory = memory; - } + return _memory.ToArray(); + } - private readonly ReadOnlyMemory _memory; + public ReadOnlySpan BorrowBytes() + { + return _memory.Span; + } - public int ImageflowErrorCode { get; } + public void Dispose() + { + // no-op + } +} - public string CopyString() - { - return _memory.Span.Utf8ToString(); - } +/// +/// Readable even if the JobContext is in an error state. +/// +#pragma warning disable CS0618 // Type or member is obsolete +internal sealed class ImageflowJsonResponse : IJsonResponseProvider, IAssertReady, IJsonResponseSpanProvider, IJsonResponse +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly JsonResponseHandle _handle; + private int? _statusCode; - public JsonNode? Parse() + private JsonResponseHandle Handle + { + get { - return _memory.Span.ParseJsonNode(); + AssertReady(); + return _handle; } + } - public byte[] CopyBytes() - { - return _memory.ToArray(); - } + internal ImageflowJsonResponse(JsonResponseHandle ptr) + { + ptr.ParentContext.AssertReady(); + ptr.AssertReady(); + _handle = ptr; + } - public ReadOnlySpan BorrowBytes() + public void AssertReady() + { + _handle.ParentContext.AssertReady(); + if (!_handle.IsValid) { - return _memory.Span; + throw new ObjectDisposedException("Imageflow JsonResponse"); } + } - public void Dispose() - { - // no-op - } + private void Read(out int statusCode, out IntPtr utf8Buffer, out UIntPtr bufferSize) + { + AssertReady(); + NativeMethods.imageflow_json_response_read(_handle.ParentContext, Handle, out statusCode, out utf8Buffer, + out bufferSize); + AssertReady(); } /// - /// Readable even if the JobContext is in an error state. + /// The stream will become invalid if the JsonResponse or JobContext is disposed. /// -#pragma warning disable CS0618 // Type or member is obsolete - internal sealed class ImageflowJsonResponse : IJsonResponseProvider, IAssertReady, IJsonResponseSpanProvider, IJsonResponse -#pragma warning restore CS0618 // Type or member is obsolete + /// + [Obsolete("Do not read the JSON directly; utilize helper methods instead")] + public Stream GetStream() { - private readonly JsonResponseHandle _handle; - private int? _statusCode; - - private JsonResponseHandle Handle - { - get - { - AssertReady(); - return _handle; - } - } + Read(out var _, out var utf8Buffer, out var bufferSize); + return new ImageflowUnmanagedReadStream(this, _handle, utf8Buffer, bufferSize); + } - internal ImageflowJsonResponse(JsonResponseHandle ptr) + public unsafe ReadOnlySpan BorrowBytes() + { + Read(out var _, out var utf8Buffer, out var bufferSize); + if (utf8Buffer == IntPtr.Zero) { - ptr.ParentContext.AssertReady(); - ptr.AssertReady(); - _handle = ptr; + return ReadOnlySpan.Empty; } - public void AssertReady() + if (bufferSize == UIntPtr.Zero) { - _handle.ParentContext.AssertReady(); - if (!_handle.IsValid) throw new ObjectDisposedException("Imageflow JsonResponse"); + return ReadOnlySpan.Empty; } - private void Read(out int statusCode, out IntPtr utf8Buffer, out UIntPtr bufferSize) + if (bufferSize.ToUInt64() > int.MaxValue) { - AssertReady(); - NativeMethods.imageflow_json_response_read(_handle.ParentContext, Handle, out statusCode, out utf8Buffer, - out bufferSize); - AssertReady(); + throw new ArgumentOutOfRangeException(nameof(bufferSize)); } + return new ReadOnlySpan((void*)utf8Buffer, (int)bufferSize); + } - - /// - /// The stream will become invalid if the JsonResponse or JobContext is disposed. - /// - /// - [Obsolete("Do not read the JSON directly; utilize helper methods instead")] - public Stream GetStream() - { - Read(out var _, out var utf8Buffer, out var bufferSize); - return new ImageflowUnmanagedReadStream(this, _handle, utf8Buffer, bufferSize); - } - - public unsafe ReadOnlySpan BorrowBytes() + public MemoryManager BorrowMemory() + { + Read(out var _, out var utf8Buffer, out var bufferSize); + if (bufferSize.ToUInt64() > int.MaxValue) { - Read(out var _, out var utf8Buffer, out var bufferSize); - if (utf8Buffer == IntPtr.Zero) return ReadOnlySpan.Empty; - if (bufferSize == UIntPtr.Zero) return ReadOnlySpan.Empty; - if (bufferSize.ToUInt64() > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(bufferSize)); - - return new ReadOnlySpan((void*)utf8Buffer, (int)bufferSize); + throw new ArgumentOutOfRangeException(nameof(bufferSize)); } - public MemoryManager BorrowMemory() - { - Read(out var _, out var utf8Buffer, out var bufferSize); - if (bufferSize.ToUInt64() > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(bufferSize)); - return SafeHandleMemoryManager.BorrowFromHandle(_handle, utf8Buffer, (uint)bufferSize); - } + return SafeHandleMemoryManager.BorrowFromHandle(_handle, utf8Buffer, (uint)bufferSize); + } + public bool IsDisposed => !_handle.IsValid; - public bool IsDisposed => !_handle.IsValid; + public void Dispose() + { + _handle.Dispose(); + } - public void Dispose() - { - _handle.Dispose(); - } + public int GetStatusCode() + { + Read(out var statusCode, out var _, out var _); + return statusCode; + } + public int ImageflowErrorCode => _statusCode ??= GetStatusCode(); + public string CopyString() + { + return BorrowBytes().Utf8ToString(); + } - public int GetStatusCode() - { - Read(out var statusCode, out var _, out var _); - return statusCode; - } + public JsonNode? Parse() + { + return BorrowBytes().ParseJsonNode(); + } - public int ImageflowErrorCode => _statusCode ??= GetStatusCode(); - public string CopyString() - { - return BorrowBytes().Utf8ToString(); - } + public byte[] CopyBytes() + { + return BorrowBytes().ToArray(); + } +} - public JsonNode? Parse() - { - return BorrowBytes().ParseJsonNode(); - } +// ReSharper disable once InconsistentNaming +public static class IJsonResponseProviderExtensions +{ + internal static JsonNode? ParseJsonNode(this ReadOnlySpan buffer) + { - public byte[] CopyBytes() - { - return BorrowBytes().ToArray(); - } + var reader3 = new Utf8JsonReader(buffer); + return JsonNode.Parse(ref reader3); } - // ReSharper disable once InconsistentNaming - public static class IJsonResponseProviderExtensions + // [Obsolete("Use DeserializeJsonNode() instead")] + // public static T? Deserialize(this IJsonResponseProvider p) where T : class + // { + // using var readStream = p.GetStream(); + // using var ms = new MemoryStream(readStream.CanSeek ? (int)readStream.Length : 0); + // readStream.CopyTo(ms); + // var allBytes = ms.ToArray(); + // var options = new JsonSerializerOptions + // { + // PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower + // }; + // var v = System.Text.Json.JsonSerializer.Deserialize(allBytes, options); + // return v; + //} + // + // [Obsolete("Use Deserialize or DeserializeJsonNode() instead")] + // public static dynamic? DeserializeDynamic(this IJsonResponseProvider p) + // { + // using var reader = new StreamReader(p.GetStream(), Encoding.UTF8); + // //return JsonSerializer.Create().Deserialize(new JsonTextReader(reader)); + // } + + [Obsolete("Use IJsonResponse.Parse()? instead")] + public static JsonNode? DeserializeJsonNode(this IJsonResponseProvider p) { - internal static JsonNode? ParseJsonNode(this ReadOnlySpan buffer) + if (p is IJsonResponseSpanProvider j) { - - var reader3 = new Utf8JsonReader(buffer); + var reader3 = new Utf8JsonReader(j.BorrowBytes()); return JsonNode.Parse(ref reader3); } - - // [Obsolete("Use DeserializeJsonNode() instead")] - // public static T? Deserialize(this IJsonResponseProvider p) where T : class - // { - // using var readStream = p.GetStream(); - // using var ms = new MemoryStream(readStream.CanSeek ? (int)readStream.Length : 0); - // readStream.CopyTo(ms); - // var allBytes = ms.ToArray(); - // var options = new JsonSerializerOptions - // { - // PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower - // }; - // var v = System.Text.Json.JsonSerializer.Deserialize(allBytes, options); - // return v; - //} - // - // [Obsolete("Use Deserialize or DeserializeJsonNode() instead")] - // public static dynamic? DeserializeDynamic(this IJsonResponseProvider p) - // { - // using var reader = new StreamReader(p.GetStream(), Encoding.UTF8); - // //return JsonSerializer.Create().Deserialize(new JsonTextReader(reader)); - // } - - [Obsolete("Use IJsonResponse.Parse()? instead")] - public static JsonNode? DeserializeJsonNode(this IJsonResponseProvider p) - { - if (p is IJsonResponseSpanProvider j) - { - var reader3 = new Utf8JsonReader(j.BorrowBytes()); - return JsonNode.Parse(ref reader3); - } - #pragma warning disable CS0618 // Type or member is obsolete - using var reader = new StreamReader(p.GetStream(), Encoding.UTF8); + using var reader = new StreamReader(p.GetStream(), Encoding.UTF8); #pragma warning restore CS0618 // Type or member is obsolete - var json = reader.ReadToEnd(); - var reader2 = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); - return JsonNode.Parse(ref reader2); - } - + var json = reader.ReadToEnd(); + var reader2 = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + return JsonNode.Parse(ref reader2); + } - [Obsolete("IJsonResponseProvider is deprecated; use IJsonResponse instead")] - public static string GetString(this IJsonResponseProvider p) + [Obsolete("IJsonResponseProvider is deprecated; use IJsonResponse instead")] + public static string GetString(this IJsonResponseProvider p) + { + if (p is IJsonResponseSpanProvider j) { - if (p is IJsonResponseSpanProvider j) - { - return j.BorrowBytes().Utf8ToString(); - } - using var s = new StreamReader(p.GetStream(), Encoding.UTF8); - return s.ReadToEnd(); + return j.BorrowBytes().Utf8ToString(); } + using var s = new StreamReader(p.GetStream(), Encoding.UTF8); + return s.ReadToEnd(); } } diff --git a/src/Imageflow/Bindings/JsonResponseHandle.cs b/src/Imageflow/Bindings/JsonResponseHandle.cs index d179893..b522763 100644 --- a/src/Imageflow/Bindings/JsonResponseHandle.cs +++ b/src/Imageflow/Bindings/JsonResponseHandle.cs @@ -4,52 +4,60 @@ using Microsoft.Win32.SafeHandles; -namespace Imageflow.Bindings +namespace Imageflow.Bindings; + +/// +/// A child SafeHandle that increments the reference count on JobContextHandle when created and decrements it when disposed. +/// +/// +internal sealed class JsonResponseHandle : SafeHandleZeroOrMinusOneIsInvalid, IAssertReady { - /// - /// A child SafeHandle that increments the reference count on JobContextHandle when created and decrements it when disposed. - /// - /// - internal sealed class JsonResponseHandle : SafeHandleZeroOrMinusOneIsInvalid, IAssertReady + public JsonResponseHandle(JobContextHandle parent, IntPtr ptr) + : base(true) { - public JsonResponseHandle(JobContextHandle parent, IntPtr ptr) - : base(true) - { - ParentContext = parent ?? throw new ArgumentNullException(nameof(parent)); - SetHandle(ptr); - - var addRefSucceeded = false; - parent.DangerousAddRef(ref addRefSucceeded); - if (!addRefSucceeded) - { - throw new ArgumentException("SafeHandle.DangerousAddRef failed", nameof(parent)); - } + ParentContext = parent ?? throw new ArgumentNullException(nameof(parent)); + SetHandle(ptr); + var addRefSucceeded = false; + parent.DangerousAddRef(ref addRefSucceeded); + if (!addRefSucceeded) + { + throw new ArgumentException("SafeHandle.DangerousAddRef failed", nameof(parent)); } - public JobContextHandle ParentContext { get; } + } + + public JobContextHandle ParentContext { get; } - public bool IsValid => !IsInvalid && !IsClosed && ParentContext.IsValid; + public bool IsValid => !IsInvalid && !IsClosed && ParentContext.IsValid; - public void AssertReady() + public void AssertReady() + { + if (!ParentContext.IsValid) { - if (!ParentContext.IsValid) throw new ObjectDisposedException("Imageflow JobContextHandle"); - if (!IsValid) throw new ObjectDisposedException("Imageflow JsonResponseHandle"); + throw new ObjectDisposedException("Imageflow JobContextHandle"); } - + if (!IsValid) + { + throw new ObjectDisposedException("Imageflow JsonResponseHandle"); + } + } #pragma warning disable SYSLIB0004 - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] #pragma warning restore SYSLIB0004 - protected override bool ReleaseHandle() + protected override bool ReleaseHandle() + { + // The base class, the caller, handles interlocked / sync and preventing multiple calls. + // We check ParentContext just in case someone went wild with DangerousRelease elsewhere. + // It's a process-ending error if ParentContext is invalid. + if (ParentContext.IsValid) { - // The base class, the caller, handles interlocked / sync and preventing multiple calls. - // We check ParentContext just in case someone went wild with DangerousRelease elsewhere. - // It's a process-ending error if ParentContext is invalid. - if (ParentContext.IsValid) NativeMethods.imageflow_json_response_destroy(ParentContext, handle); - ParentContext.DangerousRelease(); - return true; + NativeMethods.imageflow_json_response_destroy(ParentContext, handle); } + + ParentContext.DangerousRelease(); + return true; } } diff --git a/src/Imageflow/Bindings/NativeLibraryLoading.cs b/src/Imageflow/Bindings/NativeLibraryLoading.cs index 68067a7..0b048c8 100644 --- a/src/Imageflow/Bindings/NativeLibraryLoading.cs +++ b/src/Imageflow/Bindings/NativeLibraryLoading.cs @@ -7,545 +7,552 @@ #if !NET8_0_OR_GREATER using System.Reflection; #endif -namespace Imageflow.Bindings +namespace Imageflow.Bindings; + +internal class LoadLogger : ILibraryLoadLogger { - internal class LoadLogger : ILibraryLoadLogger - { - internal string Verb = "loaded"; - internal string Filename = $"{RuntimeFileLocator.SharedLibraryPrefix.Value}"; + internal string Verb = "loaded"; + internal string Filename = $"{RuntimeFileLocator.SharedLibraryPrefix.Value}"; + + internal Exception? FirstException; + internal Exception? LastException; - internal Exception? FirstException; - internal Exception? LastException; + private readonly List _log = new List(7); - private readonly List _log = new List(7); + private struct LogEntry + { + internal string Basename; + internal string? FullPath; + internal bool FileExists; + internal bool PreviouslyLoaded; + internal int? LoadErrorCode; + } - private struct LogEntry + public void NotifyAttempt(string basename, string? fullPath, bool fileExists, bool previouslyLoaded, + int? loadErrorCode) + { + _log.Add(new LogEntry { - internal string Basename; - internal string? FullPath; - internal bool FileExists; - internal bool PreviouslyLoaded; - internal int? LoadErrorCode; - } + Basename = basename, + FullPath = fullPath, + FileExists = fileExists, + PreviouslyLoaded = previouslyLoaded, + LoadErrorCode = loadErrorCode + }); + } - public void NotifyAttempt(string basename, string? fullPath, bool fileExists, bool previouslyLoaded, - int? loadErrorCode) + internal void RaiseException() + { + var sb = new StringBuilder(_log.Select((e) => e.Basename?.Length ?? 0 + e.FullPath?.Length ?? 0 + 20) + .Sum()); + sb.AppendFormat("Looking for \"{0}\" RID=\"{1}-{2}\", IsUnix={3}, IsDotNetCore={4} RelativeSearchPath=\"{5}\"\n", + Filename, + RuntimeFileLocator.PlatformRuntimePrefix.Value, + RuntimeFileLocator.ArchitectureSubdir.Value, RuntimeFileLocator.IsUnix, + RuntimeFileLocator.IsDotNetCore.Value, + AppDomain.CurrentDomain.RelativeSearchPath); + if (FirstException != null) { - _log.Add(new LogEntry - { - Basename = basename, - FullPath = fullPath, - FileExists = fileExists, - PreviouslyLoaded = previouslyLoaded, - LoadErrorCode = loadErrorCode - }); + sb.AppendFormat("Before searching: {0}\n", FirstException.Message); } - internal void RaiseException() + foreach (var e in _log) { - var sb = new StringBuilder(_log.Select((e) => e.Basename?.Length ?? 0 + e.FullPath?.Length ?? 0 + 20) - .Sum()); - sb.AppendFormat("Looking for \"{0}\" RID=\"{1}-{2}\", IsUnix={3}, IsDotNetCore={4} RelativeSearchPath=\"{5}\"\n", - Filename, - RuntimeFileLocator.PlatformRuntimePrefix.Value, - RuntimeFileLocator.ArchitectureSubdir.Value, RuntimeFileLocator.IsUnix, - RuntimeFileLocator.IsDotNetCore.Value, - AppDomain.CurrentDomain.RelativeSearchPath); - if (FirstException != null) sb.AppendFormat("Before searching: {0}\n", FirstException.Message); - foreach (var e in _log) + if (e.PreviouslyLoaded) { - if (e.PreviouslyLoaded) - { - sb.AppendFormat("\"{0}\" is already {1}", e.Basename, Verb); - } - else if (!e.FileExists) - { - sb.AppendFormat("File not found: {0}", e.FullPath); - } - else if (e.LoadErrorCode.HasValue) + sb.AppendFormat("\"{0}\" is already {1}", e.Basename, Verb); + } + else if (!e.FileExists) + { + sb.AppendFormat("File not found: {0}", e.FullPath); + } + else if (e.LoadErrorCode.HasValue) + { + string errorCode = e.LoadErrorCode.Value < 0 + ? string.Format(CultureInfo.InvariantCulture, "0x{0:X8}", e.LoadErrorCode.Value) + : e.LoadErrorCode.Value.ToString(CultureInfo.InvariantCulture); + + sb.AppendFormat("Error \"{0}\" ({1}) loading {2} from {3}", + new Win32Exception(e.LoadErrorCode.Value).Message, + errorCode, + e.Basename, e.FullPath); + + if (e.LoadErrorCode.Value == 193 && + RuntimeFileLocator.PlatformRuntimePrefix.Value == "win") { - string errorCode = e.LoadErrorCode.Value < 0 - ? string.Format(CultureInfo.InvariantCulture, "0x{0:X8}", e.LoadErrorCode.Value) - : e.LoadErrorCode.Value.ToString(CultureInfo.InvariantCulture); - - sb.AppendFormat("Error \"{0}\" ({1}) loading {2} from {3}", - new Win32Exception(e.LoadErrorCode.Value).Message, - errorCode, - e.Basename, e.FullPath); - - if (e.LoadErrorCode.Value == 193 && - RuntimeFileLocator.PlatformRuntimePrefix.Value == "win") - { - var installed = Environment.Is64BitProcess ? "32-bit (x86)" : "64-bit (x86_64)"; - var needed = Environment.Is64BitProcess ? "64-bit (x86_64)" : "32-bit (x86)"; - - sb.AppendFormat("\n> You have installed a {0} copy of imageflow.dll but need the {1} version", - installed, needed); - } - - if (e.LoadErrorCode.Value == 126 && - RuntimeFileLocator.PlatformRuntimePrefix.Value == "win") - { - var crtLink = "https://aka.ms/vs/16/release/vc_redist." - + (Environment.Is64BitProcess ? "x64.exe" : "x86.exe"); - - sb.AppendFormat("\n> You may need to install the C Runtime from {0}", crtLink); - } + var installed = Environment.Is64BitProcess ? "32-bit (x86)" : "64-bit (x86_64)"; + var needed = Environment.Is64BitProcess ? "64-bit (x86_64)" : "32-bit (x86)"; + + sb.AppendFormat("\n> You have installed a {0} copy of imageflow.dll but need the {1} version", + installed, needed); } - else + + if (e.LoadErrorCode.Value == 126 && + RuntimeFileLocator.PlatformRuntimePrefix.Value == "win") { - sb.AppendFormat("{0} {1} in {2}", Verb, e.Basename, e.FullPath); + var crtLink = "https://aka.ms/vs/16/release/vc_redist." + + (Environment.Is64BitProcess ? "x64.exe" : "x86.exe"); + + sb.AppendFormat("\n> You may need to install the C Runtime from {0}", crtLink); } - sb.Append('\n'); } - if (LastException != null) sb.AppendLine(LastException.Message); - var stackTrace = (FirstException ?? LastException)?.StackTrace; - if (stackTrace != null) sb.AppendLine(stackTrace); + else + { + sb.AppendFormat("{0} {1} in {2}", Verb, e.Basename, e.FullPath); + } + sb.Append('\n'); + } + if (LastException != null) + { + sb.AppendLine(LastException.Message); + } - throw new DllNotFoundException(sb.ToString()); + var stackTrace = (FirstException ?? LastException)?.StackTrace; + if (stackTrace != null) + { + sb.AppendLine(stackTrace); } - } + throw new DllNotFoundException(sb.ToString()); + } +} - internal static class RuntimeFileLocator - { - internal static bool IsUnix => Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX; +internal static class RuntimeFileLocator +{ + internal static bool IsUnix => Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX; - internal static readonly Lazy SharedLibraryPrefix = new Lazy(() => IsUnix ? "lib" : "", LazyThreadSafetyMode.PublicationOnly); + internal static readonly Lazy SharedLibraryPrefix = new Lazy(() => IsUnix ? "lib" : "", LazyThreadSafetyMode.PublicationOnly); #if NET8_0_OR_GREATER - internal static readonly Lazy IsDotNetCore = new Lazy(() => - true - , LazyThreadSafetyMode.PublicationOnly); + internal static readonly Lazy IsDotNetCore = new Lazy(() => + true + , LazyThreadSafetyMode.PublicationOnly); #else - internal static readonly Lazy IsDotNetCore = new Lazy(() => - typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.CodeBase.Contains("Microsoft.NETCore.App") - , LazyThreadSafetyMode.PublicationOnly); + internal static readonly Lazy IsDotNetCore = new Lazy(() => + typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.CodeBase.Contains("Microsoft.NETCore.App") + , LazyThreadSafetyMode.PublicationOnly); #endif - internal static readonly Lazy PlatformRuntimePrefix = new Lazy(() => + internal static readonly Lazy PlatformRuntimePrefix = new Lazy(() => + { + switch (Environment.OSVersion.Platform) { - switch (Environment.OSVersion.Platform) - { - case PlatformID.MacOSX: - return "osx"; - case PlatformID.Unix: - return "linux"; - case PlatformID.Win32NT: - case PlatformID.Win32S: - case PlatformID.Win32Windows: - case PlatformID.WinCE: - case PlatformID.Xbox: - return "win"; - default: - return "win"; - } - }, LazyThreadSafetyMode.PublicationOnly); + case PlatformID.MacOSX: + return "osx"; + case PlatformID.Unix: + return "linux"; + case PlatformID.Win32NT: + case PlatformID.Win32S: + case PlatformID.Win32Windows: + case PlatformID.WinCE: + case PlatformID.Xbox: + return "win"; + default: + return "win"; + } + }, LazyThreadSafetyMode.PublicationOnly); - internal static readonly Lazy SharedLibraryExtension = new Lazy(() => + internal static readonly Lazy SharedLibraryExtension = new Lazy(() => + { + switch (Environment.OSVersion.Platform) { - switch (Environment.OSVersion.Platform) - { - case PlatformID.MacOSX: - return "dylib"; - case PlatformID.Unix: - return "so"; - case PlatformID.Win32NT: - case PlatformID.Win32S: - case PlatformID.Win32Windows: - case PlatformID.WinCE: - case PlatformID.Xbox: - return "dll"; - default: - return "dll"; - } - }, LazyThreadSafetyMode.PublicationOnly); + case PlatformID.MacOSX: + return "dylib"; + case PlatformID.Unix: + return "so"; + case PlatformID.Win32NT: + case PlatformID.Win32S: + case PlatformID.Win32Windows: + case PlatformID.WinCE: + case PlatformID.Xbox: + return "dll"; + default: + return "dll"; + } + }, LazyThreadSafetyMode.PublicationOnly); - internal static readonly Lazy ExecutableExtension = new Lazy(() => + internal static readonly Lazy ExecutableExtension = new Lazy(() => + { + switch (Environment.OSVersion.Platform) { - switch (Environment.OSVersion.Platform) - { - case PlatformID.MacOSX: - return ""; - case PlatformID.Unix: - return ""; - case PlatformID.Win32NT: - case PlatformID.Win32S: - case PlatformID.Win32Windows: - case PlatformID.WinCE: - case PlatformID.Xbox: - return "dll"; - default: - return "dll"; - } - }, LazyThreadSafetyMode.PublicationOnly); + case PlatformID.MacOSX: + return ""; + case PlatformID.Unix: + return ""; + case PlatformID.Win32NT: + case PlatformID.Win32S: + case PlatformID.Win32Windows: + case PlatformID.WinCE: + case PlatformID.Xbox: + return "dll"; + default: + return "dll"; + } + }, LazyThreadSafetyMode.PublicationOnly); - /// - /// The output subdirectory that NuGet .props/.targets should be copying unmanaged binaries to. - /// If you're using .NET Core you don't need this. - /// - internal static readonly Lazy ArchitectureSubdir = new Lazy(() => - { - // ReSharper disable once InvertIf - if (!IsUnix) - { - var architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); - if (string.Equals(architecture, "ia64", StringComparison.OrdinalIgnoreCase)) - { - return "ia64"; - } - if (string.Equals(architecture, "arm", StringComparison.OrdinalIgnoreCase)) - { - return Environment.Is64BitProcess ? "arm64" : "arm"; - } - // We don't currently support unlisted/unknown architectures. We default to x86/x64 as backup - } - //TODO: Add support for arm/arm64 on linux - return Environment.Is64BitProcess ? "x64" : "x86"; - }, LazyThreadSafetyMode.PublicationOnly); - - /// - /// Enumerates a set of folders to search within. If the boolean value of the tuple is true, - /// specific subdirectories can be searched. - /// - /// - /// - private static IEnumerable> BaseFolders(IEnumerable? customSearchDirectories = null) + /// + /// The output subdirectory that NuGet .props/.targets should be copying unmanaged binaries to. + /// If you're using .NET Core you don't need this. + /// + internal static readonly Lazy ArchitectureSubdir = new Lazy(() => + { + // ReSharper disable once InvertIf + if (!IsUnix) { - // Prioritize user suggestions - if (customSearchDirectories != null) + var architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); + if (string.Equals(architecture, "ia64", StringComparison.OrdinalIgnoreCase)) { - foreach (var d in customSearchDirectories) - { - yield return Tuple.Create(true, d); - } + return "ia64"; } - - // First look in AppDomain.CurrentDomain.RelativeSearchPath - if it is within the BaseDirectory - if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath) && - AppDomain.CurrentDomain.RelativeSearchPath.StartsWith(AppDomain.CurrentDomain.BaseDirectory)) + if (string.Equals(architecture, "arm", StringComparison.OrdinalIgnoreCase)) { - yield return Tuple.Create(true, AppDomain.CurrentDomain.RelativeSearchPath); + return Environment.Is64BitProcess ? "arm64" : "arm"; } - // look in System.AppContext.BaseDirectory - if (!string.IsNullOrEmpty(AppContext.BaseDirectory)) + // We don't currently support unlisted/unknown architectures. We default to x86/x64 as backup + } + //TODO: Add support for arm/arm64 on linux + return Environment.Is64BitProcess ? "x64" : "x86"; + }, LazyThreadSafetyMode.PublicationOnly); + + /// + /// Enumerates a set of folders to search within. If the boolean value of the tuple is true, + /// specific subdirectories can be searched. + /// + /// + /// + private static IEnumerable> BaseFolders(IEnumerable? customSearchDirectories = null) + { + // Prioritize user suggestions + if (customSearchDirectories != null) + { + foreach (var d in customSearchDirectories) { - yield return Tuple.Create(true, AppContext.BaseDirectory); + yield return Tuple.Create(true, d); } + } - // Look in the base directory from which .NET looks for managed assemblies - yield return Tuple.Create(true, AppDomain.CurrentDomain.BaseDirectory); + // First look in AppDomain.CurrentDomain.RelativeSearchPath - if it is within the BaseDirectory + if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath) && + AppDomain.CurrentDomain.RelativeSearchPath.StartsWith(AppDomain.CurrentDomain.BaseDirectory)) + { + yield return Tuple.Create(true, AppDomain.CurrentDomain.RelativeSearchPath); + } + // look in System.AppContext.BaseDirectory + if (!string.IsNullOrEmpty(AppContext.BaseDirectory)) + { + yield return Tuple.Create(true, AppContext.BaseDirectory); + } - //Issue #17 - Azure Functions 2.0 - https://github.com/imazen/imageflow-dotnet/issues/17 - // If the BaseDirectory is /bin/, look one step outside of it. - if (AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).EndsWith("bin")) - { - //Look in the parent directory if we're in /bin/, but only look in ../runtimes/:rid:/native - var dir = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory); - if (dir != null) - yield return Tuple.Create(false, Path.Combine(dir.FullName, - "runtimes", PlatformRuntimePrefix.Value + "-" + ArchitectureSubdir.Value, "native")); + // Look in the base directory from which .NET looks for managed assemblies + yield return Tuple.Create(true, AppDomain.CurrentDomain.BaseDirectory); + //Issue #17 - Azure Functions 2.0 - https://github.com/imazen/imageflow-dotnet/issues/17 + // If the BaseDirectory is /bin/, look one step outside of it. + if (AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).EndsWith("bin")) + { + //Look in the parent directory if we're in /bin/, but only look in ../runtimes/:rid:/native + var dir = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory); + if (dir != null) + { + yield return Tuple.Create(false, Path.Combine(dir.FullName, + "runtimes", PlatformRuntimePrefix.Value + "-" + ArchitectureSubdir.Value, "native")); } + } - string? assemblyLocation = null; + string? assemblyLocation = null; #if !NETCOREAPP && !NET5_0_OR_GREATER && !NET8_0_OR_GREATER - try{ - // Look in the folder that *this* assembly is located. - assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - - } catch (NotImplementedException){ - // ignored - } + try{ + // Look in the folder that *this* assembly is located. + assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + } catch (NotImplementedException){ + // ignored + } #endif - if (!string.IsNullOrEmpty(assemblyLocation)) - yield return Tuple.Create(true, assemblyLocation!); + if (!string.IsNullOrEmpty(assemblyLocation)) + { + yield return Tuple.Create(true, assemblyLocation!); } + } - internal static IEnumerable SearchPossibilitiesForFile(string filename, IEnumerable? customSearchDirectories = null) + internal static IEnumerable SearchPossibilitiesForFile(string filename, IEnumerable? customSearchDirectories = null) + { + var attemptedPaths = new HashSet(); + foreach (var t in BaseFolders(customSearchDirectories)) { - var attemptedPaths = new HashSet(); - foreach (var t in BaseFolders(customSearchDirectories)) + if (string.IsNullOrEmpty(t.Item2)) { - if (string.IsNullOrEmpty(t.Item2)) continue; - var directory = Path.GetFullPath(t.Item2); - var searchSubDirs = t.Item1; - // Try architecture-specific subdirectories first - string path; - - if (searchSubDirs) - { - // First try the simple arch subdir since that is where the nuget native packages unpack - path = Path.Combine(directory, ArchitectureSubdir.Value, filename); - if (attemptedPaths.Add(path)) - { - yield return path; - } - } + continue; + } - // Try the folder itself - path = Path.Combine(directory, filename); + var directory = Path.GetFullPath(t.Item2); + var searchSubDirs = t.Item1; + // Try architecture-specific subdirectories first + string path; + if (searchSubDirs) + { + // First try the simple arch subdir since that is where the nuget native packages unpack + path = Path.Combine(directory, ArchitectureSubdir.Value, filename); if (attemptedPaths.Add(path)) { yield return path; } + } - if (searchSubDirs) + // Try the folder itself + path = Path.Combine(directory, filename); + + if (attemptedPaths.Add(path)) + { + yield return path; + } + + if (searchSubDirs) + { + // Last try native runtimes directory in case this is happening in .NET Core + path = Path.Combine(directory, "runtimes", + PlatformRuntimePrefix.Value + "-" + ArchitectureSubdir.Value, "native", filename); + if (attemptedPaths.Add(path)) { - // Last try native runtimes directory in case this is happening in .NET Core - path = Path.Combine(directory, "runtimes", - PlatformRuntimePrefix.Value + "-" + ArchitectureSubdir.Value, "native", filename); - if (attemptedPaths.Add(path)) - { - yield return path; - } + yield return path; } - } - } - /// - /// Return the path of the first file found with the given filename, or null if none found - /// - /// - /// - /// - static string? SearchForFile(string filename, IEnumerable? customSearchDirectories = null) - { - return SearchPossibilitiesForFile(filename, customSearchDirectories).FirstOrDefault(File.Exists); } } +} - internal interface ILibraryLoadLogger - { - void NotifyAttempt(string basename, string? fullPath, bool fileExists, bool previouslyLoaded, int? loadErrorCode); - } +internal interface ILibraryLoadLogger +{ + void NotifyAttempt(string basename, string? fullPath, bool fileExists, bool previouslyLoaded, int? loadErrorCode); +} +public static class ExecutableLocator +{ - public static class ExecutableLocator + private static string GetFilenameWithoutDirectory(string basename) => RuntimeFileLocator.ExecutableExtension.Value.Length > 0 + ? $"{basename}.{RuntimeFileLocator.ExecutableExtension.Value}" + : basename; + + /// + /// Raises an exception if the file couldn't be found + /// + /// + /// + /// + public static string? FindExecutable(string basename, IEnumerable? customSearchDirectories = null) { + var logger = new LoadLogger { Verb = "located", Filename = GetFilenameWithoutDirectory(basename) }; + if (TryLoadByBasename(basename, logger, out var exePath, customSearchDirectories)) + { + return exePath; + } + logger.RaiseException(); + return null; + } - private static string GetFilenameWithoutDirectory(string basename) => RuntimeFileLocator.ExecutableExtension.Value.Length > 0 - ? $"{basename}.{RuntimeFileLocator.ExecutableExtension.Value}" - : basename; - - - /// - /// Raises an exception if the file couldn't be found - /// - /// - /// - /// - public static string? FindExecutable(string basename, IEnumerable? customSearchDirectories = null) + private static readonly Lazy> ExecutablePathsByName = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); + + // Not yet implemented. + // static readonly Lazy> LibraryHandlesByFullPath = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); + + /// + /// Searches known directories for the provided file basename (or returns true if one is already loaded) + /// basename 'imageflow' -> imageflow.exe, imageflow + /// Basename is case-sensitive + /// + /// The executable name sans extension + /// Where to log attempts at assembly search and load + /// + /// Provide this if you want a custom search folder + /// True if previously or successfully loaded + internal static bool TryLoadByBasename(string basename, ILibraryLoadLogger log, out string? exePath, + IEnumerable? customSearchDirectories = null) + { + if (string.IsNullOrEmpty(basename)) { - var logger = new LoadLogger { Verb = "located", Filename = GetFilenameWithoutDirectory(basename) }; - if (TryLoadByBasename(basename, logger, out var exePath, customSearchDirectories)) - { - return exePath; - } - logger.RaiseException(); - return null; + throw new ArgumentNullException(nameof(basename)); } - private static readonly Lazy> ExecutablePathsByName = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); - - // Not yet implemented. - // static readonly Lazy> LibraryHandlesByFullPath = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); - - /// - /// Searches known directories for the provided file basename (or returns true if one is already loaded) - /// basename 'imageflow' -> imageflow.exe, imageflow - /// Basename is case-sensitive - /// - /// The executable name sans extension - /// Where to log attempts at assembly search and load - /// - /// Provide this if you want a custom search folder - /// True if previously or successfully loaded - internal static bool TryLoadByBasename(string basename, ILibraryLoadLogger log, out string? exePath, - IEnumerable? customSearchDirectories = null) + if (ExecutablePathsByName.Value.TryGetValue(basename, out exePath)) { - if (string.IsNullOrEmpty(basename)) - throw new ArgumentNullException(nameof(basename)); + return true; + } + + var filename = GetFilenameWithoutDirectory(basename); - if (ExecutablePathsByName.Value.TryGetValue(basename, out exePath)) + exePath = null; + foreach (var path in RuntimeFileLocator.SearchPossibilitiesForFile(filename, customSearchDirectories)) + { + if (!File.Exists(path)) { - return true; + log.NotifyAttempt(basename, path, false, false, 0); } - - var filename = GetFilenameWithoutDirectory(basename); - - exePath = null; - foreach (var path in RuntimeFileLocator.SearchPossibilitiesForFile(filename, customSearchDirectories)) + else { - if (!File.Exists(path)) - { - log.NotifyAttempt(basename, path, false, false, 0); - } - else - { - exePath = path; - ExecutablePathsByName.Value[basename] = exePath; - return true; - } + exePath = path; + ExecutablePathsByName.Value[basename] = exePath; + return true; } - return false; } + return false; } +} - - internal static class NativeLibraryLoader +internal static class NativeLibraryLoader +{ + private static string GetFilenameWithoutDirectory(string basename) => $"{RuntimeFileLocator.SharedLibraryPrefix.Value}{basename}.{RuntimeFileLocator.SharedLibraryExtension.Value}"; + + /// + /// Attempts to resolve DllNotFoundException and BadImageFormatExceptions + /// + /// + /// + /// + /// + /// + public static T? FixDllNotFoundException(string basename, Func invokingOperation, + IEnumerable? customSearchDirectories = null) { - private static string GetFilenameWithoutDirectory(string basename) => $"{RuntimeFileLocator.SharedLibraryPrefix.Value}{basename}.{RuntimeFileLocator.SharedLibraryExtension.Value}"; - - /// - /// Attempts to resolve DllNotFoundException and BadImageFormatExceptions - /// - /// - /// - /// - /// - /// - public static T? FixDllNotFoundException(string basename, Func invokingOperation, - IEnumerable? customSearchDirectories = null) + // It turns out that trying to do it "before" is 4-5x slower in cases where the standard loading mechanism works + // And catching the DllNotFoundException does not seem to measurably slow us down. So no "preventative" stuff. + Exception? caughtException; + try + { + return invokingOperation(); + } + catch (BadImageFormatException a) + { + caughtException = a; + } + catch (DllNotFoundException b) + { + caughtException = b; + } + + //Try loading + var logger = new LoadLogger + { FirstException = caughtException, Filename = GetFilenameWithoutDirectory(basename) }; + if (TryLoadByBasename(basename, logger, out _, customSearchDirectories)) { - // It turns out that trying to do it "before" is 4-5x slower in cases where the standard loading mechanism works - // And catching the DllNotFoundException does not seem to measurably slow us down. So no "preventative" stuff. - Exception? caughtException = null; try { return invokingOperation(); } - catch (BadImageFormatException a) - { - caughtException = a; - } - catch (DllNotFoundException b) + catch (DllNotFoundException last) { - caughtException = b; + logger.LastException = last; } - - //Try loading - var logger = new LoadLogger - { FirstException = caughtException, Filename = GetFilenameWithoutDirectory(basename) }; - if (TryLoadByBasename(basename, logger, out var _, customSearchDirectories)) - { - try - { - return invokingOperation(); - } - catch (DllNotFoundException last) - { - logger.LastException = last; - } - } - logger.RaiseException(); - return default; } + logger.RaiseException(); + return default; + } - private static readonly Lazy> LibraryHandlesByBasename = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); - - // Not yet implemented. - // static readonly Lazy> LibraryHandlesByFullPath = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); - - /// - /// Searches known directories for the provided file basename (or returns true if one is already loaded) - /// basename 'imageflow' -> imageflow.dll, libimageflow.so, libimageflow.dylib. - /// Basename is case-sensitive - /// - /// The library name sans extension or "lib" prefix - /// Where to log attempts at assembly search and load - /// Where to store the loaded library handle - /// Provide this if you want a custom search folder - /// True if previously or successfully loaded - public static bool TryLoadByBasename(string basename, ILibraryLoadLogger log, out IntPtr handle, IEnumerable? customSearchDirectories = null) + private static readonly Lazy> LibraryHandlesByBasename = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); + + // Not yet implemented. + // static readonly Lazy> LibraryHandlesByFullPath = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); + + /// + /// Searches known directories for the provided file basename (or returns true if one is already loaded) + /// basename 'imageflow' -> imageflow.dll, libimageflow.so, libimageflow.dylib. + /// Basename is case-sensitive + /// + /// The library name sans extension or "lib" prefix + /// Where to log attempts at assembly search and load + /// Where to store the loaded library handle + /// Provide this if you want a custom search folder + /// True if previously or successfully loaded + public static bool TryLoadByBasename(string basename, ILibraryLoadLogger log, out IntPtr handle, IEnumerable? customSearchDirectories = null) + { + if (string.IsNullOrEmpty(basename)) { - if (string.IsNullOrEmpty(basename)) - throw new ArgumentNullException(nameof(basename)); + throw new ArgumentNullException(nameof(basename)); + } + if (LibraryHandlesByBasename.Value.TryGetValue(basename, out handle)) + { + log.NotifyAttempt(basename, null, true, true, 0); + return true; + } + lock (LibraryHandlesByBasename) + { if (LibraryHandlesByBasename.Value.TryGetValue(basename, out handle)) { log.NotifyAttempt(basename, null, true, true, 0); return true; } - lock (LibraryHandlesByBasename) + var success = TryLoadByBasenameInternal(basename, log, out handle, customSearchDirectories); + if (success) { - if (LibraryHandlesByBasename.Value.TryGetValue(basename, out handle)) - { - log.NotifyAttempt(basename, null, true, true, 0); - return true; - } - var success = TryLoadByBasenameInternal(basename, log, out handle, customSearchDirectories); - if (success) LibraryHandlesByBasename.Value[basename] = handle; - return success; + LibraryHandlesByBasename.Value[basename] = handle; } - } + return success; + } + } - private static bool TryLoadByBasenameInternal(string basename, ILibraryLoadLogger log, out IntPtr handle, IEnumerable? customSearchDirectories = null) + private static bool TryLoadByBasenameInternal(string basename, ILibraryLoadLogger log, out IntPtr handle, IEnumerable? customSearchDirectories = null) + { + var filename = GetFilenameWithoutDirectory(basename); + foreach (var path in RuntimeFileLocator.SearchPossibilitiesForFile(filename, customSearchDirectories)) { - var filename = GetFilenameWithoutDirectory(basename); - foreach (var path in RuntimeFileLocator.SearchPossibilitiesForFile(filename, customSearchDirectories)) + if (!File.Exists(path)) { - if (!File.Exists(path)) - { - log.NotifyAttempt(basename, path, false, false, 0); - } - else - { - var success = LoadLibrary(path, out handle, out var errorCode); - log.NotifyAttempt(basename, path, true, false, errorCode); - if (success) - { - return true; - } - } + log.NotifyAttempt(basename, path, false, false, 0); } - handle = IntPtr.Zero; - return false; - } - - private static bool LoadLibrary(string fullPath, out IntPtr handle, out int? errorCode) - { - handle = RuntimeFileLocator.IsUnix ? UnixLoadLibrary.Execute(fullPath) : WindowsLoadLibrary.Execute(fullPath); - if (handle == IntPtr.Zero) + else { - errorCode = Marshal.GetLastWin32Error(); - return false; + var success = LoadLibrary(path, out handle, out var errorCode); + log.NotifyAttempt(basename, path, true, false, errorCode); + if (success) + { + return true; + } } - errorCode = null; - return true; } + handle = IntPtr.Zero; + return false; } - [SuppressUnmanagedCodeSecurity] - [SecurityCritical] - internal static class WindowsLoadLibrary + private static bool LoadLibrary(string fullPath, out IntPtr handle, out int? errorCode) { - [DllImport("kernel32", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)] - private static extern IntPtr LoadLibraryEx(string fileName, IntPtr reservedNull, uint flags); - - public static IntPtr Execute(string fileName) + handle = RuntimeFileLocator.IsUnix ? UnixLoadLibrary.Execute(fullPath) : WindowsLoadLibrary.Execute(fullPath); + if (handle == IntPtr.Zero) { - // Look in the library dir instead of the process dir - const uint loadWithAlteredSearchPath = 0x00000008; - return LoadLibraryEx(fileName, IntPtr.Zero, loadWithAlteredSearchPath); + errorCode = Marshal.GetLastWin32Error(); + return false; } + errorCode = null; + return true; } +} - [SuppressUnmanagedCodeSecurity] - [SecurityCritical] - internal static class UnixLoadLibrary - { - // TODO: unsure if this works on Mac OS X; it might be libc instead. dncore works, but mono is untested - [DllImport("libdl.so", SetLastError = true, CharSet = CharSet.Ansi)] - private static extern IntPtr dlopen(string fileName, int flags); +[SuppressUnmanagedCodeSecurity] +[SecurityCritical] +internal static class WindowsLoadLibrary +{ + [DllImport("kernel32", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr LoadLibraryEx(string fileName, IntPtr reservedNull, uint flags); - public static IntPtr Execute(string fileName) - { - const int rtldNow = 2; - return dlopen(fileName, rtldNow); - } + public static IntPtr Execute(string fileName) + { + // Look in the library dir instead of the process dir + const uint loadWithAlteredSearchPath = 0x00000008; + return LoadLibraryEx(fileName, IntPtr.Zero, loadWithAlteredSearchPath); } +} +[SuppressUnmanagedCodeSecurity] +[SecurityCritical] +internal static class UnixLoadLibrary +{ + // TODO: unsure if this works on Mac OS X; it might be libc instead. dncore works, but mono is untested + [DllImport("libdl.so", SetLastError = true, CharSet = CharSet.Ansi)] + private static extern IntPtr dlopen(string fileName, int flags); + public static IntPtr Execute(string fileName) + { + const int rtldNow = 2; + return dlopen(fileName, rtldNow); + } } diff --git a/src/Imageflow/Bindings/NativeMethods.cs b/src/Imageflow/Bindings/NativeMethods.cs index ba29c3c..811a670 100644 --- a/src/Imageflow/Bindings/NativeMethods.cs +++ b/src/Imageflow/Bindings/NativeMethods.cs @@ -1,132 +1,117 @@ using System.Runtime.InteropServices; -namespace Imageflow.Bindings -{ +namespace Imageflow.Bindings; - internal static class NativeMethods +internal static class NativeMethods +{ + public enum Lifetime { - public enum Lifetime - { - - /// OutlivesFunctionCall -> 0 - OutlivesFunctionCall = 0, - - /// OutlivesContext -> 1 - OutlivesContext = 1, - } - - // ReSharper disable once InconsistentNaming - public const int ABI_MAJOR = 3; - // ReSharper disable once InconsistentNaming - public const int ABI_MINOR = 0; - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_abi_compatible(uint imageflowAbiVerMajor, uint imageflowAbiVerMinor); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - public static extern uint imageflow_abi_version_major(); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - public static extern uint imageflow_abi_version_minor(); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr imageflow_context_create(uint imageflowAbiVerMajor, uint imageflowAbiVerMinor); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_begin_terminate(JobContextHandle context); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - public static extern void imageflow_context_destroy(IntPtr context); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_has_error(JobContextHandle context); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_error_recoverable(JobContextHandle context); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_error_try_clear(JobContextHandle context); - - /// Return Type: int32_t->int - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - public static extern int imageflow_context_error_code(JobContextHandle context); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - public static extern int imageflow_context_error_as_exit_code(JobContextHandle context); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - public static extern int imageflow_context_error_as_http_code(JobContextHandle context); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_print_and_exit_if_error(JobContextHandle context); - - ///response_in: void* - ///status_code_out: int64_t* - ///buffer_utf8_no_nulls_out: uint8_t** - ///buffer_size_out: size_t* - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_json_response_read(JobContextHandle context, JsonResponseHandle responseIn, - out int statusCodeOut, out IntPtr bufferUtf8NoNullsOut, out UIntPtr bufferSizeOut); - + /// OutlivesFunctionCall -> 0 + OutlivesFunctionCall = 0, - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_json_response_destroy(JobContextHandle context, IntPtr response); - - - - - ///pointer: void* - ///filename: char* - ///line: int32_t->int - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_memory_free(JobContextHandle context, IntPtr pointer, - IntPtr filename, int line); - - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_error_write_to_buffer(JobContextHandle context, IntPtr buffer, - UIntPtr bufferLength, - out UIntPtr bytesWritten); - - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr imageflow_context_send_json(JobContextHandle context, IntPtr method, - IntPtr jsonBuffer, UIntPtr jsonBufferSize); - - - - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr imageflow_context_memory_allocate(JobContextHandle context, IntPtr bytes, - IntPtr filename, int line); - - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_add_input_buffer(JobContextHandle context, int ioId, IntPtr buffer, - UIntPtr bufferByteCount, Lifetime lifetime); - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_add_output_buffer(JobContextHandle context, int ioId); - - - [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.I1)] - public static extern bool imageflow_context_get_output_buffer_by_id(JobContextHandle context, - int ioId, out IntPtr resultBuffer, out UIntPtr resultBufferLength); + /// OutlivesContext -> 1 + OutlivesContext = 1, + } + // ReSharper disable once InconsistentNaming + public const int ABI_MAJOR = 3; + // ReSharper disable once InconsistentNaming + public const int ABI_MINOR = 0; + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_abi_compatible(uint imageflowAbiVerMajor, uint imageflowAbiVerMinor); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + public static extern uint imageflow_abi_version_major(); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + public static extern uint imageflow_abi_version_minor(); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr imageflow_context_create(uint imageflowAbiVerMajor, uint imageflowAbiVerMinor); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_context_begin_terminate(JobContextHandle context); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + public static extern void imageflow_context_destroy(IntPtr context); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_context_has_error(JobContextHandle context); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_context_error_recoverable(JobContextHandle context); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_context_error_try_clear(JobContextHandle context); + + /// Return Type: int32_t->int + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + public static extern int imageflow_context_error_code(JobContextHandle context); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + public static extern int imageflow_context_error_as_exit_code(JobContextHandle context); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + public static extern int imageflow_context_error_as_http_code(JobContextHandle context); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_context_print_and_exit_if_error(JobContextHandle context); + + ///response_in: void* + ///status_code_out: int64_t* + ///buffer_utf8_no_nulls_out: uint8_t** + ///buffer_size_out: size_t* + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_json_response_read(JobContextHandle context, JsonResponseHandle responseIn, + out int statusCodeOut, out IntPtr bufferUtf8NoNullsOut, out UIntPtr bufferSizeOut); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_json_response_destroy(JobContextHandle context, IntPtr response); + + ///pointer: void* + ///filename: char* + ///line: int32_t->int + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_context_memory_free(JobContextHandle context, IntPtr pointer, + IntPtr filename, int line); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_context_error_write_to_buffer(JobContextHandle context, IntPtr buffer, + UIntPtr bufferLength, + out UIntPtr bytesWritten); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr imageflow_context_send_json(JobContextHandle context, IntPtr method, + IntPtr jsonBuffer, UIntPtr jsonBufferSize); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr imageflow_context_memory_allocate(JobContextHandle context, IntPtr bytes, + IntPtr filename, int line); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_context_add_input_buffer(JobContextHandle context, int ioId, IntPtr buffer, + UIntPtr bufferByteCount, Lifetime lifetime); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_context_add_output_buffer(JobContextHandle context, int ioId); + + [DllImport("imageflow", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool imageflow_context_get_output_buffer_by_id(JobContextHandle context, + int ioId, out IntPtr resultBuffer, out UIntPtr resultBufferLength); - } } diff --git a/src/Imageflow/Bindings/VersionInfo.cs b/src/Imageflow/Bindings/VersionInfo.cs index b202ce3..40f7544 100644 --- a/src/Imageflow/Bindings/VersionInfo.cs +++ b/src/Imageflow/Bindings/VersionInfo.cs @@ -1,73 +1,70 @@ using System.Text.Json.Nodes; -namespace Imageflow.Bindings +namespace Imageflow.Bindings; + +public class VersionInfo { - public class VersionInfo + private VersionInfo(JsonNode versionInfo) { - private VersionInfo(JsonNode versionInfo) - { - // LongVersionString = versionInfo.long_version_string.Value; - // LastGitCommit = versionInfo.last_git_commit.Value; - // DirtyWorkingTree = versionInfo.dirty_working_tree.Value; - // GitTag = versionInfo.git_tag?.Value; - // GitDescribeAlways = versionInfo.git_describe_always?.Value; - // // Sometimes Newtonsoft gives us a DateTime; other times a string. - // object dateTime = versionInfo.build_date.Value; - // if (dateTime is string time) - // { - // BuildDate = XmlConvert.ToDateTime(time, XmlDateTimeSerializationMode.Utc); - // } - // else if (dateTime is DateTime dt) - // { - // BuildDate = new DateTimeOffset(dt); - // } - - var obj = versionInfo.AsObject(); - const string longVersionMsg = "Imageflow get_version_info responded with null version_info.long_version_string"; - LongVersionString = obj.TryGetPropertyValue("long_version_string", out var longVersionValue) - ? longVersionValue?.GetValue() ?? throw new ImageflowAssertionFailed(longVersionMsg) - : throw new ImageflowAssertionFailed(longVersionMsg); - const string lastGitCommitMsg = "Imageflow get_version_info responded with null version_info.last_git_commit"; - LastGitCommit = obj.TryGetPropertyValue("last_git_commit", out var lastGitCommitValue) - ? lastGitCommitValue?.GetValue() ?? throw new ImageflowAssertionFailed(lastGitCommitMsg) - : throw new ImageflowAssertionFailed(lastGitCommitMsg); - const string dirtyWorkingTreeMsg = "Imageflow get_version_info responded with null version_info.dirty_working_tree"; - DirtyWorkingTree = obj.TryGetPropertyValue("dirty_working_tree", out var dirtyWorkingTreeValue) - ? dirtyWorkingTreeValue?.GetValue() ?? throw new ImageflowAssertionFailed(dirtyWorkingTreeMsg) - : throw new ImageflowAssertionFailed(dirtyWorkingTreeMsg); - const string buildDateMsg = "Imageflow get_version_info responded with null version_info.build_date"; - BuildDate = obj.TryGetPropertyValue("build_date", out var buildDateValue) - ? buildDateValue?.GetValue() ?? throw new ImageflowAssertionFailed(buildDateMsg) - : throw new ImageflowAssertionFailed(buildDateMsg); - // git tag and git describe are optional - GitTag = obj.TryGetPropertyValue("git_tag", out var gitTagValue) - ? gitTagValue?.GetValue() - : null; - GitDescribeAlways = obj.TryGetPropertyValue("git_describe_always", out var gitDescribeAlwaysValue) - ? gitDescribeAlwaysValue?.GetValue() - : null; + // LongVersionString = versionInfo.long_version_string.Value; + // LastGitCommit = versionInfo.last_git_commit.Value; + // DirtyWorkingTree = versionInfo.dirty_working_tree.Value; + // GitTag = versionInfo.git_tag?.Value; + // GitDescribeAlways = versionInfo.git_describe_always?.Value; + // // Sometimes Newtonsoft gives us a DateTime; other times a string. + // object dateTime = versionInfo.build_date.Value; + // if (dateTime is string time) + // { + // BuildDate = XmlConvert.ToDateTime(time, XmlDateTimeSerializationMode.Utc); + // } + // else if (dateTime is DateTime dt) + // { + // BuildDate = new DateTimeOffset(dt); + // } + var obj = versionInfo.AsObject(); + const string longVersionMsg = "Imageflow get_version_info responded with null version_info.long_version_string"; + LongVersionString = obj.TryGetPropertyValue("long_version_string", out var longVersionValue) + ? longVersionValue?.GetValue() ?? throw new ImageflowAssertionFailed(longVersionMsg) + : throw new ImageflowAssertionFailed(longVersionMsg); + const string lastGitCommitMsg = "Imageflow get_version_info responded with null version_info.last_git_commit"; + LastGitCommit = obj.TryGetPropertyValue("last_git_commit", out var lastGitCommitValue) + ? lastGitCommitValue?.GetValue() ?? throw new ImageflowAssertionFailed(lastGitCommitMsg) + : throw new ImageflowAssertionFailed(lastGitCommitMsg); + const string dirtyWorkingTreeMsg = "Imageflow get_version_info responded with null version_info.dirty_working_tree"; + DirtyWorkingTree = obj.TryGetPropertyValue("dirty_working_tree", out var dirtyWorkingTreeValue) + ? dirtyWorkingTreeValue?.GetValue() ?? throw new ImageflowAssertionFailed(dirtyWorkingTreeMsg) + : throw new ImageflowAssertionFailed(dirtyWorkingTreeMsg); + const string buildDateMsg = "Imageflow get_version_info responded with null version_info.build_date"; + BuildDate = obj.TryGetPropertyValue("build_date", out var buildDateValue) + ? buildDateValue?.GetValue() ?? throw new ImageflowAssertionFailed(buildDateMsg) + : throw new ImageflowAssertionFailed(buildDateMsg); + // git tag and git describe are optional + GitTag = obj.TryGetPropertyValue("git_tag", out var gitTagValue) + ? gitTagValue?.GetValue() + : null; + GitDescribeAlways = obj.TryGetPropertyValue("git_describe_always", out var gitDescribeAlwaysValue) + ? gitDescribeAlwaysValue?.GetValue() + : null; + } + internal static VersionInfo FromNode(JsonNode versionInfo) + { + return new VersionInfo(versionInfo); + } - } - internal static VersionInfo FromNode(JsonNode versionInfo) - { - return new VersionInfo(versionInfo); - } - - public string LongVersionString { get; private set; } - public string LastGitCommit { get; private set; } + public string LongVersionString { get; private set; } + public string LastGitCommit { get; private set; } - /// - /// Usually includes the last version tag, the number of commits since, and a shortened git hash - /// - public string? GitDescribeAlways { get; private set; } + /// + /// Usually includes the last version tag, the number of commits since, and a shortened git hash + /// + public string? GitDescribeAlways { get; private set; } - /// - /// May be null if the current version was not a tagged release - /// - public string? GitTag { get; private set; } - public bool DirtyWorkingTree { get; private set; } - public DateTimeOffset BuildDate { get; private set; } - } + /// + /// May be null if the current version was not a tagged release + /// + public string? GitTag { get; private set; } + public bool DirtyWorkingTree { get; private set; } + public DateTimeOffset BuildDate { get; private set; } } diff --git a/src/Imageflow/Fluent/AnyColor.cs b/src/Imageflow/Fluent/AnyColor.cs index 622238d..fae9773 100644 --- a/src/Imageflow/Fluent/AnyColor.cs +++ b/src/Imageflow/Fluent/AnyColor.cs @@ -2,53 +2,52 @@ using Imageflow.Bindings; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +/// +/// Represents a color with or without transparency. +/// +public readonly struct AnyColor { - /// - /// Represents a color with or without transparency. - /// - public readonly struct AnyColor + private AnyColor(ColorKind kind, SrgbColor srgb = default) { - private AnyColor(ColorKind kind, SrgbColor srgb = default) - { - _kind = kind; - _srgb = srgb; - } - private readonly ColorKind _kind; - private readonly SrgbColor _srgb; - public static AnyColor Black => new AnyColor(ColorKind.Black); - public static AnyColor Transparent => new AnyColor(ColorKind.Transparent); + _kind = kind; + _srgb = srgb; + } + private readonly ColorKind _kind; + private readonly SrgbColor _srgb; + public static AnyColor Black => new AnyColor(ColorKind.Black); + public static AnyColor Transparent => new AnyColor(ColorKind.Transparent); - /// - /// Parses color in RGB, RGBA, RRGGBB or RRGGBBAA format - /// - /// - /// - public static AnyColor FromHexSrgb(string hex) => new AnyColor(ColorKind.Srgb, SrgbColor.FromHex(hex)); + /// + /// Parses color in RGB, RGBA, RRGGBB or RRGGBBAA format + /// + /// + /// + public static AnyColor FromHexSrgb(string hex) => new AnyColor(ColorKind.Srgb, SrgbColor.FromHex(hex)); - public static AnyColor Srgb(SrgbColor c) => new AnyColor(ColorKind.Srgb, c); + public static AnyColor Srgb(SrgbColor c) => new AnyColor(ColorKind.Srgb, c); - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() + { + switch (_kind) { - switch (_kind) - { - case ColorKind.Black: return new { black = (string?)null }; - case ColorKind.Transparent: return new { transparent = (string?)null }; - case ColorKind.Srgb: return new { srgb = new { hex = _srgb.ToHexUnprefixed() } }; - default: throw new ImageflowAssertionFailed("default"); - } + case ColorKind.Black: return new { black = (string?)null }; + case ColorKind.Transparent: return new { transparent = (string?)null }; + case ColorKind.Srgb: return new { srgb = new { hex = _srgb.ToHexUnprefixed() } }; + default: throw new ImageflowAssertionFailed("default"); } + } - public JsonNode ToJsonNode() + public JsonNode ToJsonNode() + { + switch (_kind) { - switch (_kind) - { - case ColorKind.Black: return new JsonObject() { { "black", (string?)null } }; - case ColorKind.Transparent: return new JsonObject() { { "transparent", (string?)null } }; - case ColorKind.Srgb: return new JsonObject() { { "srgb", new JsonObject() { { "hex", _srgb.ToHexUnprefixed() } } } }; - default: throw new ImageflowAssertionFailed("default"); - } + case ColorKind.Black: return new JsonObject() { { "black", (string?)null } }; + case ColorKind.Transparent: return new JsonObject() { { "transparent", (string?)null } }; + case ColorKind.Srgb: return new JsonObject() { { "srgb", new JsonObject() { { "hex", _srgb.ToHexUnprefixed() } } } }; + default: throw new ImageflowAssertionFailed("default"); } } } diff --git a/src/Imageflow/Fluent/BufferedStreamSource.cs b/src/Imageflow/Fluent/BufferedStreamSource.cs index d283cf0..178cb42 100644 --- a/src/Imageflow/Fluent/BufferedStreamSource.cs +++ b/src/Imageflow/Fluent/BufferedStreamSource.cs @@ -13,7 +13,10 @@ private BufferedStreamSource(Stream stream, bool disposeUnderlying, bool seekToS throw new ArgumentException("Stream must be seekable if seekToStart is true"); } var length = stream.CanSeek ? stream.Length : 0; - if (length >= int.MaxValue) throw new OverflowException("Streams cannot exceed 2GB"); + if (length >= int.MaxValue) + { + throw new OverflowException("Streams cannot exceed 2GB"); + } _underlying = stream; _disposeUnderlying = disposeUnderlying; @@ -113,7 +116,6 @@ public ReadOnlyMemory BorrowReadOnlyMemory() public bool AsyncPreferred => _underlying is not MemoryStream && _underlying is not UnmanagedMemoryStream; - /// /// Seeks to the beginning of the stream before reading. /// You swear not to close, dispose, or reuse the stream or its underlying memory/stream until after this wrapper and the job are disposed. diff --git a/src/Imageflow/Fluent/BuildDecodeResult.cs b/src/Imageflow/Fluent/BuildDecodeResult.cs index ad22061..5060ea5 100644 --- a/src/Imageflow/Fluent/BuildDecodeResult.cs +++ b/src/Imageflow/Fluent/BuildDecodeResult.cs @@ -1,13 +1,11 @@ -namespace Imageflow.Fluent -{ - public class BuildDecodeResult - { - public string? PreferredMimeType { get; internal init; } - public string? PreferredExtension { get; internal init; } - public int IoId { get; internal init; } - public int Width { get; internal init; } - public int Height { get; internal init; } +namespace Imageflow.Fluent; - } +public class BuildDecodeResult +{ + public string? PreferredMimeType { get; internal init; } + public string? PreferredExtension { get; internal init; } + public int IoId { get; internal init; } + public int Width { get; internal init; } + public int Height { get; internal init; } } diff --git a/src/Imageflow/Fluent/BuildEncodeResult.cs b/src/Imageflow/Fluent/BuildEncodeResult.cs index 7749154..b3a2226 100644 --- a/src/Imageflow/Fluent/BuildEncodeResult.cs +++ b/src/Imageflow/Fluent/BuildEncodeResult.cs @@ -1,45 +1,44 @@ -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public record BuildEncodeResult { - public record BuildEncodeResult - { - // internal BuildEncodeResult(string preferredMimeType, - // string preferredExtension, int ioId, int width, int height, IOutputDestination destination) - // { - // - // PreferredMimeType = preferredMimeType; - // PreferredExtension = preferredExtension; - // IoId = ioId; - // Width = width; - // Height = height; - // Destination = destination; - // } + // internal BuildEncodeResult(string preferredMimeType, + // string preferredExtension, int ioId, int width, int height, IOutputDestination destination) + // { + // + // PreferredMimeType = preferredMimeType; + // PreferredExtension = preferredExtension; + // IoId = ioId; + // Width = width; + // Height = height; + // Destination = destination; + // } - internal BuildEncodeResult() - { - } - // maps to "preferred_mime_type" in json - public required string PreferredMimeType { get; init; } + internal BuildEncodeResult() + { + } + // maps to "preferred_mime_type" in json + public required string PreferredMimeType { get; init; } - // maps to "preferred_extension" in json - public required string PreferredExtension { get; init; } - public required int IoId { get; init; } - // maps to "w" in json - public required int Width { get; init; } - // maps to "h" in json - public required int Height { get; init; } + // maps to "preferred_extension" in json + public required string PreferredExtension { get; init; } + public required int IoId { get; init; } + // maps to "w" in json + public required int Width { get; init; } + // maps to "h" in json + public required int Height { get; init; } - public required IOutputDestination Destination { get; init; } + public required IOutputDestination Destination { get; init; } - /// - /// If this Destination is a BytesDestination, returns the ArraySegment - otherwise null - /// Returns the byte segment for the given output ID (if that output is a BytesDestination) - /// - public ArraySegment? TryGetBytes() => (Destination is BytesDestination d) ? d.GetBytes() : default; - } - // Width = er.w, - // Height = er.h, - // IoId = er.io_id, - // PreferredExtension = er.preferred_extension, - // PreferredMimeType = er.preferred_mime_type, - // Destination = outputs[(int)er.io_id.Value] + /// + /// If this Destination is a BytesDestination, returns the ArraySegment - otherwise null + /// Returns the byte segment for the given output ID (if that output is a BytesDestination) + /// + public ArraySegment? TryGetBytes() => (Destination is BytesDestination d) ? d.GetBytes() : default; } +// Width = er.w, +// Height = er.h, +// IoId = er.io_id, +// PreferredExtension = er.preferred_extension, +// PreferredMimeType = er.preferred_mime_type, +// Destination = outputs[(int)er.io_id.Value] diff --git a/src/Imageflow/Fluent/BuildEndpoint.cs b/src/Imageflow/Fluent/BuildEndpoint.cs index 6100d75..3f8bed6 100644 --- a/src/Imageflow/Fluent/BuildEndpoint.cs +++ b/src/Imageflow/Fluent/BuildEndpoint.cs @@ -1,32 +1,27 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent -{ - - /// - /// Represents an endpoint in the operations graph, such as an Encode node. No more nodes can be chained to this one. - /// Only allows executing the job. - /// - public class BuildEndpoint : BuildItemBase - { - internal BuildEndpoint(ImageJob builder, JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) : base(builder, nodeData, inputNode, - canvasNode) - { } +namespace Imageflow.Fluent; - public FinishJobBuilder Finish() => new FinishJobBuilder(Builder, default); +/// +/// Represents an endpoint in the operations graph, such as an Encode node. No more nodes can be chained to this one. +/// Only allows executing the job. +/// +public class BuildEndpoint : BuildItemBase +{ + internal BuildEndpoint(ImageJob builder, JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) : base(builder, nodeData, inputNode, + canvasNode) + { } - [Obsolete("Use Finish().WithCancellationToken")] - public FinishJobBuilder FinishWithToken(CancellationToken token) => new FinishJobBuilder(Builder, token); + public FinishJobBuilder Finish() => new FinishJobBuilder(Builder, default); - [Obsolete("Use Finish().WithCancellationTimeout")] - public FinishJobBuilder FinishWithTimeout(int milliseconds) - { - using var tokenSource = new CancellationTokenSource(milliseconds); - return FinishWithToken(tokenSource.Token); - } + [Obsolete("Use Finish().WithCancellationToken")] + public FinishJobBuilder FinishWithToken(CancellationToken token) => new FinishJobBuilder(Builder, token); + [Obsolete("Use Finish().WithCancellationTimeout")] + public FinishJobBuilder FinishWithTimeout(int milliseconds) + { + using var tokenSource = new CancellationTokenSource(milliseconds); + return FinishWithToken(tokenSource.Token); } - - } diff --git a/src/Imageflow/Fluent/BuildItemBase.cs b/src/Imageflow/Fluent/BuildItemBase.cs index 80e67df..8670b30 100644 --- a/src/Imageflow/Fluent/BuildItemBase.cs +++ b/src/Imageflow/Fluent/BuildItemBase.cs @@ -1,32 +1,31 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +/// +/// Base class for nodes in the job graph +/// +public class BuildItemBase { - /// - /// Base class for nodes in the job graph - /// - public class BuildItemBase - { - internal ImageJob Builder { get; } - internal BuildNode? Input { get; } - internal BuildNode? Canvas { get; } - internal JsonNode? NodeData { get; } - internal long Uid { get; } - public override bool Equals(object? obj) => Uid == (obj as BuildItemBase)?.Uid; - public override int GetHashCode() => (int)Uid; //We probably don't need to worry about more than 2 billion instances? + internal ImageJob Builder { get; } + internal BuildNode? Input { get; } + internal BuildNode? Canvas { get; } + internal JsonNode? NodeData { get; } + internal long Uid { get; } + public override bool Equals(object? obj) => Uid == (obj as BuildItemBase)?.Uid; + public override int GetHashCode() => (int)Uid; //We probably don't need to worry about more than 2 billion instances? - private static long _next; - private static long NextUid() => Interlocked.Increment(ref _next); - internal bool IsEmpty => NodeData == null; + private static long _next; + private static long NextUid() => Interlocked.Increment(ref _next); + internal bool IsEmpty => NodeData == null; - protected BuildItemBase(ImageJob builder, JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) - { - Builder = builder; - Input = inputNode; - Canvas = canvasNode; - NodeData = nodeData; - Uid = NextUid(); - builder.AddNode(this); - } + protected BuildItemBase(ImageJob builder, JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) + { + Builder = builder; + Input = inputNode; + Canvas = canvasNode; + NodeData = nodeData; + Uid = NextUid(); + builder.AddNode(this); } } diff --git a/src/Imageflow/Fluent/BuildJobResult.cs b/src/Imageflow/Fluent/BuildJobResult.cs index 3b39370..cc4f560 100644 --- a/src/Imageflow/Fluent/BuildJobResult.cs +++ b/src/Imageflow/Fluent/BuildJobResult.cs @@ -1,188 +1,199 @@ using Imageflow.Bindings; // ReSharper disable CheckNamespace -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public class BuildJobResult { + [Obsolete("Use ImageJob.FinishAsync() to get a result; you should never create a BuildJobResult directly.")] + internal BuildJobResult() + { + _encodeResults = new Dictionary(); + DecodeResults = new List(); + EncodeResults = new List(); + PerformanceDetails = new PerformanceDetails(null); + } - public class BuildJobResult + private BuildJobResult(IReadOnlyCollection decodeResults, + IReadOnlyCollection encodeResults, + PerformanceDetails performanceDetails) { + _encodeResults = encodeResults.ToDictionary(r => r.IoId); + DecodeResults = decodeResults; + EncodeResults = encodeResults; + PerformanceDetails = performanceDetails; + } + + private readonly Dictionary _encodeResults; + + /// + /// A collection of the decoded image details produced by the job + /// + public IReadOnlyCollection DecodeResults { get; private set; } + + /// + /// A collection of the encoded images produced by the job + /// + public IReadOnlyCollection EncodeResults { get; private set; } + + /// + /// Details about the runtime performance of the job + /// + public PerformanceDetails PerformanceDetails { get; private set; } + + /// + /// The first encoded image produced by the job (with the lowest io_id) + /// + public BuildEncodeResult? First => EncodeResults.FirstOrDefault(); - [Obsolete("Use ImageJob.FinishAsync() to get a result; you should never create a BuildJobResult directly.")] - internal BuildJobResult() + public BuildEncodeResult this[int ioId] => _encodeResults[ioId]; + + /// + /// Returns null if the encode result by the given io_id doesn't exist. + /// + /// + /// + public BuildEncodeResult? TryGet(int ioId) => _encodeResults.TryGetValue(ioId, out var result) ? result : null; + + internal static BuildJobResult From(IJsonResponse response, Dictionary outputs) + { + var v = response.Parse(); + if (v == null) { - _encodeResults = new Dictionary(); - DecodeResults = new List(); - EncodeResults = new List(); - PerformanceDetails = new PerformanceDetails(null); + throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response " + response.CopyString()); } - private BuildJobResult(IReadOnlyCollection decodeResults, - IReadOnlyCollection encodeResults, - PerformanceDetails performanceDetails) + bool? success = v.AsObject().TryGetPropertyValue("success", out var successValue) + ? successValue?.GetValue() + : null; + switch (success) { - _encodeResults = encodeResults.ToDictionary(r => r.IoId); - DecodeResults = decodeResults; - EncodeResults = encodeResults; - PerformanceDetails = performanceDetails; + case false: + throw new ImageflowAssertionFailed("BuildJobResult.From cannot convert a failure: " + response.CopyString()); + case null: + throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response " + response.CopyString()); } - private readonly Dictionary _encodeResults; - - /// - /// A collection of the decoded image details produced by the job - /// - public IReadOnlyCollection DecodeResults { get; private set; } - - /// - /// A collection of the encoded images produced by the job - /// - public IReadOnlyCollection EncodeResults { get; private set; } + var data = v.AsObject().TryGetPropertyValue("data", out var dataValue) + ? dataValue + : null; + if (data == null) + { + throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response ('data' missing) " + response.CopyString()); + } - /// - /// Details about the runtime performance of the job - /// - public PerformanceDetails PerformanceDetails { get; private set; } + // IEnumerable encodes = (v.data.job_result ?? v.data.build_result).encodes; + // + // var encodeResults = encodes.Select(er => new BuildEncodeResult + // { + // Width = er.w, + // Height = er.h, + // IoId = er.io_id, + // PreferredExtension = er.preferred_extension, + // PreferredMimeType = er.preferred_mime_type, + // Destination = outputs[(int)er.io_id.Value] + // }).OrderBy(er => er.IoId).ToList(); + // + // IEnumerable decodes = (v.data.job_result ?? v.data.build_result).decodes ?? Enumerable.Empty(); + // + // var decodeResults = decodes.Select(er => new BuildDecodeResult + // { + // Width = er.w, + // Height = er.h, + // IoId = er.io_id, + // PreferredExtension = er.preferred_extension, + // PreferredMimeType = er.preferred_mime_type, + // }).OrderBy(er => er.IoId).ToList(); + + var jobResult = data.AsObject().TryGetPropertyValue("job_result", out var jobResultValue) + ? jobResultValue + : null; + + if (jobResult == null) + { + data.AsObject().TryGetPropertyValue("build_result", out jobResultValue); + } - /// - /// The first encoded image produced by the job (with the lowest io_id) - /// - public BuildEncodeResult? First => EncodeResults.FirstOrDefault(); + if (jobResult == null) + { + throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response (missing job_result or build_result) " + response.CopyString()); + } - public BuildEncodeResult this[int ioId] => _encodeResults[ioId]; + var encodeResults = new List(); + if (!jobResult.AsObject().TryGetPropertyValue("encodes", out var encodeArray)) + { + // This is unusual + throw new ImageflowAssertionFailed("encodes = null"); + } - /// - /// Returns null if the encode result by the given io_id doesn't exist. - /// - /// - /// - public BuildEncodeResult? TryGet(int ioId) => _encodeResults.TryGetValue(ioId, out var result) ? result : null; + if (!jobResult.AsObject().TryGetPropertyValue("decodes", out var decodeArray)) + { + throw new ImageflowAssertionFailed("decodes = null"); + } + var requiredMessage = "BuildJobResult.From cannot parse response (missing required properties io_id, w, h, preferred_extension, or preferred_mime_type) " + response.CopyString(); - internal static BuildJobResult From(IJsonResponse response, Dictionary outputs) + // Parse from JsonNode + foreach (var encode in encodeArray?.AsArray() ?? []) { - var v = response.Parse(); - if (v == null) throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response " + response.CopyString()); - bool? success = v.AsObject().TryGetPropertyValue("success", out var successValue) - ? successValue?.GetValue() - : null; - switch (success) + if (encode == null) { - case false: - throw new ImageflowAssertionFailed("BuildJobResult.From cannot convert a failure: " + response.CopyString()); - case null: - throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response " + response.CopyString()); + continue; } - - var data = v.AsObject().TryGetPropertyValue("data", out var dataValue) - ? dataValue - : null; - if (data == null) throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response ('data' missing) " + response.CopyString()); - - - // IEnumerable encodes = (v.data.job_result ?? v.data.build_result).encodes; - // - // var encodeResults = encodes.Select(er => new BuildEncodeResult - // { - // Width = er.w, - // Height = er.h, - // IoId = er.io_id, - // PreferredExtension = er.preferred_extension, - // PreferredMimeType = er.preferred_mime_type, - // Destination = outputs[(int)er.io_id.Value] - // }).OrderBy(er => er.IoId).ToList(); - // - // IEnumerable decodes = (v.data.job_result ?? v.data.build_result).decodes ?? Enumerable.Empty(); - // - // var decodeResults = decodes.Select(er => new BuildDecodeResult - // { - // Width = er.w, - // Height = er.h, - // IoId = er.io_id, - // PreferredExtension = er.preferred_extension, - // PreferredMimeType = er.preferred_mime_type, - // }).OrderBy(er => er.IoId).ToList(); - - var jobResult = data.AsObject().TryGetPropertyValue("job_result", out var jobResultValue) - ? jobResultValue - : null; - - if (jobResult == null) data.AsObject().TryGetPropertyValue("build_result", out jobResultValue); - if (jobResult == null) throw new ImageflowAssertionFailed("BuildJobResult.From cannot parse response (missing job_result or build_result) " + response.CopyString()); - - var encodeResults = new List(); - if (!jobResult.AsObject().TryGetPropertyValue("encodes", out var encodeArray)) + // ioId, w, h, preferred_extension, preferred_mime_type are required + if (!encode.AsObject().TryGetPropertyValue("io_id", out var ioIdValue) + || !encode.AsObject().TryGetPropertyValue("w", out var wValue) + || !encode.AsObject().TryGetPropertyValue("h", out var hValue) + || !encode.AsObject().TryGetPropertyValue("preferred_extension", out var preferredExtensionValue) + || !encode.AsObject().TryGetPropertyValue("preferred_mime_type", out var preferredMimeTypeValue)) { - // This is unusual - throw new ImageflowAssertionFailed("encodes = null"); + throw new ImageflowAssertionFailed(requiredMessage); } - - if (!jobResult.AsObject().TryGetPropertyValue("decodes", out var decodeArray)) + var ioId = ioIdValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage); + encodeResults.Add(new BuildEncodeResult { - throw new ImageflowAssertionFailed("decodes = null"); - } - var requiredMessage = "BuildJobResult.From cannot parse response (missing required properties io_id, w, h, preferred_extension, or preferred_mime_type) " + response.CopyString(); + IoId = ioId, + Width = wValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), + Height = hValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), + PreferredExtension = preferredExtensionValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), + PreferredMimeType = preferredMimeTypeValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), + Destination = outputs[ioId] + }); + } - // Parse from JsonNode - foreach (var encode in encodeArray?.AsArray() ?? []) + // Parse from JsonNode + var decodeResults = new List(); + foreach (var decode in (decodeArray?.AsArray() ?? [])) + { + if (decode == null) { - if (encode == null) continue; - // ioId, w, h, preferred_extension, preferred_mime_type are required - if (!encode.AsObject().TryGetPropertyValue("io_id", out var ioIdValue) - || !encode.AsObject().TryGetPropertyValue("w", out var wValue) - || !encode.AsObject().TryGetPropertyValue("h", out var hValue) - || !encode.AsObject().TryGetPropertyValue("preferred_extension", out var preferredExtensionValue) - || !encode.AsObject().TryGetPropertyValue("preferred_mime_type", out var preferredMimeTypeValue)) - { - throw new ImageflowAssertionFailed(requiredMessage); - } - var ioId = ioIdValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage); - encodeResults.Add(new BuildEncodeResult - { - IoId = ioId, - Width = wValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), - Height = hValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), - PreferredExtension = preferredExtensionValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), - PreferredMimeType = preferredMimeTypeValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), - Destination = outputs[ioId] - }); + continue; } - - // Parse from JsonNode - var decodeResults = new List(); - foreach (var decode in (decodeArray?.AsArray() ?? [])) + // ioId, w, h, preferred_extension, preferred_mime_type are required + if (!decode.AsObject().TryGetPropertyValue("io_id", out var ioIdValue) + || !decode.AsObject().TryGetPropertyValue("w", out var wValue) + || !decode.AsObject().TryGetPropertyValue("h", out var hValue) + || !decode.AsObject().TryGetPropertyValue("preferred_extension", out var preferredExtensionValue) + || !decode.AsObject().TryGetPropertyValue("preferred_mime_type", out var preferredMimeTypeValue)) { - if (decode == null) continue; - // ioId, w, h, preferred_extension, preferred_mime_type are required - if (!decode.AsObject().TryGetPropertyValue("io_id", out var ioIdValue) - || !decode.AsObject().TryGetPropertyValue("w", out var wValue) - || !decode.AsObject().TryGetPropertyValue("h", out var hValue) - || !decode.AsObject().TryGetPropertyValue("preferred_extension", out var preferredExtensionValue) - || !decode.AsObject().TryGetPropertyValue("preferred_mime_type", out var preferredMimeTypeValue)) - { - throw new ImageflowAssertionFailed(requiredMessage); - } - - decodeResults.Add(new BuildDecodeResult - { - IoId = ioIdValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), - Width = wValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), - Height = hValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), - PreferredExtension = preferredExtensionValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), - PreferredMimeType = preferredMimeTypeValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), - }); + throw new ImageflowAssertionFailed(requiredMessage); } - - - var perfDetails = new PerformanceDetails(jobResult.AsObject().TryGetPropertyValue("performance", out var perfValue) ? perfValue : null); - - // There may be fewer reported outputs than registered ones - encoding is conditional on input, I think - return new BuildJobResult(decodeResults.OrderBy(er => er.IoId).ToList(), - encodeResults.OrderBy(er => er.IoId).ToList(), perfDetails); + decodeResults.Add(new BuildDecodeResult + { + IoId = ioIdValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), + Width = wValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), + Height = hValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), + PreferredExtension = preferredExtensionValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), + PreferredMimeType = preferredMimeTypeValue?.GetValue() ?? throw new ImageflowAssertionFailed(requiredMessage), + }); } + var perfDetails = new PerformanceDetails(jobResult.AsObject().TryGetPropertyValue("performance", out var perfValue) ? perfValue : null); + // There may be fewer reported outputs than registered ones - encoding is conditional on input, I think + return new BuildJobResult(decodeResults.OrderBy(er => er.IoId).ToList(), + encodeResults.OrderBy(er => er.IoId).ToList(), perfDetails); } - } diff --git a/src/Imageflow/Fluent/BuildNode.cs b/src/Imageflow/Fluent/BuildNode.cs index 8ee6825..9542506 100644 --- a/src/Imageflow/Fluent/BuildNode.cs +++ b/src/Imageflow/Fluent/BuildNode.cs @@ -1,719 +1,714 @@ using System.Drawing; using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public class BuildNode : BuildItemBase { - public class BuildNode : BuildItemBase + internal static BuildNode StartNode(ImageJob graph, JsonNode data) + => new BuildNode(graph, data, null, null); + + /// + /// Encode the result to the given destination (such as a BytesDestination or StreamDestination) + /// + /// Where to write the bytes + /// + /// An encoder class, such as `new MozJpegEncoder()` + /// + public BuildEndpoint Encode(IOutputDestination destination, int ioId, IEncoderPreset encoderPreset) { - internal static BuildNode StartNode(ImageJob graph, JsonNode data) - => new BuildNode(graph, data, null, null); - - /// - /// Encode the result to the given destination (such as a BytesDestination or StreamDestination) - /// - /// Where to write the bytes - /// - /// An encoder class, such as `new MozJpegEncoder()` - /// - public BuildEndpoint Encode(IOutputDestination destination, int ioId, IEncoderPreset encoderPreset) - { - Builder.AddOutput(ioId, destination); - // return new BuildEndpoint(Builder, - // new {encode = new {io_id = ioId, preset = encoderPreset?.ToImageflowDynamic()}}, this, null); - return new BuildEndpoint(Builder, - new JsonObject() { ["encode"] = new JsonObject() { ["io_id"] = ioId, ["preset"] = encoderPreset.ToJsonNode() } }, this, null); - } - /// - /// Encode the result to the given destination (such as a BytesDestination or StreamDestination) - /// - /// Where to write the bytes - /// An encoder class, such as `new MozJpegEncoder()` - /// - public BuildEndpoint Encode(IOutputDestination destination, IEncoderPreset encoderPreset) => - Encode(destination, Builder.GenerateIoId(), encoderPreset); - - [Obsolete("Use Encode(IOutputDestination destination, int ioId, IEncoderPreset encoderPreset)")] - public BuildEndpoint EncodeToBytes(int ioId, IEncoderPreset encoderPreset) => - Encode(new BytesDestination(), ioId, encoderPreset); - public BuildEndpoint EncodeToBytes(IEncoderPreset encoderPreset) => - Encode(new BytesDestination(), encoderPreset); - - [Obsolete("Use Encode(IOutputDestination destination, int ioId, IEncoderPreset encoderPreset)")] - public BuildEndpoint EncodeToStream(Stream stream, bool disposeStream, int ioId, IEncoderPreset encoderPreset) => - Encode(new StreamDestination(stream, disposeStream), ioId, encoderPreset); - public BuildEndpoint EncodeToStream(Stream stream, bool disposeStream, IEncoderPreset encoderPreset) => - Encode(new StreamDestination(stream, disposeStream), encoderPreset); - - - private BuildNode(ImageJob builder, JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) : base(builder, nodeData, inputNode, - canvasNode) - { } - - // private BuildNode To(object data) => new BuildNode(Builder, data, this, null); - - private BuildNode To(JsonNode data) => new BuildNode(Builder, data, this, null); - // private BuildNode NodeWithCanvas(BuildNode canvas, object data) => new BuildNode(Builder, data, this, canvas); - private BuildNode NodeWithCanvas(BuildNode canvas, JsonNode data) => new BuildNode(Builder, data, this, canvas); - - - /// - /// Downscale the image to fit within the given dimensions, but do not upscale. See Constrain() for more options. - /// - /// - /// - /// - public BuildNode ConstrainWithin(uint? w, uint? h) + Builder.AddOutput(ioId, destination); + // return new BuildEndpoint(Builder, + // new {encode = new {io_id = ioId, preset = encoderPreset?.ToImageflowDynamic()}}, this, null); + return new BuildEndpoint(Builder, + new JsonObject() { ["encode"] = new JsonObject() { ["io_id"] = ioId, ["preset"] = encoderPreset.ToJsonNode() } }, this, null); + } + /// + /// Encode the result to the given destination (such as a BytesDestination or StreamDestination) + /// + /// Where to write the bytes + /// An encoder class, such as `new MozJpegEncoder()` + /// + public BuildEndpoint Encode(IOutputDestination destination, IEncoderPreset encoderPreset) => + Encode(destination, Builder.GenerateIoId(), encoderPreset); + + [Obsolete("Use Encode(IOutputDestination destination, int ioId, IEncoderPreset encoderPreset)")] + public BuildEndpoint EncodeToBytes(int ioId, IEncoderPreset encoderPreset) => + Encode(new BytesDestination(), ioId, encoderPreset); + public BuildEndpoint EncodeToBytes(IEncoderPreset encoderPreset) => + Encode(new BytesDestination(), encoderPreset); + + [Obsolete("Use Encode(IOutputDestination destination, int ioId, IEncoderPreset encoderPreset)")] + public BuildEndpoint EncodeToStream(Stream stream, bool disposeStream, int ioId, IEncoderPreset encoderPreset) => + Encode(new StreamDestination(stream, disposeStream), ioId, encoderPreset); + public BuildEndpoint EncodeToStream(Stream stream, bool disposeStream, IEncoderPreset encoderPreset) => + Encode(new StreamDestination(stream, disposeStream), encoderPreset); + + private BuildNode(ImageJob builder, JsonNode nodeData, BuildNode? inputNode, BuildNode? canvasNode) : base(builder, nodeData, inputNode, + canvasNode) + { } + + // private BuildNode To(object data) => new BuildNode(Builder, data, this, null); + + private BuildNode To(JsonNode data) => new BuildNode(Builder, data, this, null); + // private BuildNode NodeWithCanvas(BuildNode canvas, object data) => new BuildNode(Builder, data, this, canvas); + private BuildNode NodeWithCanvas(BuildNode canvas, JsonNode data) => new BuildNode(Builder, data, this, canvas); + + /// + /// Downscale the image to fit within the given dimensions, but do not upscale. See Constrain() for more options. + /// + /// + /// + /// + public BuildNode ConstrainWithin(uint? w, uint? h) + { + var jsonObject = new JsonObject { - var jsonObject = new JsonObject + ["constrain"] = new JsonObject { - ["constrain"] = new JsonObject - { - ["mode"] = "within", - ["w"] = w, - ["h"] = h, - } - }; - return To(jsonObject); - } - /// - /// Downscale the image to fit within the given dimensions, but do not upscale. See Constrain() for more options. - /// - /// - /// - /// - /// - public BuildNode ConstrainWithin(uint? w, uint? h, ResampleHints? hints) + ["mode"] = "within", + ["w"] = w, + ["h"] = h, + } + }; + return To(jsonObject); + } + /// + /// Downscale the image to fit within the given dimensions, but do not upscale. See Constrain() for more options. + /// + /// + /// + /// + /// + public BuildNode ConstrainWithin(uint? w, uint? h, ResampleHints? hints) + { + var jsonObject = new JsonObject { - var jsonObject = new JsonObject - { - ["constrain"] = new JsonObject - { - ["mode"] = "within", - ["w"] = w, - ["h"] = h, - ["hints"] = hints?.ToJsonNode() - } - }; - return To(jsonObject); - } - - /// - /// Scale an image using the given Constraint object. - /// - /// - /// - public BuildNode Constrain(Constraint constraint) - //=> To(new { constrain = constraint.ToImageflowDynamic() }); - => To(new JsonObject { ["constrain"] = constraint.ToJsonNode() }); - /// - /// Distort the image to exactly the given dimensions. - /// - /// - /// - /// - public BuildNode Distort(uint w, uint h) => Distort(w, h, null); - /// - /// Distort the image to exactly the given dimensions. - /// - /// - /// - /// - /// - public BuildNode Distort(uint w, uint h, ResampleHints? hints) - // => To(new - // { - // resample_2d = new - // { - // w, - // h, - // hints = hints?.ToImageflowDynamic() - // } - // }); - => To(new JsonObject - { - ["resample_2d"] = new JsonObject - { - ["w"] = w, - ["h"] = h, - ["hints"] = hints?.ToJsonNode() - } - }); - - /// - /// Crops the image to the given coordinates - /// - /// - /// - /// - /// - /// - public BuildNode Crop(int x1, int y1, int x2, int y2) - // => To(new - // { - // crop = new - // { - // x1, - // y1, - // x2, - // y2 - // } - // }); - => To(new JsonObject - { - ["crop"] = new JsonObject - { - ["x1"] = x1, - ["y1"] = y1, - ["x2"] = x2, - ["y2"] = y2 - } - }); - - /// - /// Region is like a crop command, but you can specify coordinates outside of the image and - /// thereby add padding. It's like a window. Coordinates are in pixels. - /// - /// - /// - /// - /// - /// - /// - public BuildNode Region(int x1, int y1, int x2, int y2, AnyColor backgroundColor) - // => To(new - // { - // region = new - // { - // x1, - // y1, - // x2, - // y2, - // background_color = backgroundColor.ToImageflowDynamic() - // } - // }); - => To(new JsonObject + ["constrain"] = new JsonObject { - ["region"] = new JsonObject - { - ["x1"] = x1, - ["y1"] = y1, - ["x2"] = x2, - ["y2"] = y2, - ["background_color"] = backgroundColor.ToJsonNode() - } - }); - - /// - /// Region is like a crop command, but you can specify coordinates outside of the image and - /// thereby add padding. It's like a window. - /// You can specify a region as a percentage of the image's width and height. - /// - /// - /// - /// - /// - /// - /// - public BuildNode RegionPercent(float x1, float y1, float x2, float y2, AnyColor backgroundColor) - // => To(new - // { - // region_percent = new - // { - // x1, - // y1, - // x2, - // y2, - // background_color = backgroundColor.ToImageflowDynamic() - // } - // }); - => To(new JsonObject + ["mode"] = "within", + ["w"] = w, + ["h"] = h, + ["hints"] = hints?.ToJsonNode() + } + }; + return To(jsonObject); + } + + /// + /// Scale an image using the given Constraint object. + /// + /// + /// + public BuildNode Constrain(Constraint constraint) + //=> To(new { constrain = constraint.ToImageflowDynamic() }); + => To(new JsonObject { ["constrain"] = constraint.ToJsonNode() }); + /// + /// Distort the image to exactly the given dimensions. + /// + /// + /// + /// + public BuildNode Distort(uint w, uint h) => Distort(w, h, null); + /// + /// Distort the image to exactly the given dimensions. + /// + /// + /// + /// + /// + public BuildNode Distort(uint w, uint h, ResampleHints? hints) + // => To(new + // { + // resample_2d = new + // { + // w, + // h, + // hints = hints?.ToImageflowDynamic() + // } + // }); + => To(new JsonObject + { + ["resample_2d"] = new JsonObject { - ["region_percent"] = new JsonObject - { - ["x1"] = x1, - ["y1"] = y1, - ["x2"] = x2, - ["y2"] = y2, - ["background_color"] = backgroundColor.ToJsonNode() - } - }); - - /// - /// Crops away whitespace of any color at the edges of the image. - /// - /// (1..255). determines how much noise/edges to tolerate before cropping - /// is finalized. 80 is a good starting point. - /// determines how much of the image to restore after cropping to - /// provide some padding. 0.5 (half a percent) is a good starting point. - /// - public BuildNode CropWhitespace(int threshold, float percentPadding) - // => To(new - // { - // crop_whitespace = new - // { - // threshold, - // percent_padding = percentPadding - // } - // }); - => To(new JsonObject + ["w"] = w, + ["h"] = h, + ["hints"] = hints?.ToJsonNode() + } + }); + + /// + /// Crops the image to the given coordinates + /// + /// + /// + /// + /// + /// + public BuildNode Crop(int x1, int y1, int x2, int y2) + // => To(new + // { + // crop = new + // { + // x1, + // y1, + // x2, + // y2 + // } + // }); + => To(new JsonObject + { + ["crop"] = new JsonObject { - ["crop_whitespace"] = new JsonObject - { - ["threshold"] = threshold, - ["percent_padding"] = percentPadding - } - }); - - /// - /// Does not honor encoding or decoding parameters. Use ImageJob.BuildCommandString() instead unless - /// you are actually combining this node with others in a job. - /// - /// - /// - public BuildNode ResizerCommands(string commandString) - // => To(new - // { - // command_string = new - // { - // kind = "ir4", - // value = commandString - // } - // }); - => To(new JsonObject + ["x1"] = x1, + ["y1"] = y1, + ["x2"] = x2, + ["y2"] = y2 + } + }); + + /// + /// Region is like a crop command, but you can specify coordinates outside of the image and + /// thereby add padding. It's like a window. Coordinates are in pixels. + /// + /// + /// + /// + /// + /// + /// + public BuildNode Region(int x1, int y1, int x2, int y2, AnyColor backgroundColor) + // => To(new + // { + // region = new + // { + // x1, + // y1, + // x2, + // y2, + // background_color = backgroundColor.ToImageflowDynamic() + // } + // }); + => To(new JsonObject + { + ["region"] = new JsonObject { - ["command_string"] = new JsonObject - { - ["kind"] = "ir4", - ["value"] = commandString - } - }); - - /// - /// Flips the image vertically - /// - /// - public BuildNode FlipVertical() - // => To(new {flip_v = (string?)null}); - => To(new JsonObject { ["flip_v"] = null }); - /// - /// Flips the image horizontally - /// - /// - public BuildNode FlipHorizontal() // => To(new {flip_h = (string?)null }); - => To(new JsonObject { ["flip_h"] = null }); - - /// - /// Rotates the image 90 degrees clockwise. - /// - /// - public BuildNode Rotate90() // => To(new {rotate_90 = (string?)null }); - => To(new JsonObject { ["rotate_90"] = null }); - /// - /// Rotates the image 180 degrees clockwise. - /// - /// - public BuildNode Rotate180() // To(new {rotate_180 = (string?)null }); - => To(new JsonObject { ["rotate_180"] = null }); - /// - /// Rotates the image 270 degrees clockwise. (same as 90 degrees counterclockwise). - /// - /// - public BuildNode Rotate270() //To(new {rotate_270 = (string?)null }); - => To(new JsonObject { ["rotate_270"] = null }); - /// - /// Swaps the x and y dimensions of the image - /// - /// - public BuildNode Transpose() //=> To(new {transpose = (string?)null }); - => To(new JsonObject { ["transpose"] = null }); - - /// - /// Allows you to generate multiple outputs by branching the graph - /// - /// var r = await b.Decode(imageBytes) - /// .Branch(f => f.EncodeToBytes(new WebPLosslessEncoder())) - /// .Branch(f => f.EncodeToBytes(new WebPLossyEncoder(50))) - /// .EncodeToBytes(new LibPngEncoder()) - /// .Finish().InProcessAsync(); - /// - /// - /// - /// - public BuildNode Branch(Func f) + ["x1"] = x1, + ["y1"] = y1, + ["x2"] = x2, + ["y2"] = y2, + ["background_color"] = backgroundColor.ToJsonNode() + } + }); + + /// + /// Region is like a crop command, but you can specify coordinates outside of the image and + /// thereby add padding. It's like a window. + /// You can specify a region as a percentage of the image's width and height. + /// + /// + /// + /// + /// + /// + /// + public BuildNode RegionPercent(float x1, float y1, float x2, float y2, AnyColor backgroundColor) + // => To(new + // { + // region_percent = new + // { + // x1, + // y1, + // x2, + // y2, + // background_color = backgroundColor.ToImageflowDynamic() + // } + // }); + => To(new JsonObject { - f(this); - return this; - } - - /// - /// Copies (not composes) the given rectangle from input to canvas. - /// You cannot copy from a BGRA input to a BGR canvas. - /// - /// - /// - /// - /// - public BuildNode CopyRectTo(BuildNode canvas, Rectangle area, Point to) - // => NodeWithCanvas(canvas, new - // { - // copy_rect_to_canvas = new - // { - // from_x = area.X, - // from_y = area.Y, - // w = area.Width, - // h = area.Height, - // x = to.X, - // y = to.Y - // } - // }); - => NodeWithCanvas(canvas, new JsonObject + ["region_percent"] = new JsonObject { - ["copy_rect_to_canvas"] = new JsonObject - { - ["from_x"] = area.X, - ["from_y"] = area.Y, - ["w"] = area.Width, - ["h"] = area.Height, - ["x"] = to.X, - ["y"] = to.Y - } - }); - - /// - /// Draws the input image to the given rectangle on the canvas, distorting if the aspect ratios differ. - /// - /// - /// - /// - /// - /// - /// - public BuildNode DrawImageExactTo(BuildNode canvas, Rectangle to, ResampleHints? hints, CompositingMode? blend) - // => NodeWithCanvas(canvas, new - // { - // draw_image_exact = new - // { - // w = to.Width, - // h = to.Height, - // x = to.X, - // y = to.Y, - // blend = blend?.ToString()?.ToLowerInvariant(), - // hints = hints?.ToImageflowDynamic() - // } - // }); - => NodeWithCanvas(canvas, new JsonObject + ["x1"] = x1, + ["y1"] = y1, + ["x2"] = x2, + ["y2"] = y2, + ["background_color"] = backgroundColor.ToJsonNode() + } + }); + + /// + /// Crops away whitespace of any color at the edges of the image. + /// + /// (1..255). determines how much noise/edges to tolerate before cropping + /// is finalized. 80 is a good starting point. + /// determines how much of the image to restore after cropping to + /// provide some padding. 0.5 (half a percent) is a good starting point. + /// + public BuildNode CropWhitespace(int threshold, float percentPadding) + // => To(new + // { + // crop_whitespace = new + // { + // threshold, + // percent_padding = percentPadding + // } + // }); + => To(new JsonObject + { + ["crop_whitespace"] = new JsonObject { - ["draw_image_exact"] = new JsonObject - { - ["w"] = to.Width, - ["h"] = to.Height, - ["x"] = to.X, - ["y"] = to.Y, - ["blend"] = blend?.ToString().ToLowerInvariant(), - ["hints"] = hints?.ToJsonNode() - } - }); - - - - /// - /// Rounds all 4 corners using the given radius in pixels - /// - /// - /// - /// - public BuildNode RoundAllImageCorners(int radiusPixels, AnyColor backgroundColor) - // => To(new - // { - // round_image_corners = new - // { - // radius = new - // { - // pixels = radiusPixels - // }, - // background_color = backgroundColor.ToImageflowDynamic() - // } - // }); - => To(new JsonObject + ["threshold"] = threshold, + ["percent_padding"] = percentPadding + } + }); + + /// + /// Does not honor encoding or decoding parameters. Use ImageJob.BuildCommandString() instead unless + /// you are actually combining this node with others in a job. + /// + /// + /// + public BuildNode ResizerCommands(string commandString) + // => To(new + // { + // command_string = new + // { + // kind = "ir4", + // value = commandString + // } + // }); + => To(new JsonObject + { + ["command_string"] = new JsonObject { - ["round_image_corners"] = new JsonObject - { - ["radius"] = new JsonObject - { - ["pixels"] = radiusPixels - }, - ["background_color"] = backgroundColor.ToJsonNode() - } - }); - - /// - /// Rounds all 4 corners by a percentage. 100% would make a circle if the image was square. - /// - /// - /// - /// - public BuildNode RoundAllImageCornersPercent(float radiusPercent, AnyColor backgroundColor) - // => To(new - // { - // round_image_corners = new - // { - // radius = new - // { - // percentage = radiusPercent - // }, - // background_color = backgroundColor.ToImageflowDynamic() - // } - // }); - => To(new JsonObject + ["kind"] = "ir4", + ["value"] = commandString + } + }); + + /// + /// Flips the image vertically + /// + /// + public BuildNode FlipVertical() + // => To(new {flip_v = (string?)null}); + => To(new JsonObject { ["flip_v"] = null }); + /// + /// Flips the image horizontally + /// + /// + public BuildNode FlipHorizontal() // => To(new {flip_h = (string?)null }); + => To(new JsonObject { ["flip_h"] = null }); + + /// + /// Rotates the image 90 degrees clockwise. + /// + /// + public BuildNode Rotate90() // => To(new {rotate_90 = (string?)null }); + => To(new JsonObject { ["rotate_90"] = null }); + /// + /// Rotates the image 180 degrees clockwise. + /// + /// + public BuildNode Rotate180() // To(new {rotate_180 = (string?)null }); + => To(new JsonObject { ["rotate_180"] = null }); + /// + /// Rotates the image 270 degrees clockwise. (same as 90 degrees counterclockwise). + /// + /// + public BuildNode Rotate270() //To(new {rotate_270 = (string?)null }); + => To(new JsonObject { ["rotate_270"] = null }); + /// + /// Swaps the x and y dimensions of the image + /// + /// + public BuildNode Transpose() //=> To(new {transpose = (string?)null }); + => To(new JsonObject { ["transpose"] = null }); + + /// + /// Allows you to generate multiple outputs by branching the graph + /// + /// var r = await b.Decode(imageBytes) + /// .Branch(f => f.EncodeToBytes(new WebPLosslessEncoder())) + /// .Branch(f => f.EncodeToBytes(new WebPLossyEncoder(50))) + /// .EncodeToBytes(new LibPngEncoder()) + /// .Finish().InProcessAsync(); + /// + /// + /// + /// + public BuildNode Branch(Func f) + { + f(this); + return this; + } + + /// + /// Copies (not composes) the given rectangle from input to canvas. + /// You cannot copy from a BGRA input to a BGR canvas. + /// + /// + /// + /// + /// + public BuildNode CopyRectTo(BuildNode canvas, Rectangle area, Point to) + // => NodeWithCanvas(canvas, new + // { + // copy_rect_to_canvas = new + // { + // from_x = area.X, + // from_y = area.Y, + // w = area.Width, + // h = area.Height, + // x = to.X, + // y = to.Y + // } + // }); + => NodeWithCanvas(canvas, new JsonObject + { + ["copy_rect_to_canvas"] = new JsonObject { - ["round_image_corners"] = new JsonObject - { - ["radius"] = new JsonObject - { - ["percentage"] = radiusPercent - }, - ["background_color"] = backgroundColor.ToJsonNode() - } - }); - - /// - /// Fills the given rectangle with the specified color - /// - /// - /// - /// - /// - /// - /// - public BuildNode FillRectangle(int x1, int y1, int x2, int y2, AnyColor color) - // => To(new - // { - // fill_rect = new - // { - // x1, - // y1, - // x2, - // y2, - // color = color.ToImageflowDynamic() - // } - // }); - => To(new JsonObject + ["from_x"] = area.X, + ["from_y"] = area.Y, + ["w"] = area.Width, + ["h"] = area.Height, + ["x"] = to.X, + ["y"] = to.Y + } + }); + + /// + /// Draws the input image to the given rectangle on the canvas, distorting if the aspect ratios differ. + /// + /// + /// + /// + /// + /// + /// + public BuildNode DrawImageExactTo(BuildNode canvas, Rectangle to, ResampleHints? hints, CompositingMode? blend) + // => NodeWithCanvas(canvas, new + // { + // draw_image_exact = new + // { + // w = to.Width, + // h = to.Height, + // x = to.X, + // y = to.Y, + // blend = blend?.ToString()?.ToLowerInvariant(), + // hints = hints?.ToImageflowDynamic() + // } + // }); + => NodeWithCanvas(canvas, new JsonObject + { + ["draw_image_exact"] = new JsonObject { - ["fill_rect"] = new JsonObject - { - ["x1"] = x1, - ["y1"] = y1, - ["x2"] = x2, - ["y2"] = y2, - ["color"] = color.ToJsonNode() - } - }); - - /// - /// Adds padding of the given color by enlarging the canvas on the sides specified. - /// - /// - /// - /// - /// - /// - /// - public BuildNode ExpandCanvas(int left, int top, int right, int bottom, AnyColor color) - // => To(new - // { - // expand_canvas = new - // { - // left, - // top, - // right, - // bottom, - // color = color.ToImageflowDynamic() - // } - // }); - => To(new JsonObject + ["w"] = to.Width, + ["h"] = to.Height, + ["x"] = to.X, + ["y"] = to.Y, + ["blend"] = blend?.ToString().ToLowerInvariant(), + ["hints"] = hints?.ToJsonNode() + } + }); + + /// + /// Rounds all 4 corners using the given radius in pixels + /// + /// + /// + /// + public BuildNode RoundAllImageCorners(int radiusPixels, AnyColor backgroundColor) + // => To(new + // { + // round_image_corners = new + // { + // radius = new + // { + // pixels = radiusPixels + // }, + // background_color = backgroundColor.ToImageflowDynamic() + // } + // }); + => To(new JsonObject + { + ["round_image_corners"] = new JsonObject { - ["expand_canvas"] = new JsonObject + ["radius"] = new JsonObject { - ["left"] = left, - ["top"] = top, - ["right"] = right, - ["bottom"] = bottom, - ["color"] = color.ToJsonNode() - } - }); - /// - /// This command is not endorsed as it operates in the sRGB space and does not produce perfect results. - /// - /// - /// - public BuildNode WhiteBalanceSrgb(int threshold) - // => To(new - // { - // white_balance_histogram_area_threshold_srgb = new - // { - // threshold - // } - // }); - => To(new JsonObject + ["pixels"] = radiusPixels + }, + ["background_color"] = backgroundColor.ToJsonNode() + } + }); + + /// + /// Rounds all 4 corners by a percentage. 100% would make a circle if the image was square. + /// + /// + /// + /// + public BuildNode RoundAllImageCornersPercent(float radiusPercent, AnyColor backgroundColor) + // => To(new + // { + // round_image_corners = new + // { + // radius = new + // { + // percentage = radiusPercent + // }, + // background_color = backgroundColor.ToImageflowDynamic() + // } + // }); + => To(new JsonObject + { + ["round_image_corners"] = new JsonObject { - ["white_balance_histogram_area_threshold_srgb"] = new JsonObject + ["radius"] = new JsonObject { - ["threshold"] = threshold - } - }); - - /// - /// Set the transparency of the image from 0 (transparent) to 1 (opaque) - /// - /// - /// - public BuildNode TransparencySrgb(float opacity) - // => To(new - // { - // color_filter_srgb = new - // { - // alpha = opacity - // } - // }); - => To(new JsonObject + ["percentage"] = radiusPercent + }, + ["background_color"] = backgroundColor.ToJsonNode() + } + }); + + /// + /// Fills the given rectangle with the specified color + /// + /// + /// + /// + /// + /// + /// + public BuildNode FillRectangle(int x1, int y1, int x2, int y2, AnyColor color) + // => To(new + // { + // fill_rect = new + // { + // x1, + // y1, + // x2, + // y2, + // color = color.ToImageflowDynamic() + // } + // }); + => To(new JsonObject + { + ["fill_rect"] = new JsonObject { - ["color_filter_srgb"] = new JsonObject - { - ["alpha"] = opacity - } - }); - - /// - /// Adjust contrast between -1 and 1. - /// - /// -1...1 - /// - public BuildNode ContrastSrgb(float amount) - // => To(new - // { - // color_filter_srgb = new - // { - // contrast = amount - // } - // }); - => To(new JsonObject + ["x1"] = x1, + ["y1"] = y1, + ["x2"] = x2, + ["y2"] = y2, + ["color"] = color.ToJsonNode() + } + }); + + /// + /// Adds padding of the given color by enlarging the canvas on the sides specified. + /// + /// + /// + /// + /// + /// + /// + public BuildNode ExpandCanvas(int left, int top, int right, int bottom, AnyColor color) + // => To(new + // { + // expand_canvas = new + // { + // left, + // top, + // right, + // bottom, + // color = color.ToImageflowDynamic() + // } + // }); + => To(new JsonObject + { + ["expand_canvas"] = new JsonObject { - ["color_filter_srgb"] = new JsonObject - { - ["contrast"] = amount - } - }); - - /// - /// Adjust brightness between -1 and 1. - /// - /// -1...1 - /// - public BuildNode BrightnessSrgb(float amount) - // => To(new - // { - // color_filter_srgb = new - // { - // brightness = amount - // } - // }); - => To(new JsonObject + ["left"] = left, + ["top"] = top, + ["right"] = right, + ["bottom"] = bottom, + ["color"] = color.ToJsonNode() + } + }); + /// + /// This command is not endorsed as it operates in the sRGB space and does not produce perfect results. + /// + /// + /// + public BuildNode WhiteBalanceSrgb(int threshold) + // => To(new + // { + // white_balance_histogram_area_threshold_srgb = new + // { + // threshold + // } + // }); + => To(new JsonObject + { + ["white_balance_histogram_area_threshold_srgb"] = new JsonObject { - ["color_filter_srgb"] = new JsonObject - { - ["brightness"] = amount - } - }); - - /// - /// Adjust saturation between -1 and 1. - /// - /// -1...1 - /// - public BuildNode SaturationSrgb(float amount) - // => To(new - // { - // color_filter_srgb = new - // { - // saturation = amount - // } - // }); - => To(new JsonObject + ["threshold"] = threshold + } + }); + + /// + /// Set the transparency of the image from 0 (transparent) to 1 (opaque) + /// + /// + /// + public BuildNode TransparencySrgb(float opacity) + // => To(new + // { + // color_filter_srgb = new + // { + // alpha = opacity + // } + // }); + => To(new JsonObject + { + ["color_filter_srgb"] = new JsonObject { - ["color_filter_srgb"] = new JsonObject - { - ["saturation"] = amount - } - }); - - /// - /// Apply filters like grayscale, sepia, or inversion in the sRGB color space - /// - /// - /// - public BuildNode ColorFilterSrgb(ColorFilterSrgb filter) - // => To(new - // { - // color_filter_srgb = filter.ToString().ToLowerInvariant() - // }); - => To(new JsonObject + ["alpha"] = opacity + } + }); + + /// + /// Adjust contrast between -1 and 1. + /// + /// -1...1 + /// + public BuildNode ContrastSrgb(float amount) + // => To(new + // { + // color_filter_srgb = new + // { + // contrast = amount + // } + // }); + => To(new JsonObject + { + ["color_filter_srgb"] = new JsonObject { - ["color_filter_srgb"] = filter.ToString().ToLowerInvariant() - }); - - /// - /// Draw a watermark from the given BytesSource or StreamSource - /// - /// - /// - /// - [Obsolete("Use Watermark(IMemorySource source, ..) instead. BufferedStreamSource and MemorySource are preferred over BytesSource and StreamSource.")] - public BuildNode Watermark(IBytesSource source, WatermarkOptions watermark) => - Watermark(source.ToMemorySource(), null, watermark); - - /// - /// Draw a watermark from the given BufferedStreamSource or MemorySource - /// - /// - /// - /// - public BuildNode Watermark(IAsyncMemorySource source, WatermarkOptions watermark) => - Watermark(source, null, watermark); - - /// - /// Draw a watermark from the given BytesSource or StreamSource - /// - /// - /// - /// - /// - [Obsolete("Use Watermark(IMemorySource source, ..) instead. BufferedStreamSource and MemorySource are preferred over BytesSource and StreamSource.")] - public BuildNode Watermark(IBytesSource source, int? ioId, WatermarkOptions watermark) + ["contrast"] = amount + } + }); + + /// + /// Adjust brightness between -1 and 1. + /// + /// -1...1 + /// + public BuildNode BrightnessSrgb(float amount) + // => To(new + // { + // color_filter_srgb = new + // { + // brightness = amount + // } + // }); + => To(new JsonObject { - return Watermark(source.ToMemorySource(), ioId, watermark); - } - - /// - /// Draw a watermark from the given BufferedStreamSource or MemorySource - /// - /// - /// - /// - /// - public BuildNode Watermark(IAsyncMemorySource source, int? ioId, WatermarkOptions watermark) + ["color_filter_srgb"] = new JsonObject + { + ["brightness"] = amount + } + }); + + /// + /// Adjust saturation between -1 and 1. + /// + /// -1...1 + /// + public BuildNode SaturationSrgb(float amount) + // => To(new + // { + // color_filter_srgb = new + // { + // saturation = amount + // } + // }); + => To(new JsonObject { - ioId ??= Builder.GenerateIoId(); - Builder.AddInput(ioId.Value, source); - // return To(new - // { - // watermark = watermark.ToImageflowDynamic(ioId.Value) - // }); - return To(new JsonObject + ["color_filter_srgb"] = new JsonObject { - ["watermark"] = watermark.ToJsonNode(ioId.Value) - }); + ["saturation"] = amount + } + }); + + /// + /// Apply filters like grayscale, sepia, or inversion in the sRGB color space + /// + /// + /// + public BuildNode ColorFilterSrgb(ColorFilterSrgb filter) + // => To(new + // { + // color_filter_srgb = filter.ToString().ToLowerInvariant() + // }); + => To(new JsonObject + { + ["color_filter_srgb"] = filter.ToString().ToLowerInvariant() + }); + + /// + /// Draw a watermark from the given BytesSource or StreamSource + /// + /// + /// + /// + [Obsolete("Use Watermark(IMemorySource source, ..) instead. BufferedStreamSource and MemorySource are preferred over BytesSource and StreamSource.")] + public BuildNode Watermark(IBytesSource source, WatermarkOptions watermark) => + Watermark(source.ToMemorySource(), null, watermark); + + /// + /// Draw a watermark from the given BufferedStreamSource or MemorySource + /// + /// + /// + /// + public BuildNode Watermark(IAsyncMemorySource source, WatermarkOptions watermark) => + Watermark(source, null, watermark); + + /// + /// Draw a watermark from the given BytesSource or StreamSource + /// + /// + /// + /// + /// + [Obsolete("Use Watermark(IMemorySource source, ..) instead. BufferedStreamSource and MemorySource are preferred over BytesSource and StreamSource.")] + public BuildNode Watermark(IBytesSource source, int? ioId, WatermarkOptions watermark) + { + return Watermark(source.ToMemorySource(), ioId, watermark); + } - } + /// + /// Draw a watermark from the given BufferedStreamSource or MemorySource + /// + /// + /// + /// + /// + public BuildNode Watermark(IAsyncMemorySource source, int? ioId, WatermarkOptions watermark) + { + ioId ??= Builder.GenerateIoId(); + Builder.AddInput(ioId.Value, source); + // return To(new + // { + // watermark = watermark.ToImageflowDynamic(ioId.Value) + // }); + return To(new JsonObject + { + ["watermark"] = watermark.ToJsonNode(ioId.Value) + }); } + } diff --git a/src/Imageflow/Fluent/BytesDestination.cs b/src/Imageflow/Fluent/BytesDestination.cs index 3e5b84a..242405b 100644 --- a/src/Imageflow/Fluent/BytesDestination.cs +++ b/src/Imageflow/Fluent/BytesDestination.cs @@ -19,20 +19,36 @@ public Task RequestCapacityAsync(int bytes) public Task WriteAsync(ArraySegment bytes, CancellationToken cancellationToken) { - if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.WriteAsync called before RequestCapacityAsync"); - if (bytes.Array == null) throw new ImageflowAssertionFailed("BytesDestination.WriteAsync called with null array"); + if (_m == null) + { + throw new ImageflowAssertionFailed("BytesDestination.WriteAsync called before RequestCapacityAsync"); + } + + if (bytes.Array == null) + { + throw new ImageflowAssertionFailed("BytesDestination.WriteAsync called with null array"); + } + return _m.WriteAsync(bytes.Array, bytes.Offset, bytes.Count, cancellationToken); } public Task FlushAsync(CancellationToken cancellationToken) { - if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.FlushAsync called before RequestCapacityAsync"); + if (_m == null) + { + throw new ImageflowAssertionFailed("BytesDestination.FlushAsync called before RequestCapacityAsync"); + } + return _m.FlushAsync(cancellationToken); } public ArraySegment GetBytes() { - if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.GetBytes called before RequestCapacityAsync"); + if (_m == null) + { + throw new ImageflowAssertionFailed("BytesDestination.GetBytes called before RequestCapacityAsync"); + } + if (!_m.TryGetBuffer(out var bytes)) { throw new ImageflowAssertionFailed("MemoryStream TryGetBuffer should not fail here"); @@ -43,17 +59,28 @@ public ArraySegment GetBytes() public void RequestCapacity(int bytes) { _m ??= new MemoryStream(bytes); - if (_m.Capacity < bytes) _m.Capacity = bytes; + if (_m.Capacity < bytes) + { + _m.Capacity = bytes; + } } public void Write(ReadOnlySpan data) { - if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.Write called before RequestCapacity"); + if (_m == null) + { + throw new ImageflowAssertionFailed("BytesDestination.Write called before RequestCapacity"); + } + _m.WriteSpan(data); } public ValueTask FastWriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken) { - if (_m == null) throw new ImageflowAssertionFailed("BytesDestination.FastWriteAsync called before RequestCapacityAsync"); + if (_m == null) + { + throw new ImageflowAssertionFailed("BytesDestination.FastWriteAsync called before RequestCapacityAsync"); + } + return _m.WriteMemoryAsync(data, cancellationToken); } public void Flush() diff --git a/src/Imageflow/Fluent/Constraint.cs b/src/Imageflow/Fluent/Constraint.cs index f73fa7e..19dc6d8 100644 --- a/src/Imageflow/Fluent/Constraint.cs +++ b/src/Imageflow/Fluent/Constraint.cs @@ -1,85 +1,108 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public class Constraint { - public class Constraint + public Constraint(uint? w, uint? h) : this(ConstraintMode.Within, w, h) { } + public Constraint(ConstraintMode mode, uint? w, uint? h, ResampleHints hints, AnyColor? canvasColor) { - public Constraint(uint? w, uint? h) : this(ConstraintMode.Within, w, h) { } - public Constraint(ConstraintMode mode, uint? w, uint? h, ResampleHints hints, AnyColor? canvasColor) + Mode = mode; + W = w; + H = h; + Hints = hints; + CanvasColor = canvasColor; + if (w == null && h == null) { - Mode = mode; - W = w; - H = h; - Hints = hints; - CanvasColor = canvasColor; - if (w == null && h == null) - throw new ArgumentNullException(nameof(w), "Either w or h must be non-null."); + throw new ArgumentNullException(nameof(w), "Either w or h must be non-null."); } + } - public Constraint(ConstraintMode mode, uint? w, uint? h) + public Constraint(ConstraintMode mode, uint? w, uint? h) + { + Mode = mode; + W = w; + H = h; + if (w == null && h == null) { - Mode = mode; - W = w; - H = h; - if (w == null && h == null) - throw new ArgumentNullException(nameof(w), "Either w or h must be non-null."); + throw new ArgumentNullException(nameof(w), "Either w or h must be non-null."); } - public ConstraintMode Mode { get; set; } - public uint? W { get; set; } - public uint? H { get; set; } - public ResampleHints? Hints { get; set; } - public AnyColor? CanvasColor { get; set; } + } + public ConstraintMode Mode { get; set; } + public uint? W { get; set; } + public uint? H { get; set; } + public ResampleHints? Hints { get; set; } + public AnyColor? CanvasColor { get; set; } + + public ConstraintGravity? Gravity { get; set; } + + public Constraint SetConstraintMode(ConstraintMode mode) + { + Mode = mode; + return this; + } - public ConstraintGravity? Gravity { get; set; } + public Constraint SetHints(ResampleHints hints) + { + Hints = hints; + return this; + } - public Constraint SetConstraintMode(ConstraintMode mode) + public Constraint SetCanvasColor(AnyColor? canvasColor) + { + CanvasColor = canvasColor; + return this; + } + + public Constraint SetGravity(ConstraintGravity gravity) + { + Gravity = gravity; + + return this; + } + + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() + { + return new { - Mode = mode; - return this; - } + mode = Mode.ToString().ToLowerInvariant(), + w = W, + h = H, + hints = Hints?.ToImageflowDynamic(), + canvas_color = CanvasColor?.ToImageflowDynamic(), + gravity = Gravity?.ToImageflowDynamic() + }; + } - public Constraint SetHints(ResampleHints hints) + internal JsonNode ToJsonNode() + { + var node = new JsonObject { { "mode", Mode.ToString().ToLowerInvariant() } }; + if (W != null) { - Hints = hints; - return this; + node.Add("w", W); } - public Constraint SetCanvasColor(AnyColor? canvasColor) + if (H != null) { - CanvasColor = canvasColor; - return this; + node.Add("h", H); } - public Constraint SetGravity(ConstraintGravity gravity) + if (Hints != null) { - Gravity = gravity; - - return this; + node.Add("hints", Hints.ToJsonNode()); } - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() + if (CanvasColor != null) { - return new - { - mode = Mode.ToString().ToLowerInvariant(), - w = W, - h = H, - hints = Hints?.ToImageflowDynamic(), - canvas_color = CanvasColor?.ToImageflowDynamic(), - gravity = Gravity?.ToImageflowDynamic() - }; + node.Add("canvas_color", CanvasColor?.ToJsonNode()); } - internal JsonNode ToJsonNode() + if (Gravity != null) { - var node = new JsonObject { { "mode", Mode.ToString().ToLowerInvariant() } }; - if (W != null) node.Add("w", W); - if (H != null) node.Add("h", H); - if (Hints != null) node.Add("hints", Hints.ToJsonNode()); - if (CanvasColor != null) node.Add("canvas_color", CanvasColor?.ToJsonNode()); - if (Gravity != null) node.Add("gravity", Gravity.ToJsonNode()); - return node; + node.Add("gravity", Gravity.ToJsonNode()); } + + return node; } } diff --git a/src/Imageflow/Fluent/ConstraintGravity.cs b/src/Imageflow/Fluent/ConstraintGravity.cs index d6d1eb8..58d9a69 100644 --- a/src/Imageflow/Fluent/ConstraintGravity.cs +++ b/src/Imageflow/Fluent/ConstraintGravity.cs @@ -1,55 +1,53 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public class ConstraintGravity { - public class ConstraintGravity + /// + /// Centers the gravity (the default) + /// + public ConstraintGravity() { - /// - /// Centers the gravity (the default) - /// - public ConstraintGravity() - { - XPercent = 50; - YPercent = 50; - } - /// - /// Aligns the watermark so xPercent of free space is on the left and yPercent of free space is on the top - /// - /// - /// - public ConstraintGravity(float xPercent, float yPercent) - { - XPercent = xPercent; - YPercent = yPercent; - } + XPercent = 50; + YPercent = 50; + } + /// + /// Aligns the watermark so xPercent of free space is on the left and yPercent of free space is on the top + /// + /// + /// + public ConstraintGravity(float xPercent, float yPercent) + { + XPercent = xPercent; + YPercent = yPercent; + } - public float XPercent { get; } - public float YPercent { get; } + public float XPercent { get; } + public float YPercent { get; } - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() + { + return new { - return new + percentage = new { - percentage = new - { - x = XPercent, - y = YPercent - } + x = XPercent, + y = YPercent + } - }; - } + }; + } - public JsonNode ToJsonNode() + public JsonNode ToJsonNode() + { + var obj = new JsonObject + { { "percentage", new JsonObject() { - var obj = new JsonObject - { { "percentage", new JsonObject() - { - {"x", XPercent}, - {"y", YPercent} - } } }; - return obj; - } + {"x", XPercent}, + {"y", YPercent} + } } }; + return obj; } - } diff --git a/src/Imageflow/Fluent/DecodeCommands.cs b/src/Imageflow/Fluent/DecodeCommands.cs index df102b5..291fae4 100644 --- a/src/Imageflow/Fluent/DecodeCommands.cs +++ b/src/Imageflow/Fluent/DecodeCommands.cs @@ -1,126 +1,122 @@ using System.Drawing; using System.Text.Json.Nodes; -namespace Imageflow.Fluent -{ - +namespace Imageflow.Fluent; - public class DecodeCommands - { - public Size? JpegDownscaleHint { get; set; } +public class DecodeCommands +{ + public Size? JpegDownscaleHint { get; set; } - public DecoderDownscalingMode JpegDownscalingMode { get; set; } = DecoderDownscalingMode.Unspecified; + public DecoderDownscalingMode JpegDownscalingMode { get; set; } = DecoderDownscalingMode.Unspecified; - [Obsolete("Use WebPDownscaleHint instead")] - public Size? WebpDownscaleHint { get => WebPDownscaleHint; set => WebPDownscaleHint = value; } + [Obsolete("Use WebPDownscaleHint instead")] + public Size? WebpDownscaleHint { get => WebPDownscaleHint; set => WebPDownscaleHint = value; } - public Size? WebPDownscaleHint { get; set; } + public Size? WebPDownscaleHint { get; set; } - public bool DiscardColorProfile { get; set; } + public bool DiscardColorProfile { get; set; } - public bool IgnoreColorProfileErrors { get; set; } + public bool IgnoreColorProfileErrors { get; set; } - public DecodeCommands SetJpegDownscaling(int targetWidthHint, - int targetHeightHint, DecoderDownscalingMode mode) - { - JpegDownscaleHint = new Size(targetWidthHint, targetHeightHint); - JpegDownscalingMode = mode; - return this; - } + public DecodeCommands SetJpegDownscaling(int targetWidthHint, + int targetHeightHint, DecoderDownscalingMode mode) + { + JpegDownscaleHint = new Size(targetWidthHint, targetHeightHint); + JpegDownscalingMode = mode; + return this; + } - public DecodeCommands SetWebPDownscaling(int targetWidthHint, - int targetHeightHint) - { - WebPDownscaleHint = new Size(targetWidthHint, targetHeightHint); - return this; - } + public DecodeCommands SetWebPDownscaling(int targetWidthHint, + int targetHeightHint) + { + WebPDownscaleHint = new Size(targetWidthHint, targetHeightHint); + return this; + } - public DecodeCommands SetDiscardColorProfile(bool value) - { - DiscardColorProfile = value; - return this; - } - public DecodeCommands SetIgnoreColorProfileErrors(bool value) - { - IgnoreColorProfileErrors = value; - return this; - } + public DecodeCommands SetDiscardColorProfile(bool value) + { + DiscardColorProfile = value; + return this; + } + public DecodeCommands SetIgnoreColorProfileErrors(bool value) + { + IgnoreColorProfileErrors = value; + return this; + } - public object[] ToImageflowDynamic() + public object[] ToImageflowDynamic() + { + object? downscale = JpegDownscaleHint.HasValue ? new { - object? downscale = JpegDownscaleHint.HasValue ? new + jpeg_downscale_hints = new { - jpeg_downscale_hints = new - { - width = JpegDownscaleHint.Value.Width, - height = JpegDownscaleHint.Value.Height, - scale_luma_spatially = JpegDownscalingMode == DecoderDownscalingMode.SpatialLumaScaling || JpegDownscalingMode == DecoderDownscalingMode.GammaCorrectSpatialLumaScaling, - gamma_correct_for_srgb_during_spatial_luma_scaling = JpegDownscalingMode == DecoderDownscalingMode.GammaCorrectSpatialLumaScaling - } - } : null; - object? downscaleWebP = WebPDownscaleHint.HasValue - ? new + width = JpegDownscaleHint.Value.Width, + height = JpegDownscaleHint.Value.Height, + scale_luma_spatially = JpegDownscalingMode == DecoderDownscalingMode.SpatialLumaScaling || JpegDownscalingMode == DecoderDownscalingMode.GammaCorrectSpatialLumaScaling, + gamma_correct_for_srgb_during_spatial_luma_scaling = JpegDownscalingMode == DecoderDownscalingMode.GammaCorrectSpatialLumaScaling + } + } : null; + object? downscaleWebP = WebPDownscaleHint.HasValue + ? new + { + webp_decoder_hints = new { - webp_decoder_hints = new - { - width = WebPDownscaleHint.Value.Width, - height = WebPDownscaleHint.Value.Height - } + width = WebPDownscaleHint.Value.Width, + height = WebPDownscaleHint.Value.Height } - : null; - + } + : null; - object? ignore = DiscardColorProfile ? new { discard_color_profile = (string?)null } : null; - object? ignoreErrors = IgnoreColorProfileErrors ? new { ignore_color_profile_errors = (string?)null } : null; - return new[] { downscale, ignore, ignoreErrors, downscaleWebP }.Where(obj => obj != null).Cast().ToArray(); - } + object? ignore = DiscardColorProfile ? new { discard_color_profile = (string?)null } : null; + object? ignoreErrors = IgnoreColorProfileErrors ? new { ignore_color_profile_errors = (string?)null } : null; + return new[] { downscale, ignore, ignoreErrors, downscaleWebP }.Where(obj => obj != null).Cast().ToArray(); + } - public JsonArray ToJsonNode() + public JsonArray ToJsonNode() + { + var node = new JsonArray(); + if (JpegDownscaleHint.HasValue) { - var node = new JsonArray(); - if (JpegDownscaleHint.HasValue) + node.Add((JsonNode)new JsonObject { - node.Add((JsonNode)new JsonObject + {"jpeg_downscale_hints", new JsonObject { - {"jpeg_downscale_hints", new JsonObject - { - {"width", JpegDownscaleHint.Value.Width}, - {"height", JpegDownscaleHint.Value.Height}, - {"scale_luma_spatially", JpegDownscalingMode == DecoderDownscalingMode.SpatialLumaScaling || JpegDownscalingMode == DecoderDownscalingMode.GammaCorrectSpatialLumaScaling}, - {"gamma_correct_for_srgb_during_spatial_luma_scaling", JpegDownscalingMode == DecoderDownscalingMode.GammaCorrectSpatialLumaScaling} - }} - }); - } + {"width", JpegDownscaleHint.Value.Width}, + {"height", JpegDownscaleHint.Value.Height}, + {"scale_luma_spatially", JpegDownscalingMode == DecoderDownscalingMode.SpatialLumaScaling || JpegDownscalingMode == DecoderDownscalingMode.GammaCorrectSpatialLumaScaling}, + {"gamma_correct_for_srgb_during_spatial_luma_scaling", JpegDownscalingMode == DecoderDownscalingMode.GammaCorrectSpatialLumaScaling} + }} + }); + } - if (WebPDownscaleHint.HasValue) + if (WebPDownscaleHint.HasValue) + { + node.Add((JsonNode)new JsonObject { - node.Add((JsonNode)new JsonObject + {"webp_decoder_hints", new JsonObject { - {"webp_decoder_hints", new JsonObject - { - {"width", WebPDownscaleHint.Value.Width}, - {"height", WebPDownscaleHint.Value.Height} - }} - }); - } + {"width", WebPDownscaleHint.Value.Width}, + {"height", WebPDownscaleHint.Value.Height} + }} + }); + } - if (DiscardColorProfile) + if (DiscardColorProfile) + { + node.Add((JsonNode)new JsonObject { - node.Add((JsonNode)new JsonObject - { - {"discard_color_profile", null} - }); - } + {"discard_color_profile", null} + }); + } - if (IgnoreColorProfileErrors) + if (IgnoreColorProfileErrors) + { + node.Add((JsonNode)new JsonObject { - node.Add((JsonNode)new JsonObject - { - {"ignore_color_profile_errors", null} - }); - } - - return node; + {"ignore_color_profile_errors", null} + }); } + + return node; } } diff --git a/src/Imageflow/Fluent/Enumerations.cs b/src/Imageflow/Fluent/Enumerations.cs index b20ba69..0026280 100644 --- a/src/Imageflow/Fluent/Enumerations.cs +++ b/src/Imageflow/Fluent/Enumerations.cs @@ -1,193 +1,188 @@ using Imageflow.Bindings; -namespace Imageflow.Fluent -{ - // ReSharper disable InconsistentNaming +namespace Imageflow.Fluent; +// ReSharper disable InconsistentNaming - public enum PixelFormat - { - Bgra_32 = 4, - Bgr_32 = 70, - // Bgr_24 = 3, - // Gray_8 = 1, - } - internal static class PixelFormatParser +public enum PixelFormat +{ + Bgra_32 = 4, + Bgr_32 = 70, + // Bgr_24 = 3, + // Gray_8 = 1, +} +internal static class PixelFormatParser +{ + public static PixelFormat Parse(string s) { - public static PixelFormat Parse(string s) + return s switch { - return s switch - { - "bgra_32" => PixelFormat.Bgra_32, - "bgr_32" => PixelFormat.Bgr_32, - // "bgr_24" => PixelFormat.Bgr_24, - // "gray_8" => PixelFormat.Gray_8, - _ => throw new ImageflowAssertionFailed($"Unknown pixel format {s}") - }; - } - } - - public enum ResampleWhen - { - Size_Differs, - Size_Differs_Or_Sharpening_Requested, - Always + "bgra_32" => PixelFormat.Bgra_32, + "bgr_32" => PixelFormat.Bgr_32, + // "bgr_24" => PixelFormat.Bgr_24, + // "gray_8" => PixelFormat.Gray_8, + _ => throw new ImageflowAssertionFailed($"Unknown pixel format {s}") + }; } +} - public enum SharpenWhen - { - Downscaling, - Upscaling, - Size_Differs, - Always - } - // ReSharper enable InconsistentNaming - - public enum ScalingFloatspace - { - Srgb, - Linear - } - public enum InterpolationFilter - { - Robidoux_Fast = 1, - Robidoux = 2, - Robidoux_Sharp = 3, - Ginseng = 4, - Ginseng_Sharp = 5, - Lanczos = 6, - Lanczos_Sharp = 7, - Lanczos_2 = 8, - Lanczos_2_Sharp = 9, - Cubic = 11, - Cubic_Sharp = 12, - Catmull_Rom = 13, - Mitchell = 14, - - Cubic_B_Spline = 15, - Hermite = 16, - Jinc = 17, - Triangle = 22, - Linear = 23, - Box = 24, - - Fastest = 27, - - N_Cubic = 29, - N_Cubic_Sharp = 30, - } +public enum ResampleWhen +{ + Size_Differs, + Size_Differs_Or_Sharpening_Requested, + Always +} +public enum SharpenWhen +{ + Downscaling, + Upscaling, + Size_Differs, + Always +} +// ReSharper enable InconsistentNaming - public enum ColorKind - { - Black, - Transparent, - Srgb, - } +public enum ScalingFloatspace +{ + Srgb, + Linear +} +public enum InterpolationFilter +{ + Robidoux_Fast = 1, + Robidoux = 2, + Robidoux_Sharp = 3, + Ginseng = 4, + Ginseng_Sharp = 5, + Lanczos = 6, + Lanczos_Sharp = 7, + Lanczos_2 = 8, + Lanczos_2_Sharp = 9, + Cubic = 11, + Cubic_Sharp = 12, + Catmull_Rom = 13, + Mitchell = 14, + + Cubic_B_Spline = 15, + Hermite = 16, + Jinc = 17, + Triangle = 22, + Linear = 23, + Box = 24, + + Fastest = 27, + + N_Cubic = 29, + N_Cubic_Sharp = 30, +} - public enum PngBitDepth - { - Png_32, - Png_24, - } +public enum ColorKind +{ + Black, + Transparent, + Srgb, +} - public enum CompositingMode - { - Compose, - Overwrite - } +public enum PngBitDepth +{ + Png_32, + Png_24, +} - public enum ColorFilterSrgb - { - Grayscale_Ntsc, - Grayscale_Flat, - Grayscale_Bt709, - Grayscale_Ry, - Sepia, - Invert - } +public enum CompositingMode +{ + Compose, + Overwrite +} - public enum ConstraintMode - { - /// Distort the image to exactly the given dimensions. - /// If only one dimension is specified, behaves like `fit`. - Distort, - /// Ensure the result fits within the provided dimensions. No up-scaling. - Within, - /// Fit the image within the dimensions, up-scaling if needed - Fit, - /// Ensure the image is larger than the given dimensions - Larger_Than, - /// Crop to desired aspect ratio if image is larger than requested, then downscale. Ignores smaller images. - /// If only one dimension is specified, behaves like `within`. - Within_Crop, - /// Crop to desired aspect ratio, then downscale or upscale to fit. - /// If only one dimension is specified, behaves like `fit`. - Fit_Crop, - /// Crop to desired aspect ratio, no up-scaling or downscaling. If only one dimension is specified, behaves like Fit. - Aspect_Crop, - /// Pad to desired aspect ratio if image is larger than requested, then downscale. Ignores smaller images. - /// If only one dimension is specified, behaves like `within` - Within_Pad, - /// Pad to desired aspect ratio, then downscale or upscale to fit - /// If only one dimension is specified, behaves like `fit`. - Fit_Pad, - } +public enum ColorFilterSrgb +{ + Grayscale_Ntsc, + Grayscale_Flat, + Grayscale_Bt709, + Grayscale_Ry, + Sepia, + Invert +} +public enum ConstraintMode +{ + /// Distort the image to exactly the given dimensions. + /// If only one dimension is specified, behaves like `fit`. + Distort, + /// Ensure the result fits within the provided dimensions. No up-scaling. + Within, + /// Fit the image within the dimensions, up-scaling if needed + Fit, + /// Ensure the image is larger than the given dimensions + Larger_Than, + /// Crop to desired aspect ratio if image is larger than requested, then downscale. Ignores smaller images. + /// If only one dimension is specified, behaves like `within`. + Within_Crop, + /// Crop to desired aspect ratio, then downscale or upscale to fit. + /// If only one dimension is specified, behaves like `fit`. + Fit_Crop, + /// Crop to desired aspect ratio, no up-scaling or downscaling. If only one dimension is specified, behaves like Fit. + Aspect_Crop, + /// Pad to desired aspect ratio if image is larger than requested, then downscale. Ignores smaller images. + /// If only one dimension is specified, behaves like `within` + Within_Pad, + /// Pad to desired aspect ratio, then downscale or upscale to fit + /// If only one dimension is specified, behaves like `fit`. + Fit_Pad, +} - public enum WatermarkConstraintMode - { - /// Distort the image to exactly the given dimensions. - /// If only one dimension is specified, behaves like `fit`. - Distort, - /// Ensure the result fits within the provided dimensions. No up-scaling. - Within, - /// Fit the image within the dimensions, up-scaling if needed - Fit, - /// Crop to desired aspect ratio if image is larger than requested, then downscale. Ignores smaller images. - /// If only one dimension is specified, behaves like `within`. - Within_Crop, - /// Crop to desired aspect ratio, then downscale or upscale to fit. - /// If only one dimension is specified, behaves like `fit`. - Fit_Crop, - } +public enum WatermarkConstraintMode +{ + /// Distort the image to exactly the given dimensions. + /// If only one dimension is specified, behaves like `fit`. + Distort, + /// Ensure the result fits within the provided dimensions. No up-scaling. + Within, + /// Fit the image within the dimensions, up-scaling if needed + Fit, + /// Crop to desired aspect ratio if image is larger than requested, then downscale. Ignores smaller images. + /// If only one dimension is specified, behaves like `within`. + Within_Crop, + /// Crop to desired aspect ratio, then downscale or upscale to fit. + /// If only one dimension is specified, behaves like `fit`. + Fit_Crop, +} - public enum WatermarkAlign - { - /// - /// Aligns the watermark within the canvas box. This is only used when using a watermark in combination with a command string. Otherwise it behaves like ToImage. - /// - Canvas, - /// - /// Aligns the watermark within the image box - /// - Image - } +public enum WatermarkAlign +{ + /// + /// Aligns the watermark within the canvas box. This is only used when using a watermark in combination with a command string. Otherwise it behaves like ToImage. + /// + Canvas, + /// + /// Aligns the watermark within the image box + /// + Image +} +/// +/// What quality level to use when downscaling the jpeg block-wise +/// +public enum DecoderDownscalingMode +{ /// - /// What quality level to use when downscaling the jpeg block-wise + /// Use the Imageflow default (usually highest quality) /// - public enum DecoderDownscalingMode - { - /// - /// Use the Imageflow default (usually highest quality) - /// - Unspecified = 0, - /// - /// Use the fastest method - /// - Fastest = 1, - - /// - /// A slower (but more accurate) scaling method is employed; the DCT blocks are fully decoded, then a true re-sampling kernel is applied. - /// - SpatialLumaScaling = 2, - /// - /// Like SpatialLumaScaling, but gamma correction is applied before the re-sampling kernel, then removed afterwards. - /// Has the effect of linear-light scaling - /// - GammaCorrectSpatialLumaScaling = 6, - Best = 6, - } + Unspecified = 0, + /// + /// Use the fastest method + /// + Fastest = 1, + /// + /// A slower (but more accurate) scaling method is employed; the DCT blocks are fully decoded, then a true re-sampling kernel is applied. + /// + SpatialLumaScaling = 2, + /// + /// Like SpatialLumaScaling, but gamma correction is applied before the re-sampling kernel, then removed afterwards. + /// Has the effect of linear-light scaling + /// + GammaCorrectSpatialLumaScaling = 6, + Best = 6, } diff --git a/src/Imageflow/Fluent/FinishJobBuilder.cs b/src/Imageflow/Fluent/FinishJobBuilder.cs index 37ad923..c1c63b2 100644 --- a/src/Imageflow/Fluent/FinishJobBuilder.cs +++ b/src/Imageflow/Fluent/FinishJobBuilder.cs @@ -1,105 +1,102 @@ -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +/// +/// Allows job execution in a fluent way +/// +public class FinishJobBuilder { - /// - /// Allows job execution in a fluent way - /// - public class FinishJobBuilder - { - private readonly ImageJob _builder; - private CancellationToken _token; - private CancellationTokenSource? _tokenSource; - private SecurityOptions? _security; + private readonly ImageJob _builder; + private CancellationToken _token; + private CancellationTokenSource? _tokenSource; + private SecurityOptions? _security; - internal FinishJobBuilder(ImageJob imageJob, CancellationToken cancellationToken) - { - _builder = imageJob; - _token = cancellationToken; - } + internal FinishJobBuilder(ImageJob imageJob, CancellationToken cancellationToken) + { + _builder = imageJob; + _token = cancellationToken; + } - public FinishJobBuilder WithSecurityOptions(SecurityOptions securityOptions) - { - _security = securityOptions; - return this; - } - public FinishJobBuilder SetSecurityOptions(SecurityOptions securityOptions) - { - _security = securityOptions; - return this; - } + public FinishJobBuilder WithSecurityOptions(SecurityOptions securityOptions) + { + _security = securityOptions; + return this; + } + public FinishJobBuilder SetSecurityOptions(SecurityOptions securityOptions) + { + _security = securityOptions; + return this; + } - /// - /// Replaces the cancellation token - /// - /// - /// - public FinishJobBuilder WithCancellationToken(CancellationToken token) - { - _token = token; - return this; - } - /// - /// Replaces the cancellation token - /// - /// - /// - public FinishJobBuilder SetCancellationToken(CancellationToken token) - { - _token = token; - return this; - } + /// + /// Replaces the cancellation token + /// + /// + /// + public FinishJobBuilder WithCancellationToken(CancellationToken token) + { + _token = token; + return this; + } + /// + /// Replaces the cancellation token + /// + /// + /// + public FinishJobBuilder SetCancellationToken(CancellationToken token) + { + _token = token; + return this; + } - /// - /// Replaces the CancellationToken with a timeout - /// - /// - /// - public FinishJobBuilder WithCancellationTimeout(int milliseconds) - { - _tokenSource = new CancellationTokenSource(milliseconds); - return WithCancellationToken(_tokenSource.Token); - } - /// - /// Replaces the CancellationToken with a timeout - /// - /// - /// - public FinishJobBuilder SetCancellationTimeout(int milliseconds) - { - _tokenSource = new CancellationTokenSource(milliseconds); - return WithCancellationToken(_tokenSource.Token); - } + /// + /// Replaces the CancellationToken with a timeout + /// + /// + /// + public FinishJobBuilder WithCancellationTimeout(int milliseconds) + { + _tokenSource = new CancellationTokenSource(milliseconds); + return WithCancellationToken(_tokenSource.Token); + } + /// + /// Replaces the CancellationToken with a timeout + /// + /// + /// + public FinishJobBuilder SetCancellationTimeout(int milliseconds) + { + _tokenSource = new CancellationTokenSource(milliseconds); + return WithCancellationToken(_tokenSource.Token); + } - public Task InProcessAsync() => _builder.FinishAsync(new JobExecutionOptions(), _security, _token); + public Task InProcessAsync() => _builder.FinishAsync(new JobExecutionOptions(), _security, _token); - public Task InSubprocessAsync(string? imageflowToolPath = null, long? outputBufferCapacity = null) => - _builder.FinishInSubprocessAsync(_security, imageflowToolPath, outputBufferCapacity, _token); + public Task InSubprocessAsync(string? imageflowToolPath = null, long? outputBufferCapacity = null) => + _builder.FinishInSubprocessAsync(_security, imageflowToolPath, outputBufferCapacity, _token); - /// - /// Returns a prepared job that can be executed with `imageflow_tool --json [job.JsonPath]`. Supporting input/output files are also created. - /// If deleteFilesOnDispose is true, then the files will be deleted when the job is disposed. - /// - /// - public Task WriteJsonJobAndInputs(bool deleteFilesOnDispose) => - _builder.WriteJsonJobAndInputs(_token, _security, deleteFilesOnDispose); + /// + /// Returns a prepared job that can be executed with `imageflow_tool --json [job.JsonPath]`. Supporting input/output files are also created. + /// If deleteFilesOnDispose is true, then the files will be deleted when the job is disposed. + /// + /// + public Task WriteJsonJobAndInputs(bool deleteFilesOnDispose) => + _builder.WriteJsonJobAndInputs(_token, _security, deleteFilesOnDispose); - internal string ToJsonDebug(SecurityOptions? securityOptions = null) => _builder.ToJsonDebug(securityOptions); + internal string ToJsonDebug(SecurityOptions? securityOptions = null) => _builder.ToJsonDebug(securityOptions); - public async Task InProcessAndDisposeAsync() + public async Task InProcessAndDisposeAsync() + { + BuildJobResult r; + try { - BuildJobResult r; - try - { - r = await InProcessAsync(); - } - finally - { - _builder.Dispose(); - } - - return r; + r = await InProcessAsync(); + } + finally + { + _builder.Dispose(); } + return r; } - } diff --git a/src/Imageflow/Fluent/FrameSizeLimit.cs b/src/Imageflow/Fluent/FrameSizeLimit.cs index 911e99c..db5afc4 100644 --- a/src/Imageflow/Fluent/FrameSizeLimit.cs +++ b/src/Imageflow/Fluent/FrameSizeLimit.cs @@ -1,33 +1,32 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public readonly struct FrameSizeLimit(uint maxWidth, uint maxHeight, float maxMegapixels) { - public readonly struct FrameSizeLimit(uint maxWidth, uint maxHeight, float maxMegapixels) - { - public uint MaxWidth { get; } = maxWidth; - public uint MaxHeight { get; } = maxHeight; - public float MaxMegapixels { get; } = maxMegapixels; + public uint MaxWidth { get; } = maxWidth; + public uint MaxHeight { get; } = maxHeight; + public float MaxMegapixels { get; } = maxMegapixels; - internal object ToImageflowDynamic() + internal object ToImageflowDynamic() + { + return new { - return new - { - w = MaxWidth, - h = MaxHeight, - megapixels = MaxMegapixels - }; - } + w = MaxWidth, + h = MaxHeight, + megapixels = MaxMegapixels + }; + } - internal JsonNode ToJsonNode() - { - var node = new JsonObject - { - { "w", MaxWidth }, - { "h", MaxHeight }, - { "megapixels", MaxMegapixels } - }; - return node; + internal JsonNode ToJsonNode() + { + var node = new JsonObject + { + { "w", MaxWidth }, + { "h", MaxHeight }, + { "megapixels", MaxMegapixels } + }; + return node; - } } } diff --git a/src/Imageflow/Fluent/IBytesSource.cs b/src/Imageflow/Fluent/IBytesSource.cs index 923a08e..deb6f8e 100644 --- a/src/Imageflow/Fluent/IBytesSource.cs +++ b/src/Imageflow/Fluent/IBytesSource.cs @@ -1,59 +1,57 @@ -namespace Imageflow.Fluent -{ - [Obsolete("Use IMemorySource instead")] - public interface IBytesSource : IDisposable - { - /// - /// Return a reference to a byte array that (until the implementor is disposed) will (a) remain immutable, and (b) can be GC pinned. - /// - /// - Task> GetBytesAsync(CancellationToken cancellationToken); - } +namespace Imageflow.Fluent; +[Obsolete("Use IMemorySource instead")] +public interface IBytesSource : IDisposable +{ /// - /// Represents a source backed by an ArraySegment or byte[] array. Use MemorySource for ReadOnlyMemory<byte> backed memory instead. + /// Return a reference to a byte array that (until the implementor is disposed) will (a) remain immutable, and (b) can be GC pinned. /// - [Obsolete("Use MemorySource.Borrow(bytes, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed) instead")] - public readonly struct BytesSource : IBytesSource - { - public BytesSource(byte[] bytes) - { - _bytes = new ArraySegment(bytes, 0, bytes.Length); - } - public BytesSource(byte[] bytes, int offset, int length) - { - _bytes = new ArraySegment(bytes, offset, length); - } - public BytesSource(ArraySegment bytes) - { - _bytes = bytes; - } + /// + Task> GetBytesAsync(CancellationToken cancellationToken); +} - private readonly ArraySegment _bytes; +/// +/// Represents a source backed by an ArraySegment or byte[] array. Use MemorySource for ReadOnlyMemory<byte> backed memory instead. +/// +[Obsolete("Use MemorySource.Borrow(bytes, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed) instead")] +public readonly struct BytesSource : IBytesSource +{ + public BytesSource(byte[] bytes) + { + _bytes = new ArraySegment(bytes, 0, bytes.Length); + } + public BytesSource(byte[] bytes, int offset, int length) + { + _bytes = new ArraySegment(bytes, offset, length); + } + public BytesSource(ArraySegment bytes) + { + _bytes = bytes; + } - public void Dispose() - { - } + private readonly ArraySegment _bytes; - public Task> GetBytesAsync(CancellationToken cancellationToken) - { - return Task.FromResult(_bytes); - } + public void Dispose() + { + } - public static implicit operator MemorySource(BytesSource source) - { - return new MemorySource(source._bytes); - } + public Task> GetBytesAsync(CancellationToken cancellationToken) + { + return Task.FromResult(_bytes); } - public static class BytesSourceExtensions + public static implicit operator MemorySource(BytesSource source) { + return new MemorySource(source._bytes); + } +} + +public static class BytesSourceExtensions +{ #pragma warning disable CS0618 // Type or member is obsolete - public static IAsyncMemorySource ToMemorySource(this IBytesSource source) + public static IAsyncMemorySource ToMemorySource(this IBytesSource source) #pragma warning restore CS0618 // Type or member is obsolete - { - return new BytesSourceAdapter(source); - } + { + return new BytesSourceAdapter(source); } - } diff --git a/src/Imageflow/Fluent/IEncoderPreset.cs b/src/Imageflow/Fluent/IEncoderPreset.cs index c9d15d3..25f6f80 100644 --- a/src/Imageflow/Fluent/IEncoderPreset.cs +++ b/src/Imageflow/Fluent/IEncoderPreset.cs @@ -1,261 +1,317 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +/// +/// An interface for encode presets. Concrete examples are GifEncoder, PngQuantEncoder, LodePngEncoder, MozJpegEncoder, WebPLossyEncoder, WebPLosslessEncoder +/// +public interface IEncoderPreset { + object ToImageflowDynamic(); + + JsonNode ToJsonNode(); +} + +/// +/// Encodes the image as a .gif +/// +public class GifEncoder : IEncoderPreset +{ + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() => new { gif = (string?)null }; + + public JsonNode ToJsonNode() => new JsonObject() { { "gif", (string?)null } }; +} +/// +/// Use LodePngEncoder instead +/// +[Obsolete("Use PngQuantEncoder or LodePngEncoder instead")] +public class LibPngEncoder : IEncoderPreset +{ + public AnyColor? Matte { get; set; } + public int? ZlibCompression { get; set; } + public PngBitDepth? BitDepth { get; set; } + + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() => new { libpng = new { depth = BitDepth?.ToString().ToLowerInvariant(), zlib_compression = ZlibCompression, matte = Matte?.ToImageflowDynamic() } }; + + public JsonNode ToJsonNode() + { + var node = new JsonObject(); + if (BitDepth != null) + { + node.Add("depth", BitDepth?.ToString().ToLowerInvariant()); + } + + if (ZlibCompression != null) + { + node.Add("zlib_compression", ZlibCompression); + } + + if (Matte != null) + { + node.Add("matte", Matte?.ToJsonNode()); + } + + return new JsonObject() { { "libpng", node } }; + } +} + +public class PngQuantEncoder : IEncoderPreset +{ + public PngQuantEncoder() : this(null, null) { } /// - /// An interface for encode presets. Concrete examples are GifEncoder, PngQuantEncoder, LodePngEncoder, MozJpegEncoder, WebPLossyEncoder, WebPLosslessEncoder + /// Try to quantize the PNG first, falling back to lossless PNG if the minimumQuality value cannot be reached /// - public interface IEncoderPreset + /// The target visual quality + /// The minimum visual quality below which to revert to lossless encoding + public PngQuantEncoder(int? quality, int? minimumQuality) { - object ToImageflowDynamic(); - - JsonNode ToJsonNode(); + Quality = quality; + MinimumQuality = minimumQuality; } + /// + /// (0..100) The target visual quality. Try to quantize the PNG first, falling back to lossless PNG if the MinimumQuality value cannot be reached + /// + public int? Quality { get; set; } + /// + /// (0..100) The minimum visual quality below which to revert to lossless encoding + /// + public int? MinimumQuality { get; set; } /// - /// Encodes the image as a .gif + /// speed: 1..10 controls the speed/quality trade-off for encoding. /// - public class GifEncoder : IEncoderPreset - { - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new { gif = (string?)null }; + public int? Speed { get; set; } + /// + /// When true, uses drastically more CPU time for a 1-2% reduction in file size + /// + public bool? MaximumDeflate { get; set; } - public JsonNode ToJsonNode() => new JsonObject() { { "gif", (string?)null } }; + /// + /// (0..100) The target visual quality. Try to quantize the PNG first, falling back to lossless PNG if the MinimumQuality value cannot be reached + /// + /// + /// + public PngQuantEncoder SetQuality(int? quality) + { + Quality = quality; + return this; } /// - /// Use LodePngEncoder instead + /// (0..100) The minimum visual quality below which to revert to lossless encoding /// - [Obsolete("Use PngQuantEncoder or LodePngEncoder instead")] - public class LibPngEncoder : IEncoderPreset + /// + /// + public PngQuantEncoder SetMinimumQuality(int? minimumQuality) { - public AnyColor? Matte { get; set; } - public int? ZlibCompression { get; set; } - public PngBitDepth? BitDepth { get; set; } - - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new { libpng = new { depth = BitDepth?.ToString().ToLowerInvariant(), zlib_compression = ZlibCompression, matte = Matte?.ToImageflowDynamic() } }; + MinimumQuality = minimumQuality; + return this; + } - public JsonNode ToJsonNode() - { - var node = new JsonObject(); - if (BitDepth != null) node.Add("depth", BitDepth?.ToString().ToLowerInvariant()); - if (ZlibCompression != null) node.Add("zlib_compression", ZlibCompression); - if (Matte != null) node.Add("matte", Matte?.ToJsonNode()); - return new JsonObject() { { "libpng", node } }; - } + /// + /// speed: 1..10 controls the speed/quality trade-off for encoding. + /// + /// + /// + public PngQuantEncoder SetSpeed(int? speed) + { + Speed = speed; + return this; } - public class PngQuantEncoder : IEncoderPreset + /// + /// Not suggested; only saves 1-2% on file size but takes 10x CPU time. + /// + /// + /// + public PngQuantEncoder SetMaximumDeflate(bool value) { - public PngQuantEncoder() : this(null, null) { } - /// - /// Try to quantize the PNG first, falling back to lossless PNG if the minimumQuality value cannot be reached - /// - /// The target visual quality - /// The minimum visual quality below which to revert to lossless encoding - public PngQuantEncoder(int? quality, int? minimumQuality) - { - Quality = quality; - MinimumQuality = minimumQuality; - } - /// - /// (0..100) The target visual quality. Try to quantize the PNG first, falling back to lossless PNG if the MinimumQuality value cannot be reached - /// - public int? Quality { get; set; } - - - /// - /// (0..100) The minimum visual quality below which to revert to lossless encoding - /// - public int? MinimumQuality { get; set; } - - - /// - /// speed: 1..10 controls the speed/quality trade-off for encoding. - /// - public int? Speed { get; set; } - /// - /// When true, uses drastically more CPU time for a 1-2% reduction in file size - /// - public bool? MaximumDeflate { get; set; } - - - /// - /// (0..100) The target visual quality. Try to quantize the PNG first, falling back to lossless PNG if the MinimumQuality value cannot be reached - /// - /// - /// - public PngQuantEncoder SetQuality(int? quality) - { - Quality = quality; - return this; - } - /// - /// (0..100) The minimum visual quality below which to revert to lossless encoding - /// - /// - /// - public PngQuantEncoder SetMinimumQuality(int? minimumQuality) + MaximumDeflate = value; + return this; + } + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() => new + { + pngquant = new { - MinimumQuality = minimumQuality; - return this; + quality = Quality, + minimum_quality = MinimumQuality, + speed = Speed, + maximum_deflate = MaximumDeflate } + }; - /// - /// speed: 1..10 controls the speed/quality trade-off for encoding. - /// - /// - /// - public PngQuantEncoder SetSpeed(int? speed) + public JsonNode ToJsonNode() + { + var node = new JsonObject(); + if (Quality != null) { - Speed = speed; - return this; + node.Add("quality", Quality); } - /// - /// Not suggested; only saves 1-2% on file size but takes 10x CPU time. - /// - /// - /// - public PngQuantEncoder SetMaximumDeflate(bool value) - { - MaximumDeflate = value; - return this; - } - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new - { - pngquant = new - { - quality = Quality, - minimum_quality = MinimumQuality, - speed = Speed, - maximum_deflate = MaximumDeflate - } - }; - - public JsonNode ToJsonNode() + if (MinimumQuality != null) { - var node = new JsonObject(); - if (Quality != null) node.Add("quality", Quality); - if (MinimumQuality != null) node.Add("minimum_quality", MinimumQuality); - if (Speed != null) node.Add("speed", Speed); - if (MaximumDeflate != null) node.Add("maximum_deflate", MaximumDeflate); - return new JsonObject() { { "pngquant", node } }; + node.Add("minimum_quality", MinimumQuality); } - } - - public class LodePngEncoder : IEncoderPreset - { - /// - /// When true, uses drastically more CPU time for a 1-2% reduction in file size - /// - public bool? MaximumDeflate { get; set; } - - /// - /// Not suggested; only saves 1-2% on file size but takes 10x CPU time. - /// - /// - /// - public LodePngEncoder SetMaximumDeflate(bool value) + if (Speed != null) { - MaximumDeflate = value; - return this; + node.Add("speed", Speed); } - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new { lodepng = new { maximum_deflate = MaximumDeflate } }; - public JsonNode ToJsonNode() + if (MaximumDeflate != null) { - var node = new JsonObject(); - if (MaximumDeflate != null) node.Add("maximum_deflate", MaximumDeflate); - return new JsonObject() { { "lodepng", node } }; + node.Add("maximum_deflate", MaximumDeflate); } + + return new JsonObject() { { "pngquant", node } }; } +} +public class LodePngEncoder : IEncoderPreset +{ + /// + /// When true, uses drastically more CPU time for a 1-2% reduction in file size + /// + public bool? MaximumDeflate { get; set; } /// - /// Deprecated. Use MozJpegEncoder instead + /// Not suggested; only saves 1-2% on file size but takes 10x CPU time. /// - [Obsolete("Use MozJpegEncoder instead for smaller files")] - public class LibJpegTurboEncoder : IEncoderPreset + /// + /// + public LodePngEncoder SetMaximumDeflate(bool value) { - public int? Quality { get; set; } - public bool? Progressive { get; set; } - public bool? OptimizeHuffmanCoding { get; set; } - - public AnyColor? Matte { get; set; } - - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new { libjpegturbo = new { quality = Quality, progressive = Progressive, optimize_huffman_coding = OptimizeHuffmanCoding, matte = Matte?.ToImageflowDynamic() } }; + MaximumDeflate = value; + return this; + } + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() => new { lodepng = new { maximum_deflate = MaximumDeflate } }; - public JsonNode ToJsonNode() + public JsonNode ToJsonNode() + { + var node = new JsonObject(); + if (MaximumDeflate != null) { - var node = new JsonObject(); - if (Quality != null) node.Add("quality", Quality); - if (Progressive != null) node.Add("progressive", Progressive); - if (OptimizeHuffmanCoding != null) node.Add("optimize_huffman_coding", OptimizeHuffmanCoding); - if (Matte != null) node.Add("matte", Matte?.ToJsonNode()); - return new JsonObject() { { "libjpegturbo", node } }; + node.Add("maximum_deflate", MaximumDeflate); } + + return new JsonObject() { { "lodepng", node } }; } +} + +/// +/// Deprecated. Use MozJpegEncoder instead +/// +[Obsolete("Use MozJpegEncoder instead for smaller files")] +public class LibJpegTurboEncoder : IEncoderPreset +{ + public int? Quality { get; set; } + public bool? Progressive { get; set; } + public bool? OptimizeHuffmanCoding { get; set; } + + public AnyColor? Matte { get; set; } + + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() => new { libjpegturbo = new { quality = Quality, progressive = Progressive, optimize_huffman_coding = OptimizeHuffmanCoding, matte = Matte?.ToImageflowDynamic() } }; - public class MozJpegEncoder : IEncoderPreset + public JsonNode ToJsonNode() { - public MozJpegEncoder(int quality) + var node = new JsonObject(); + if (Quality != null) { - Quality = quality; + node.Add("quality", Quality); } - public MozJpegEncoder(int quality, bool progressive) + + if (Progressive != null) { - Quality = quality; - Progressive = progressive; + node.Add("progressive", Progressive); } - public int? Quality { get; set; } - public bool? Progressive { get; set; } - public AnyColor? Matte { get; set; } - public MozJpegEncoder SetProgressive(bool progressive) + if (OptimizeHuffmanCoding != null) { - Progressive = progressive; - return this; + node.Add("optimize_huffman_coding", OptimizeHuffmanCoding); } - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new { mozjpeg = new { quality = Quality, progressive = Progressive, matte = Matte?.ToImageflowDynamic() } }; - - public JsonNode ToJsonNode() + if (Matte != null) { - var node = new JsonObject(); - if (Quality != null) node.Add("quality", Quality); - if (Progressive != null) node.Add("progressive", Progressive); - if (Matte != null) node.Add("matte", Matte?.ToJsonNode()); - return new JsonObject() { { "mozjpeg", node } }; + node.Add("matte", Matte?.ToJsonNode()); } + + return new JsonObject() { { "libjpegturbo", node } }; } +} +public class MozJpegEncoder : IEncoderPreset +{ + public MozJpegEncoder(int quality) + { + Quality = quality; + } + public MozJpegEncoder(int quality, bool progressive) + { + Quality = quality; + Progressive = progressive; + } + public int? Quality { get; set; } + public bool? Progressive { get; set; } - public class WebPLossyEncoder(float quality) : IEncoderPreset + public AnyColor? Matte { get; set; } + public MozJpegEncoder SetProgressive(bool progressive) { - public float? Quality { get; set; } = quality; + Progressive = progressive; + return this; + } - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new { webplossy = new { quality = Quality } }; + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() => new { mozjpeg = new { quality = Quality, progressive = Progressive, matte = Matte?.ToImageflowDynamic() } }; - public JsonNode ToJsonNode() + public JsonNode ToJsonNode() + { + var node = new JsonObject(); + if (Quality != null) + { + node.Add("quality", Quality); + } + + if (Progressive != null) { - var node = new JsonObject(); - if (Quality != null) node.Add("quality", Quality); - return new JsonObject() { { "webplossy", node } }; + node.Add("progressive", Progressive); } + + if (Matte != null) + { + node.Add("matte", Matte?.ToJsonNode()); + } + + return new JsonObject() { { "mozjpeg", node } }; } - public class WebPLosslessEncoder : IEncoderPreset +} + +public class WebPLossyEncoder(float quality) : IEncoderPreset +{ + public float? Quality { get; set; } = quality; + + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() => new { webplossy = new { quality = Quality } }; + + public JsonNode ToJsonNode() { - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() => new { webplossless = (string?)null }; + var node = new JsonObject(); + if (Quality != null) + { + node.Add("quality", Quality); + } - public JsonNode ToJsonNode() => new JsonObject() { { "webplossless", (string?)null } }; + return new JsonObject() { { "webplossy", node } }; } } +public class WebPLosslessEncoder : IEncoderPreset +{ + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() => new { webplossless = (string?)null }; + + public JsonNode ToJsonNode() => new JsonObject() { { "webplossless", (string?)null } }; +} diff --git a/src/Imageflow/Fluent/IOutputDestination.cs b/src/Imageflow/Fluent/IOutputDestination.cs index 13a4636..a4bdbd4 100644 --- a/src/Imageflow/Fluent/IOutputDestination.cs +++ b/src/Imageflow/Fluent/IOutputDestination.cs @@ -1,13 +1,10 @@ -namespace Imageflow.Fluent -{ - public interface IOutputDestination : IDisposable - { - Task RequestCapacityAsync(int bytes); - Task WriteAsync(ArraySegment bytes, CancellationToken cancellationToken); - Task FlushAsync(CancellationToken cancellationToken); - } - - +namespace Imageflow.Fluent; - // ReSharper disable once InconsistentNaming +public interface IOutputDestination : IDisposable +{ + Task RequestCapacityAsync(int bytes); + Task WriteAsync(ArraySegment bytes, CancellationToken cancellationToken); + Task FlushAsync(CancellationToken cancellationToken); } + +// ReSharper disable once InconsistentNaming diff --git a/src/Imageflow/Fluent/IOutputDestinationExtensions.cs b/src/Imageflow/Fluent/IOutputDestinationExtensions.cs index cc8d6b6..37d64fd 100644 --- a/src/Imageflow/Fluent/IOutputDestinationExtensions.cs +++ b/src/Imageflow/Fluent/IOutputDestinationExtensions.cs @@ -35,7 +35,6 @@ internal static async ValueTask AdaptiveWriteAllAsync(this IOutputDestination de await dest.FlushAsync(cancellationToken); } - internal static async ValueTask AdaptedWriteAsync(this IOutputDestination dest, ReadOnlyMemory data, CancellationToken cancellationToken) { if (MemoryMarshal.TryGetArray(data, out ArraySegment segment)) @@ -79,7 +78,6 @@ internal static void AdaptedWrite(this IOutputDestination dest, ReadOnlySpan OutputFiles { get; } - } + string JsonPath { get; } + IReadOnlyDictionary OutputFiles { get; } } diff --git a/src/Imageflow/Fluent/IWatermarkConstraintBox.cs b/src/Imageflow/Fluent/IWatermarkConstraintBox.cs index f56d7b1..5358542 100644 --- a/src/Imageflow/Fluent/IWatermarkConstraintBox.cs +++ b/src/Imageflow/Fluent/IWatermarkConstraintBox.cs @@ -1,11 +1,10 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public interface IWatermarkConstraintBox { - public interface IWatermarkConstraintBox - { - [Obsolete("Use ToJsonNode() instead")] - object ToImageflowDynamic(); - JsonNode ToJsonNode(); - } + [Obsolete("Use ToJsonNode() instead")] + object ToImageflowDynamic(); + JsonNode ToJsonNode(); } diff --git a/src/Imageflow/Fluent/ImageJob.cs b/src/Imageflow/Fluent/ImageJob.cs index 902b7c5..0d40cd0 100644 --- a/src/Imageflow/Fluent/ImageJob.cs +++ b/src/Imageflow/Fluent/ImageJob.cs @@ -8,792 +8,800 @@ using Imageflow.Internal.Helpers; using Imageflow.IO; -namespace Imageflow.Fluent -{ - [Obsolete("Use ImageJob instead")] - public class FluentBuildJob : ImageJob; +namespace Imageflow.Fluent; - public class ImageJob : IDisposable - { - private bool _disposed; - private readonly Dictionary _inputs = new Dictionary(2); - private readonly Dictionary _outputs = new Dictionary(2); +[Obsolete("Use ImageJob instead")] +public class FluentBuildJob : ImageJob; +public class ImageJob : IDisposable +{ + private bool _disposed; + private readonly Dictionary _inputs = new Dictionary(2); + private readonly Dictionary _outputs = new Dictionary(2); - internal void AddInput(int ioId, IAsyncMemorySource source) + internal void AddInput(int ioId, IAsyncMemorySource source) + { + if (_inputs.ContainsKey(ioId) || _outputs.ContainsKey(ioId)) { - if (_inputs.ContainsKey(ioId) || _outputs.ContainsKey(ioId)) - throw new ArgumentException("ioId", $"ioId {ioId} has already been assigned"); - _inputs.Add(ioId, source); + throw new ArgumentException("ioId", $"ioId {ioId} has already been assigned"); } - internal void AddOutput(int ioId, IOutputDestination destination) + + _inputs.Add(ioId, source); + } + internal void AddOutput(int ioId, IOutputDestination destination) + { + if (_inputs.ContainsKey(ioId) || _outputs.ContainsKey(ioId)) { - if (_inputs.ContainsKey(ioId) || _outputs.ContainsKey(ioId)) - throw new ArgumentException("ioId", $"ioId {ioId} has already been assigned"); - _outputs.Add(ioId, destination); + throw new ArgumentException("ioId", $"ioId {ioId} has already been assigned"); } - [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] - public BuildNode Decode(IBytesSource source, DecodeCommands commands) => - Decode(source.ToMemorySource(), GenerateIoId(), commands); - - [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] - public BuildNode Decode(IBytesSource source, int ioId) => Decode(source.ToMemorySource(), ioId, null); + _outputs.Add(ioId, destination); + } - [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] - public BuildNode Decode(IBytesSource source) => Decode(source, GenerateIoId()); + [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] + public BuildNode Decode(IBytesSource source, DecodeCommands commands) => + Decode(source.ToMemorySource(), GenerateIoId(), commands); + [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] + public BuildNode Decode(IBytesSource source, int ioId) => Decode(source.ToMemorySource(), ioId, null); - [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] - public BuildNode Decode(IBytesSource source, int ioId, DecodeCommands? commands) - { - return Decode(source.ToMemorySource(), ioId, commands); - } + [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] + public BuildNode Decode(IBytesSource source) => Decode(source, GenerateIoId()); - public BuildNode Decode(Stream source, bool disposeStream) => Decode(source, disposeStream, GenerateIoId()); + [Obsolete("IBytesSource is obsolete; use a class that implements IMemorySource instead")] + public BuildNode Decode(IBytesSource source, int ioId, DecodeCommands? commands) + { + return Decode(source.ToMemorySource(), ioId, commands); + } - public BuildNode Decode(Stream source, bool disposeStream, int ioId) => - Decode(disposeStream ? BufferedStreamSource.UseEntireStreamAndDisposeWithSource(source) - : BufferedStreamSource.BorrowEntireStream(source), ioId); + public BuildNode Decode(Stream source, bool disposeStream) => Decode(source, disposeStream, GenerateIoId()); + public BuildNode Decode(Stream source, bool disposeStream, int ioId) => + Decode(disposeStream ? BufferedStreamSource.UseEntireStreamAndDisposeWithSource(source) + : BufferedStreamSource.BorrowEntireStream(source), ioId); - [Obsolete("Use Decode(MemorySource.Borrow(arraySegment, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed)) instead")] - public BuildNode Decode(ArraySegment source) => Decode(MemorySource.Borrow(source, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), GenerateIoId()); + [Obsolete("Use Decode(MemorySource.Borrow(arraySegment, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed)) instead")] + public BuildNode Decode(ArraySegment source) => Decode(MemorySource.Borrow(source, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), GenerateIoId()); - [Obsolete("Use Decode(MemorySource.Borrow(arraySegment, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), ioId) instead")] - public BuildNode Decode(ArraySegment source, int ioId) => Decode(MemorySource.Borrow(source, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), ioId); + [Obsolete("Use Decode(MemorySource.Borrow(arraySegment, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), ioId) instead")] + public BuildNode Decode(ArraySegment source, int ioId) => Decode(MemorySource.Borrow(source, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed), ioId); - public BuildNode Decode(byte[] source) => Decode(new MemorySource(source), GenerateIoId()); - public BuildNode Decode(byte[] source, int ioId) => Decode(new MemorySource(source), ioId); + public BuildNode Decode(byte[] source) => Decode(new MemorySource(source), GenerateIoId()); + public BuildNode Decode(byte[] source, int ioId) => Decode(new MemorySource(source), ioId); - public BuildNode Decode(T source) where T : IAsyncMemorySource - { - return Decode(source, GenerateIoId(), null); - } - public BuildNode Decode(T source, DecodeCommands commands) where T : IAsyncMemorySource => - Decode(source, GenerateIoId(), commands); + public BuildNode Decode(T source) where T : IAsyncMemorySource + { + return Decode(source, GenerateIoId(), null); + } + public BuildNode Decode(T source, DecodeCommands commands) where T : IAsyncMemorySource => + Decode(source, GenerateIoId(), commands); - public BuildNode Decode(T source, int ioId) where T : IAsyncMemorySource - { - return Decode(source, ioId, null); - } + public BuildNode Decode(T source, int ioId) where T : IAsyncMemorySource + { + return Decode(source, ioId, null); + } - /// - /// Commands to the decoder, such as JPEG or WebP block-wise downscaling for performance, or to discard the color profile or ignore color profile errors - /// - /// - public BuildNode Decode(T source, int ioId, DecodeCommands? commands) where T : IAsyncMemorySource + /// + /// Commands to the decoder, such as JPEG or WebP block-wise downscaling for performance, or to discard the color profile or ignore color profile errors + /// + /// + public BuildNode Decode(T source, int ioId, DecodeCommands? commands) where T : IAsyncMemorySource + { + AddInput(ioId, source); + if (commands == null) { - AddInput(ioId, source); - if (commands == null) - { - return BuildNode.StartNode(this, - // new - // { - // decode = new - // { - // io_id = ioId - // } - // }); - new JsonObject() { { "decode", new JsonObject() { { "io_id", ioId } } } }); - } return BuildNode.StartNode(this, // new // { // decode = new // { - // io_id = ioId, - // commands = commands.ToImageflowDynamic() + // io_id = ioId // } // }); - new JsonObject() { { "decode", new JsonObject() { { "io_id", ioId }, { "commands", commands.ToJsonNode() } } } }); - } - - - public BuildNode CreateCanvasBgra32(uint w, uint h, AnyColor color) => - CreateCanvas(w, h, color, PixelFormat.Bgra_32); - - public BuildNode CreateCanvasBgr32(uint w, uint h, AnyColor color) => - CreateCanvas(w, h, color, PixelFormat.Bgr_32); - - private BuildNode CreateCanvas(uint w, uint h, AnyColor color, PixelFormat format) => - BuildNode.StartNode(this, - //new {create_canvas = new {w, h, color = color.ToImageflowDynamic() - new JsonObject() {{"create_canvas", new JsonObject() - {{"w", w}, {"h", h}, - {"color", color.ToJsonNode()}}}, - {"format", format.ToString().ToLowerInvariant()}}); - - - - - [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] - public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString) => BuildCommandString(source, null, dest, null, commandString); - [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] - public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, - int? destIoId, string commandString) - => BuildCommandString(source, sourceIoId, dest, destIoId, commandString, null); - - - - [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] - public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString, - ICollection? watermarks) - => BuildCommandString(source, null, dest, null, commandString, watermarks); - - [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] - public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, - int? destIoId, string commandString, ICollection? watermarks) - { - return BuildCommandString(source.ToMemorySource(), sourceIoId, dest, destIoId, commandString, watermarks); + new JsonObject() { { "decode", new JsonObject() { { "io_id", ioId } } } }); } + return BuildNode.StartNode(this, + // new + // { + // decode = new + // { + // io_id = ioId, + // commands = commands.ToImageflowDynamic() + // } + // }); + new JsonObject() { { "decode", new JsonObject() { { "io_id", ioId }, { "commands", commands.ToJsonNode() } } } }); + } - public BuildEndpoint BuildCommandString(byte[] source, IOutputDestination dest, string commandString) => BuildCommandString(new MemorySource(source), dest, commandString); + public BuildNode CreateCanvasBgra32(uint w, uint h, AnyColor color) => + CreateCanvas(w, h, color, PixelFormat.Bgra_32); + + public BuildNode CreateCanvasBgr32(uint w, uint h, AnyColor color) => + CreateCanvas(w, h, color, PixelFormat.Bgr_32); + + private BuildNode CreateCanvas(uint w, uint h, AnyColor color, PixelFormat format) => + BuildNode.StartNode(this, + //new {create_canvas = new {w, h, color = color.ToImageflowDynamic() + new JsonObject() {{"create_canvas", new JsonObject() + {{"w", w}, {"h", h}, + {"color", color.ToJsonNode()}}}, + {"format", format.ToString().ToLowerInvariant()}}); + + [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] + public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString) => BuildCommandString(source, null, dest, null, commandString); + [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] + public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, + int? destIoId, string commandString) + => BuildCommandString(source, sourceIoId, dest, destIoId, commandString, null); + + [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] + public BuildEndpoint BuildCommandString(IBytesSource source, IOutputDestination dest, string commandString, + ICollection? watermarks) + => BuildCommandString(source, null, dest, null, commandString, watermarks); + + [Obsolete("Use a BufferedStreamSource or MemorySource for the source parameter instead")] + public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IOutputDestination dest, + int? destIoId, string commandString, ICollection? watermarks) + { + return BuildCommandString(source.ToMemorySource(), sourceIoId, dest, destIoId, commandString, watermarks); + } - /// - /// Modify the input image (source) with the given command string and watermarks and encode to the (dest) - /// - /// - /// - /// - /// - /// - public BuildEndpoint BuildCommandString(IAsyncMemorySource source, IOutputDestination dest, string commandString) => BuildCommandString(source, null, dest, null, commandString); + public BuildEndpoint BuildCommandString(byte[] source, IOutputDestination dest, string commandString) => BuildCommandString(new MemorySource(source), dest, commandString); - public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIoId, IOutputDestination dest, - int? destIoId, string commandString) - => BuildCommandString(source, sourceIoId, dest, destIoId, commandString, null); + /// + /// Modify the input image (source) with the given command string and watermarks and encode to the (dest) + /// + /// + /// + /// + /// + /// + public BuildEndpoint BuildCommandString(IAsyncMemorySource source, IOutputDestination dest, string commandString) => BuildCommandString(source, null, dest, null, commandString); - public BuildEndpoint BuildCommandString(IAsyncMemorySource source, IOutputDestination dest, string commandString, - ICollection? watermarks) - => BuildCommandString(source, null, dest, null, commandString, watermarks); + public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIoId, IOutputDestination dest, + int? destIoId, string commandString) + => BuildCommandString(source, sourceIoId, dest, destIoId, commandString, null); + public BuildEndpoint BuildCommandString(IAsyncMemorySource source, IOutputDestination dest, string commandString, + ICollection? watermarks) + => BuildCommandString(source, null, dest, null, commandString, watermarks); + public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIoId, IOutputDestination dest, int? destIoId, string commandString, ICollection? watermarks) + { + sourceIoId = sourceIoId ?? GenerateIoId(); + AddInput(sourceIoId.Value, source); + destIoId = destIoId ?? GenerateIoId(); + AddOutput(destIoId.Value, dest); - public BuildEndpoint BuildCommandString(IAsyncMemorySource source, int? sourceIoId, IOutputDestination dest, int? destIoId, string commandString, ICollection? watermarks) + if (watermarks != null) { - sourceIoId = sourceIoId ?? GenerateIoId(); - AddInput(sourceIoId.Value, source); - destIoId = destIoId ?? GenerateIoId(); - AddOutput(destIoId.Value, dest); - - if (watermarks != null) + foreach (var w in watermarks) { - foreach (var w in watermarks) + if (w.IoId == null && w.Source == null) { - if (w.IoId == null && w.Source == null) throw new ArgumentException("InputWatermark instances cannot have both a null IoId and a null Source"); - w.IoId ??= GenerateIoId(); - if (w.Source != null) AddInput(w.IoId.Value, w.Source); + throw new ArgumentException("InputWatermark instances cannot have both a null IoId and a null Source"); } - } - // dynamic nodeData = new - // { - // command_string = new - // { - // kind = "ir4", - // value = commandString, - // decode = sourceIoId, - // encode = destIoId, - // watermarks = watermarks?.Select(w => w.ToImageflowDynamic()).ToArray() - // } - // }; - var watermarkNodes = watermarks?.Select(w => w.ToJsonNode()).ToArray(); - var nodeData = new JsonObject - { - {"command_string", new JsonObject + w.IoId ??= GenerateIoId(); + if (w.Source != null) { - {"kind", "ir4"}, - {"value", commandString}, - {"decode", sourceIoId}, - {"encode", destIoId}, - {"watermarks", watermarkNodes != null ? new JsonArray(watermarkNodes) : null} - }} - }; - return new BuildEndpoint(this, nodeData, null, null); - + AddInput(w.IoId.Value, w.Source); + } + } } - /// - /// Complete the job and set execution options - /// - /// - public FinishJobBuilder Finish() => new FinishJobBuilder(this, default); + // dynamic nodeData = new + // { + // command_string = new + // { + // kind = "ir4", + // value = commandString, + // decode = sourceIoId, + // encode = destIoId, + // watermarks = watermarks?.Select(w => w.ToImageflowDynamic()).ToArray() + // } + // }; + var watermarkNodes = watermarks?.Select(w => w.ToJsonNode()).ToArray(); + var nodeData = new JsonObject + { + {"command_string", new JsonObject + { + {"kind", "ir4"}, + {"value", commandString}, + {"decode", sourceIoId}, + {"encode", destIoId}, + {"watermarks", watermarkNodes != null ? new JsonArray(watermarkNodes) : null} + }} + }; + return new BuildEndpoint(this, nodeData, null, null); - [Obsolete("Use .Finish().InProcessAsync()")] - public Task FinishAsync() => Finish().InProcessAsync(); + } - [Obsolete("Use .Finish().SetCancellationToken(t).InProcessAsync()")] - public Task FinishAsync(CancellationToken cancellationToken) - => Finish().SetCancellationToken(cancellationToken).InProcessAsync(); + /// + /// Complete the job and set execution options + /// + /// + public FinishJobBuilder Finish() => new FinishJobBuilder(this, default); - [Obsolete("Use .Finish().SetCancellationToken(cancellationToken).InSubprocessAsync(imageflowToolPath, outputBufferCapacity)")] - public Task FinishInSubprocessAsync(CancellationToken cancellationToken, - string imageflowToolPath, long? outputBufferCapacity = null) => - Finish().SetCancellationToken(cancellationToken) - .InSubprocessAsync(imageflowToolPath, outputBufferCapacity); + [Obsolete("Use .Finish().InProcessAsync()")] + public Task FinishAsync() => Finish().InProcessAsync(); - [Obsolete("Use .Finish().SetCancellationToken(cancellationToken).WriteJsonJobAndInputs(deleteFilesOnDispose)")] - public Task WriteJsonJobAndInputs(CancellationToken cancellationToken, bool deleteFilesOnDispose) - => Finish().SetCancellationToken(cancellationToken).WriteJsonJobAndInputs(deleteFilesOnDispose); + [Obsolete("Use .Finish().SetCancellationToken(t).InProcessAsync()")] + public Task FinishAsync(CancellationToken cancellationToken) + => Finish().SetCancellationToken(cancellationToken).InProcessAsync(); - [Obsolete("Use .Finish().SetCancellationToken(cancellationToken).InProcessAndDisposeAsync()")] - public Task FinishAndDisposeAsync(CancellationToken cancellationToken) - => Finish().SetCancellationToken(cancellationToken).InProcessAndDisposeAsync(); + [Obsolete("Use .Finish().SetCancellationToken(cancellationToken).InSubprocessAsync(imageflowToolPath, outputBufferCapacity)")] + public Task FinishInSubprocessAsync(CancellationToken cancellationToken, + string imageflowToolPath, long? outputBufferCapacity = null) => + Finish().SetCancellationToken(cancellationToken) + .InSubprocessAsync(imageflowToolPath, outputBufferCapacity); + [Obsolete("Use .Finish().SetCancellationToken(cancellationToken).WriteJsonJobAndInputs(deleteFilesOnDispose)")] + public Task WriteJsonJobAndInputs(CancellationToken cancellationToken, bool deleteFilesOnDispose) + => Finish().SetCancellationToken(cancellationToken).WriteJsonJobAndInputs(deleteFilesOnDispose); - internal JsonObject CreateJsonNodeForFramewiseWithSecurityOptions(SecurityOptions? securityOptions) - { - // var message = new - // { - // security = securityOptions?.ToImageflowDynamic(), - // framewise = ToFramewise() - // }; - return new JsonObject() - { - ["framewise"] = ToFramewise(), - ["security"] = securityOptions?.ToJsonNode() - }; - } + [Obsolete("Use .Finish().SetCancellationToken(cancellationToken).InProcessAndDisposeAsync()")] + public Task FinishAndDisposeAsync(CancellationToken cancellationToken) + => Finish().SetCancellationToken(cancellationToken).InProcessAndDisposeAsync(); - internal string ToJsonDebug(SecurityOptions? securityOptions = default) + internal JsonObject CreateJsonNodeForFramewiseWithSecurityOptions(SecurityOptions? securityOptions) + { + // var message = new + // { + // security = securityOptions?.ToImageflowDynamic(), + // framewise = ToFramewise() + // }; + return new JsonObject() { - return CreateJsonNodeForFramewiseWithSecurityOptions(securityOptions).ToJsonString(); - } + ["framewise"] = ToFramewise(), + ["security"] = securityOptions?.ToJsonNode() + }; + } + + internal string ToJsonDebug(SecurityOptions? securityOptions = default) + { + return CreateJsonNodeForFramewiseWithSecurityOptions(securityOptions).ToJsonString(); + } - internal async Task FinishAsync(JobExecutionOptions executionOptions, SecurityOptions? securityOptions, CancellationToken cancellationToken) + internal async Task FinishAsync(JobExecutionOptions executionOptions, SecurityOptions? securityOptions, CancellationToken cancellationToken) + { + var inputByteArrays = await Task.WhenAll( + _inputs.Select(async pair => new KeyValuePair>(pair.Key, await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken)))); + using (var ctx = new JobContext()) { - var inputByteArrays = await Task.WhenAll( - _inputs.Select(async pair => new KeyValuePair>(pair.Key, await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken)))); - using (var ctx = new JobContext()) + foreach (var pair in inputByteArrays) { - foreach (var pair in inputByteArrays) - ctx.AddInputBytesPinned(pair.Key, pair.Value, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); + ctx.AddInputBytesPinned(pair.Key, pair.Value, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); + } - foreach (var outId in _outputs.Keys) - { - ctx.AddOutputBuffer(outId); - } + foreach (var outId in _outputs.Keys) + { + ctx.AddOutputBuffer(outId); + } - //TODO: Use a Semaphore to limit concurrency - var message = CreateJsonNodeForFramewiseWithSecurityOptions(securityOptions); + //TODO: Use a Semaphore to limit concurrency + var message = CreateJsonNodeForFramewiseWithSecurityOptions(securityOptions); - var response = executionOptions.OffloadCpuToThreadPool - ? await Task.Run(() => ctx.InvokeExecute(message), cancellationToken) - : ctx.InvokeExecute(message); + var response = executionOptions.OffloadCpuToThreadPool + ? await Task.Run(() => ctx.InvokeExecute(message), cancellationToken) + : ctx.InvokeExecute(message); - // TODO: Should we handle failure before copying out the buffers?? - using (response) - { + // TODO: Should we handle failure before copying out the buffers?? + using (response) + { - foreach (var pair in _outputs) - { - using var memOwner = ctx.BorrowOutputBufferMemoryAndAddReference(pair.Key); - await pair.Value.AdaptiveWriteAllAsync(memOwner.Memory, cancellationToken); - } - return BuildJobResult.From(response, _outputs); + foreach (var pair in _outputs) + { + using var memOwner = ctx.BorrowOutputBufferMemoryAndAddReference(pair.Key); + await pair.Value.AdaptiveWriteAllAsync(memOwner.Memory, cancellationToken); } + return BuildJobResult.From(response, _outputs); } } + } #pragma warning disable CS0618 // Type or member is obsolete - private class StreamJsonSpanProvider : IJsonResponseProvider, IJsonResponseSpanProvider, IJsonResponse + private class StreamJsonSpanProvider : IJsonResponseProvider, IJsonResponseSpanProvider, IJsonResponse #pragma warning restore CS0618 // Type or member is obsolete - { - private readonly MemoryStream _ms; - - public StreamJsonSpanProvider(int statusCode, MemoryStream ms) - { - ImageflowErrorCode = statusCode; - _ms = ms; - } - public void Dispose() => _ms.Dispose(); - public Stream GetStream() => _ms; - public ReadOnlySpan BorrowBytes() - { - return _ms.TryGetBufferSliceAllWrittenData(out var slice) ? slice : _ms.ToArray(); - } + { + private readonly MemoryStream _ms; - public int ImageflowErrorCode { get; } - public string CopyString() - { - return BorrowBytes().Utf8ToString(); - } + public StreamJsonSpanProvider(int statusCode, MemoryStream ms) + { + ImageflowErrorCode = statusCode; + _ms = ms; + } + public void Dispose() => _ms.Dispose(); + public Stream GetStream() => _ms; + public ReadOnlySpan BorrowBytes() + { + return _ms.TryGetBufferSliceAllWrittenData(out var slice) ? slice : _ms.ToArray(); + } - public JsonNode? Parse() - { - return BorrowBytes().ParseJsonNode(); - } + public int ImageflowErrorCode { get; } + public string CopyString() + { + return BorrowBytes().Utf8ToString(); + } - public byte[] CopyBytes() - { - return BorrowBytes().ToArray(); - } + public JsonNode? Parse() + { + return BorrowBytes().ParseJsonNode(); } + public byte[] CopyBytes() + { + return BorrowBytes().ToArray(); + } + } - // private object BuildJsonWithPlaceholders() - // { - // var inputIo = _inputs.Select(pair => - // new {io_id = pair.Key, direction = "in", io = new {placeholder = (string?) null}}); - // var outputIo = _outputs.Select(pair => - // new {io_id = pair.Key, direction = "out", io = new {placeholder = (string?) null}}); - // return new - // { - // io = inputIo.Concat(outputIo).ToArray(), - // framewise = ToFramewise() - // }; - // } + // private object BuildJsonWithPlaceholders() + // { + // var inputIo = _inputs.Select(pair => + // new {io_id = pair.Key, direction = "in", io = new {placeholder = (string?) null}}); + // var outputIo = _outputs.Select(pair => + // new {io_id = pair.Key, direction = "out", io = new {placeholder = (string?) null}}); + // return new + // { + // io = inputIo.Concat(outputIo).ToArray(), + // framewise = ToFramewise() + // }; + // } + + private static ITemporaryFileProvider SystemTempProvider() + { + return RuntimeFileLocator.IsUnix ? TemporaryFile.CreateProvider() : TemporaryMemoryFile.CreateProvider(); + } - private static ITemporaryFileProvider SystemTempProvider() + class SubprocessFilesystemJob : IPreparedFilesystemJob + { + public SubprocessFilesystemJob(ITemporaryFileProvider provider) { - return RuntimeFileLocator.IsUnix ? TemporaryFile.CreateProvider() : TemporaryMemoryFile.CreateProvider(); + Provider = provider; } + internal ITemporaryFileProvider Provider { get; } + public string JsonPath { get; set; } = ""; + public IReadOnlyDictionary OutputFiles { get; internal set; } = new ReadOnlyDictionary(new Dictionary()); + internal JsonNode? JobMessage { get; set; } + internal List Cleanup { get; } = new List(); + internal List>? Outputs { get; set; } - class SubprocessFilesystemJob : IPreparedFilesystemJob + internal async Task CopyOutputsToDestinations(CancellationToken token) { - public SubprocessFilesystemJob(ITemporaryFileProvider provider) + if (Outputs == null) { - Provider = provider; + return; } - internal ITemporaryFileProvider Provider { get; } - public string JsonPath { get; set; } = ""; - public IReadOnlyDictionary OutputFiles { get; internal set; } = new ReadOnlyDictionary(new Dictionary()); - internal JsonNode? JobMessage { get; set; } - internal List Cleanup { get; } = new List(); - internal List>? Outputs { get; set; } - - internal async Task CopyOutputsToDestinations(CancellationToken token) + foreach (var pair in Outputs) { - if (Outputs == null) return; - foreach (var pair in Outputs) + using (var stream = pair.Key.ReadFromBeginning()) { - using (var stream = pair.Key.ReadFromBeginning()) - { - await pair.Value.CopyFromStreamAsyncInternal(stream, token); - } + await pair.Value.CopyFromStreamAsyncInternal(stream, token); } } + } - public void Dispose() - { - foreach (var d in Cleanup) - { - d.Dispose(); - } - Cleanup.Clear(); - - } - ~SubprocessFilesystemJob() + public void Dispose() + { + foreach (var d in Cleanup) { - Dispose(); + d.Dispose(); } + Cleanup.Clear(); + } - private async Task PrepareForSubprocessAsync(CancellationToken cancellationToken, SecurityOptions? securityOptions, bool cleanupFiles, long? outputBufferCapacity = null) + ~SubprocessFilesystemJob() + { + Dispose(); + } + } + private async Task PrepareForSubprocessAsync(CancellationToken cancellationToken, SecurityOptions? securityOptions, bool cleanupFiles, long? outputBufferCapacity = null) + { + var job = new SubprocessFilesystemJob(cleanupFiles ? SystemTempProvider() : TemporaryFile.CreateProvider()); + try { - var job = new SubprocessFilesystemJob(cleanupFiles ? SystemTempProvider() : TemporaryFile.CreateProvider()); - try - { - var inputFiles = (await Task.WhenAll(_inputs.Select(async pair => - { - var bytes = await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken); + var inputFiles = (await Task.WhenAll(_inputs.Select(async pair => + { + var bytes = await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken); - var file = job.Provider.Create(cleanupFiles, bytes.Length); - job.Cleanup.Add(file); - return (io_id: pair.Key, direction: "in", - io: new JsonObject { { "file", file.Path } }, - bytes, bytes.Length, File: file); + var file = job.Provider.Create(cleanupFiles, bytes.Length); + job.Cleanup.Add(file); + return (io_id: pair.Key, direction: "in", + io: new JsonObject { { "file", file.Path } }, + bytes, bytes.Length, File: file); - }))).ToArray(); + }))).ToArray(); - var outputCapacity = outputBufferCapacity ?? inputFiles.Max(v => v.Length) * 2; - var outputFiles = _outputs.Select(pair => - { - var file = job.Provider.Create(cleanupFiles, outputCapacity); - job.Cleanup.Add(file); - return (io_id: pair.Key, direction: "out", - io: new JsonObject { { "file", file.Path } }, - Length: outputCapacity, File: file, Dest: pair.Value); - }).ToArray(); + var outputCapacity = outputBufferCapacity ?? inputFiles.Max(v => v.Length) * 2; + var outputFiles = _outputs.Select(pair => + { + var file = job.Provider.Create(cleanupFiles, outputCapacity); + job.Cleanup.Add(file); + return (io_id: pair.Key, direction: "out", + io: new JsonObject { { "file", file.Path } }, + Length: outputCapacity, File: file, Dest: pair.Value); + }).ToArray(); + foreach (var f in inputFiles) + { + using var accessor = f.File.WriteFromBeginning(); + await accessor.WriteMemoryAsync(f.bytes, cancellationToken); + } - foreach (var f in inputFiles) + // job.JobMessage = new + // { + // io = inputFiles.Select(v => (object) new {v.io_id, v.direction, v.io}) + // .Concat(outputFiles.Select(v => (object) new {v.io_id, v.direction, v.io})) + // .ToArray(), + // builder_config = new + // { + // security = securityOptions?.ToImageflowDynamic() + // }, + // framewise = ToFramewise() + // }; + job.JobMessage = new JsonObject + { + ["io"] = new JsonArray(inputFiles.Select(v => (JsonNode)new JsonObject { - using var accessor = f.File.WriteFromBeginning(); - await accessor.WriteMemoryAsync(f.bytes, cancellationToken); - } - - // job.JobMessage = new - // { - // io = inputFiles.Select(v => (object) new {v.io_id, v.direction, v.io}) - // .Concat(outputFiles.Select(v => (object) new {v.io_id, v.direction, v.io})) - // .ToArray(), - // builder_config = new - // { - // security = securityOptions?.ToImageflowDynamic() - // }, - // framewise = ToFramewise() - // }; - job.JobMessage = new JsonObject + ["io_id"] = v.io_id, + ["direction"] = v.direction, + ["io"] = v.io + }).Concat(outputFiles.Select(v => new JsonObject { - ["io"] = new JsonArray(inputFiles.Select(v => (JsonNode)new JsonObject - { - ["io_id"] = v.io_id, - ["direction"] = v.direction, - ["io"] = v.io - }).Concat(outputFiles.Select(v => new JsonObject - { - ["io_id"] = v.io_id, - ["direction"] = v.direction, - ["io"] = v.io - })).ToArray()), - ["builder_config"] = new JsonObject - { - ["security"] = securityOptions?.ToJsonNode() - }, - ["framewise"] = ToFramewise() - }; - - var outputFilenames = new Dictionary(); - foreach (var f in outputFiles) + ["io_id"] = v.io_id, + ["direction"] = v.direction, + ["io"] = v.io + })).ToArray()), + ["builder_config"] = new JsonObject { - outputFilenames[f.io_id] = f.File.Path; - } - - job.OutputFiles = new ReadOnlyDictionary(outputFilenames); - - job.Outputs = outputFiles - .Select(f => new KeyValuePair(f.File, f.Dest)).ToList(); - - - var jsonFile = job.Provider.Create(true, 100000); - job.Cleanup.Add(jsonFile); - var stream = jsonFile.WriteFromBeginning(); - // write job.JobMessage to stream using System.Text.Json - var writer = new Utf8JsonWriter(stream); - job.JobMessage.WriteTo(writer); - writer.Flush(); - stream.Flush(); - stream.Dispose(); - - - job.JsonPath = jsonFile.Path; + ["security"] = securityOptions?.ToJsonNode() + }, + ["framewise"] = ToFramewise() + }; - return job; - } - catch + var outputFilenames = new Dictionary(); + foreach (var f in outputFiles) { - job.Dispose(); - throw; + outputFilenames[f.io_id] = f.File.Path; } - } - + job.OutputFiles = new ReadOnlyDictionary(outputFilenames); + job.Outputs = outputFiles + .Select(f => new KeyValuePair(f.File, f.Dest)).ToList(); + var jsonFile = job.Provider.Create(true, 100000); + job.Cleanup.Add(jsonFile); + var stream = jsonFile.WriteFromBeginning(); + // write job.JobMessage to stream using System.Text.Json + var writer = new Utf8JsonWriter(stream); + job.JobMessage.WriteTo(writer); + writer.Flush(); + stream.Flush(); + stream.Dispose(); + job.JsonPath = jsonFile.Path; - internal async Task FinishInSubprocessAsync(SecurityOptions? securityOptions, - string? imageflowToolPath, long? outputBufferCapacity = null, CancellationToken cancellationToken = default(CancellationToken)) + return job; + } + catch { - if (imageflowToolPath == null) - { - imageflowToolPath = RuntimeFileLocator.IsUnix ? "imageflow_tool" : "imageflow_tool.exe"; - } - if (!File.Exists(imageflowToolPath)) - { - throw new FileNotFoundException("Cannot find imageflow_tool using path \"" + imageflowToolPath + "\" and currect folder \"" + Directory.GetCurrentDirectory() + "\""); - } + job.Dispose(); + throw; + } + } - using (var job = await PrepareForSubprocessAsync(cancellationToken, securityOptions, true, outputBufferCapacity)) - { + internal async Task FinishInSubprocessAsync(SecurityOptions? securityOptions, + string? imageflowToolPath, long? outputBufferCapacity = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (imageflowToolPath == null) + { + imageflowToolPath = RuntimeFileLocator.IsUnix ? "imageflow_tool" : "imageflow_tool.exe"; + } + if (!File.Exists(imageflowToolPath)) + { + throw new FileNotFoundException("Cannot find imageflow_tool using path \"" + imageflowToolPath + "\" and currect folder \"" + Directory.GetCurrentDirectory() + "\""); + } - var startInfo = new ProcessStartInfo - { - StandardErrorEncoding = Encoding.UTF8, - StandardOutputEncoding = Encoding.UTF8, - Arguments = $" v0.1/build --json {job.JsonPath}", - CreateNoWindow = true, - FileName = imageflowToolPath - }; + using (var job = await PrepareForSubprocessAsync(cancellationToken, securityOptions, true, outputBufferCapacity)) + { - var results = await ProcessEx.RunAsync(startInfo, cancellationToken); + var startInfo = new ProcessStartInfo + { + StandardErrorEncoding = Encoding.UTF8, + StandardOutputEncoding = Encoding.UTF8, + Arguments = $" v0.1/build --json {job.JsonPath}", + CreateNoWindow = true, + FileName = imageflowToolPath + }; + var results = await ProcessEx.RunAsync(startInfo, cancellationToken); - var output = results.GetBufferedOutputStream(); - var errors = results.GetStandardErrorString(); + var output = results.GetBufferedOutputStream(); + var errors = results.GetStandardErrorString(); - if (!string.IsNullOrWhiteSpace(errors) || results.ExitCode != 0) + if (!string.IsNullOrWhiteSpace(errors) || results.ExitCode != 0) + { + if (errors.Contains("InvalidJson")) + { + //throw new ImageflowException(errors + $"\n{JsonConvert.SerializeObject(job.JobMessage)}"); + throw new ImageflowException(errors + $"\n{job.JobMessage}"); + } + else { - if (errors.Contains("InvalidJson")) - { - //throw new ImageflowException(errors + $"\n{JsonConvert.SerializeObject(job.JobMessage)}"); - throw new ImageflowException(errors + $"\n{job.JobMessage}"); - } - else - { - throw new ImageflowException(errors); - } + throw new ImageflowException(errors); } + } - await job.CopyOutputsToDestinations(cancellationToken); + await job.CopyOutputsToDestinations(cancellationToken); - var outputMemory = output.GetWrittenMemory(); - return BuildJobResult.From(new MemoryJsonResponse(results.ExitCode, outputMemory), _outputs); - } + var outputMemory = output.GetWrittenMemory(); + return BuildJobResult.From(new MemoryJsonResponse(results.ExitCode, outputMemory), _outputs); } + } - internal async Task WriteJsonJobAndInputs(CancellationToken cancellationToken, SecurityOptions? securityOptions, bool deleteFilesOnDispose) - { - return await PrepareForSubprocessAsync(cancellationToken, securityOptions, deleteFilesOnDispose); - } + internal async Task WriteJsonJobAndInputs(CancellationToken cancellationToken, SecurityOptions? securityOptions, bool deleteFilesOnDispose) + { + return await PrepareForSubprocessAsync(cancellationToken, securityOptions, deleteFilesOnDispose); + } + private readonly List _nodesCreated = new List(10); + internal void AddNode(BuildItemBase n) + { + AssertReady(); - private readonly List _nodesCreated = new List(10); + if (_nodesCreated.Contains(n)) + { + throw new ImageflowAssertionFailed("Cannot add duplicate node"); + } + _nodesCreated.Add(n); + if (n.Canvas != null && !_nodesCreated.Contains(n.Canvas))// || n.Canvas.Builder != this)) + { + throw new ImageflowAssertionFailed("You cannot use a canvas node from a different ImageJob"); + } + if (n.Input != null && !_nodesCreated.Contains(n.Input)) + { + throw new ImageflowAssertionFailed("You cannot use an input node from a different ImageJob"); + } + } + private enum EdgeKind + { + Canvas, + Input + } + private ICollection CollectUnique() => _nodesCreated; - internal void AddNode(BuildItemBase n) + private static IEnumerable> CollectEdges(ICollection forUniqueNodes) + { + var edges = new List>(forUniqueNodes.Count); + foreach (var n in forUniqueNodes) { - AssertReady(); - - if (_nodesCreated.Contains(n)) + if (n.Canvas != null) { - throw new ImageflowAssertionFailed("Cannot add duplicate node"); + edges.Add(new Tuple(n.Canvas.Uid, n.Uid, EdgeKind.Canvas)); } - _nodesCreated.Add(n); - if (n.Canvas != null && !_nodesCreated.Contains(n.Canvas))// || n.Canvas.Builder != this)) + if (n.Input != null) { - throw new ImageflowAssertionFailed("You cannot use a canvas node from a different ImageJob"); - } - if (n.Input != null && !_nodesCreated.Contains(n.Input)) - { - throw new ImageflowAssertionFailed("You cannot use an input node from a different ImageJob"); + edges.Add(new Tuple(n.Input.Uid, n.Uid, EdgeKind.Input)); } } + return edges; + } + private static long? LowestUid(IEnumerable forNodes) => forNodes.Select(n => n.Uid as long?).Min(); - private enum EdgeKind - { - Canvas, - Input - } - - private ICollection CollectUnique() => _nodesCreated; + internal JsonNode ToFramewise() + { + var nodes = CollectUnique(); + return ToFramewiseGraph(nodes); + } - private static IEnumerable> CollectEdges(ICollection forUniqueNodes) + private JsonNode ToFramewiseGraph(ICollection uniqueNodes) + { + var lowestUid = LowestUid(uniqueNodes) ?? 0; + var edges = CollectEdges(uniqueNodes) + .OrderBy(t => t.Item1) + .ThenBy(t => t.Item2).ToList(); + //var framewiseEdges = edges.Select(t => new + // { + // from = t.Item1 - lowestUid, + // to = t.Item2 - lowestUid, + // kind = t.Item3.ToString().ToLowerInvariant() + // }).ToList(); + JsonNode[] framewiseEdges = edges.Select(t => (JsonNode)new JsonObject { - var edges = new List>(forUniqueNodes.Count); - foreach (var n in forUniqueNodes) - { - if (n.Canvas != null) - { - edges.Add(new Tuple(n.Canvas.Uid, n.Uid, EdgeKind.Canvas)); - } - if (n.Input != null) - { - edges.Add(new Tuple(n.Input.Uid, n.Uid, EdgeKind.Input)); - } - } - return edges; - } - - private static long? LowestUid(IEnumerable forNodes) => forNodes.Select(n => n.Uid as long?).Min(); + ["from"] = t.Item1 - lowestUid, + ["to"] = t.Item2 - lowestUid, + ["kind"] = t.Item3.ToString().ToLowerInvariant() + }).ToArray(); - internal JsonNode ToFramewise() + var nodes = new JsonObject(); + foreach (var n in uniqueNodes) { - var nodes = CollectUnique(); - return ToFramewiseGraph(nodes); + nodes.Add((n.Uid - lowestUid).ToString(), n.NodeData); } - - private JsonNode ToFramewiseGraph(ICollection uniqueNodes) + // return new + // { + // graph = new + // { + // edges = framewiseEdges, + // nodes = framewiseNodes + // } + // }; + return new JsonObject { - var lowestUid = LowestUid(uniqueNodes) ?? 0; - var edges = CollectEdges(uniqueNodes) - .OrderBy(t => t.Item1) - .ThenBy(t => t.Item2).ToList(); - //var framewiseEdges = edges.Select(t => new - // { - // from = t.Item1 - lowestUid, - // to = t.Item2 - lowestUid, - // kind = t.Item3.ToString().ToLowerInvariant() - // }).ToList(); - JsonNode[] framewiseEdges = edges.Select(t => (JsonNode)new JsonObject - { - ["from"] = t.Item1 - lowestUid, - ["to"] = t.Item2 - lowestUid, - ["kind"] = t.Item3.ToString().ToLowerInvariant() - }).ToArray(); - - - var nodes = new JsonObject(); - foreach (var n in uniqueNodes) + ["graph"] = new JsonObject { - nodes.Add((n.Uid - lowestUid).ToString(), n.NodeData); + ["edges"] = new JsonArray(framewiseEdges), + ["nodes"] = nodes } - // return new - // { - // graph = new - // { - // edges = framewiseEdges, - // nodes = framewiseNodes - // } - // }; - return new JsonObject - { - ["graph"] = new JsonObject - { - ["edges"] = new JsonArray(framewiseEdges), - ["nodes"] = nodes - } - }; - } + }; + } - private void AssertReady() + private void AssertReady() + { + if (_disposed) { - if (_disposed) throw new ObjectDisposedException("ImageJob"); + throw new ObjectDisposedException("ImageJob"); } + } - public void Dispose() + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) { - // Dispose of unmanaged resources. - Dispose(true); - // Suppress finalization. - GC.SuppressFinalize(this); + return; } - protected virtual void Dispose(bool disposing) + if (disposing) { - if (_disposed) return; + foreach (var v in _inputs.Values) + { + v.Dispose(); + } - if (disposing) + _inputs.Clear(); + foreach (var v in _outputs.Values) { - foreach (var v in _inputs.Values) - v.Dispose(); - _inputs.Clear(); - foreach (var v in _outputs.Values) - v.Dispose(); - _outputs.Clear(); + v.Dispose(); } - _disposed = true; + _outputs.Clear(); } - public int GenerateIoId() => _inputs.Keys.Concat(_outputs.Keys).DefaultIfEmpty(-1).Max() + 1; - - /// - /// Returns dimensions and format of the provided image stream or byte array - /// - /// - /// - [Obsolete("Use GetImageInfoAsync(IMemorySource, DataLifetime) instead; this method is less efficient and lacks clarity on disposing the source.")] - public static Task GetImageInfo(IBytesSource image) - => GetImageInfo(image, CancellationToken.None); - + _disposed = true; + } - /// - /// Returns dimensions and format of the provided image stream or byte array - /// - /// - /// - /// - [Obsolete("Use GetImageInfoAsync(IMemorySource, DataLifetime) instead; this method is less efficient and lacks clarity on disposing the source.")] - public static async Task GetImageInfo(IBytesSource image, CancellationToken cancellationToken) + public int GenerateIoId() => _inputs.Keys.Concat(_outputs.Keys).DefaultIfEmpty(-1).Max() + 1; + + /// + /// Returns dimensions and format of the provided image stream or byte array + /// + /// + /// + [Obsolete("Use GetImageInfoAsync(IMemorySource, DataLifetime) instead; this method is less efficient and lacks clarity on disposing the source.")] + public static Task GetImageInfo(IBytesSource image) + => GetImageInfo(image, CancellationToken.None); + + /// + /// Returns dimensions and format of the provided image stream or byte array + /// + /// + /// + /// + [Obsolete("Use GetImageInfoAsync(IMemorySource, DataLifetime) instead; this method is less efficient and lacks clarity on disposing the source.")] + public static async Task GetImageInfo(IBytesSource image, CancellationToken cancellationToken) + { + try { - try - { - var inputByteArray = await image.GetBytesAsync(cancellationToken); - using (var ctx = new JobContext()) - { - ctx.AddInputBytesPinned(0, inputByteArray); - return ctx.GetImageInfo(0); - } - } - finally + var inputByteArray = await image.GetBytesAsync(cancellationToken); + using (var ctx = new JobContext()) { - image.Dispose(); + ctx.AddInputBytesPinned(0, inputByteArray); + return ctx.GetImageInfo(0); } } + finally + { + image.Dispose(); + } + } - /// - /// Returns dimensions and format of the provided image stream or byte array. - /// Does NOT dispose the IMemorySource. - /// - /// - /// - /// - public static ImageInfo GetImageInfo(IMemorySource image, SourceLifetime disposeSource) + /// + /// Returns dimensions and format of the provided image stream or byte array. + /// Does NOT dispose the IMemorySource. + /// + /// + /// + /// + public static ImageInfo GetImageInfo(IMemorySource image, SourceLifetime disposeSource) + { + try { - try - { - var inputMemory = image.BorrowReadOnlyMemory(); - using var ctx = new JobContext(); - ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); - return ctx.GetImageInfo(0); - } - finally + var inputMemory = image.BorrowReadOnlyMemory(); + using var ctx = new JobContext(); + ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); + return ctx.GetImageInfo(0); + } + finally + { + if (disposeSource != SourceLifetime.Borrowed) { - if (disposeSource != SourceLifetime.Borrowed) - { - image.Dispose(); - } + image.Dispose(); } } + } - /// - /// Returns dimensions and format of the provided image stream or memory. - /// Does not offload processing to a thread pool; will be CPU bound unless IMemorySource is not yet in memory. - /// Does not dispose the IMemorySource. - /// - /// - /// - /// - /// - public static async ValueTask GetImageInfoAsync(IAsyncMemorySource image, SourceLifetime disposeSource, CancellationToken cancellationToken = default) + /// + /// Returns dimensions and format of the provided image stream or memory. + /// Does not offload processing to a thread pool; will be CPU bound unless IMemorySource is not yet in memory. + /// Does not dispose the IMemorySource. + /// + /// + /// + /// + /// + public static async ValueTask GetImageInfoAsync(IAsyncMemorySource image, SourceLifetime disposeSource, CancellationToken cancellationToken = default) + { + try { - try + var inputMemory = await image.BorrowReadOnlyMemoryAsync(cancellationToken); + if (inputMemory.Length == 0) { - var inputMemory = await image.BorrowReadOnlyMemoryAsync(cancellationToken); - if (inputMemory.Length == 0) throw new ArgumentException("Input image is empty"); - if (cancellationToken.IsCancellationRequested) throw new TaskCanceledException(); - using var ctx = new JobContext(); - ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); - return ctx.GetImageInfo(0); + throw new ArgumentException("Input image is empty"); } - finally + + if (cancellationToken.IsCancellationRequested) { - if (disposeSource != SourceLifetime.Borrowed) - { - image.Dispose(); - } + throw new TaskCanceledException(); } + + using var ctx = new JobContext(); + ctx.AddInputBytesPinned(0, inputMemory, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed); + return ctx.GetImageInfo(0); } - /// - /// Returns true if it is likely that Imageflow can decode the given image based on the first 12 bytes of the file. - /// - /// The first 12 or more bytes of the file - /// - [Obsolete("Bad idea: imageflow may eventually support formats via OS codecs, so this is not predictable. Use Imazen.Common.FileTypeDetection.FileTypeDetector().GuessMimeType(data) with your own allowlist instead.")] - public static bool CanDecodeBytes(byte[] first12Bytes) + finally { - return MagicBytes.IsDecodable(first12Bytes); + if (disposeSource != SourceLifetime.Borrowed) + { + image.Dispose(); + } } + } + /// + /// Returns true if it is likely that Imageflow can decode the given image based on the first 12 bytes of the file. + /// + /// The first 12 or more bytes of the file + /// + [Obsolete("Bad idea: imageflow may eventually support formats via OS codecs, so this is not predictable. Use Imazen.Common.FileTypeDetection.FileTypeDetector().GuessMimeType(data) with your own allowlist instead.")] + public static bool CanDecodeBytes(byte[] first12Bytes) + { + return MagicBytes.IsDecodable(first12Bytes); + } - /// - /// Returns a MIME type string such as "image/jpeg" based on the provided first 12 bytes of the file. - /// Only guaranteed to work for image types Imageflow supports, but support for more file types may be added - /// later. - /// - /// The first 12 or more bytes of the file - /// - [Obsolete("Use new Imazen.Common.FileTypeDetection.FileTypeDetector().GuessMimeType(data) instead")] - public static string? GetContentTypeForBytes(byte[] first12Bytes) - { - return MagicBytes.GetImageContentType(first12Bytes); - } + /// + /// Returns a MIME type string such as "image/jpeg" based on the provided first 12 bytes of the file. + /// Only guaranteed to work for image types Imageflow supports, but support for more file types may be added + /// later. + /// + /// The first 12 or more bytes of the file + /// + [Obsolete("Use new Imazen.Common.FileTypeDetection.FileTypeDetector().GuessMimeType(data) instead")] + public static string? GetContentTypeForBytes(byte[] first12Bytes) + { + return MagicBytes.GetImageContentType(first12Bytes); } } diff --git a/src/Imageflow/Fluent/InputWatermark.cs b/src/Imageflow/Fluent/InputWatermark.cs index 31d3725..ec27335 100644 --- a/src/Imageflow/Fluent/InputWatermark.cs +++ b/src/Imageflow/Fluent/InputWatermark.cs @@ -1,50 +1,49 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public class InputWatermark { - public class InputWatermark + [Obsolete("Call .ToMemorySource() on the source to use InputWatermark(IMemorySource source, WatermarkOptions watermark instead. For improved performance, use the new BufferedStreamSource and MemorySource classes directly.")] + public InputWatermark(IBytesSource source, int ioId, WatermarkOptions watermark) + { + Source = source.ToMemorySource(); + IoId = ioId; + Watermark = watermark; + } + [Obsolete("Call .ToMemorySource() on the source to use InputWatermark(IMemorySource source, WatermarkOptions watermark instead. For improved performance, use the new BufferedStreamSource and MemorySource classes directly.")] + public InputWatermark(IBytesSource source, WatermarkOptions watermark) + { + Source = source.ToMemorySource(); + Watermark = watermark; + } + + public InputWatermark(IAsyncMemorySource source, WatermarkOptions watermark) + { + Source = source; + Watermark = watermark; + } + + public InputWatermark(IAsyncMemorySource source, int ioId, WatermarkOptions watermark) + { + Source = source; + IoId = ioId; + Watermark = watermark; + } + + public IAsyncMemorySource Source { get; set; } + public int? IoId { get; set; } + public WatermarkOptions Watermark { get; set; } + + [Obsolete("Use ToJsonNode() methods instead")] + public object ToImageflowDynamic() { - [Obsolete("Call .ToMemorySource() on the source to use InputWatermark(IMemorySource source, WatermarkOptions watermark instead. For improved performance, use the new BufferedStreamSource and MemorySource classes directly.")] - public InputWatermark(IBytesSource source, int ioId, WatermarkOptions watermark) - { - Source = source.ToMemorySource(); - IoId = ioId; - Watermark = watermark; - } - [Obsolete("Call .ToMemorySource() on the source to use InputWatermark(IMemorySource source, WatermarkOptions watermark instead. For improved performance, use the new BufferedStreamSource and MemorySource classes directly.")] - public InputWatermark(IBytesSource source, WatermarkOptions watermark) - { - Source = source.ToMemorySource(); - Watermark = watermark; - } - - public InputWatermark(IAsyncMemorySource source, WatermarkOptions watermark) - { - Source = source; - Watermark = watermark; - } - - public InputWatermark(IAsyncMemorySource source, int ioId, WatermarkOptions watermark) - { - Source = source; - IoId = ioId; - Watermark = watermark; - } - - public IAsyncMemorySource Source { get; set; } - public int? IoId { get; set; } - public WatermarkOptions Watermark { get; set; } - - [Obsolete("Use ToJsonNode() methods instead")] - public object ToImageflowDynamic() - { - return Watermark.ToImageflowDynamic(IoId ?? throw new InvalidOperationException("InputWatermark.ToImageflowDynamic() cannot be called without an IoId value assigned")); - } - - internal JsonNode ToJsonNode() - { - return Watermark.ToJsonNode(IoId ?? throw new InvalidOperationException("InputWatermark.ToJson() cannot be called without an IoId value assigned")); - } + return Watermark.ToImageflowDynamic(IoId ?? throw new InvalidOperationException("InputWatermark.ToImageflowDynamic() cannot be called without an IoId value assigned")); + } + internal JsonNode ToJsonNode() + { + return Watermark.ToJsonNode(IoId ?? throw new InvalidOperationException("InputWatermark.ToJson() cannot be called without an IoId value assigned")); } + } diff --git a/src/Imageflow/Fluent/JobExecutionOptions.cs b/src/Imageflow/Fluent/JobExecutionOptions.cs index ae31a95..5f328c4 100644 --- a/src/Imageflow/Fluent/JobExecutionOptions.cs +++ b/src/Imageflow/Fluent/JobExecutionOptions.cs @@ -1,7 +1,6 @@ -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +internal class JobExecutionOptions { - internal class JobExecutionOptions - { - internal bool OffloadCpuToThreadPool { get; set; } = false; - } + internal bool OffloadCpuToThreadPool { get; set; } = false; } diff --git a/src/Imageflow/Fluent/MagicBytes.cs b/src/Imageflow/Fluent/MagicBytes.cs index fe02d93..c72e2cb 100644 --- a/src/Imageflow/Fluent/MagicBytes.cs +++ b/src/Imageflow/Fluent/MagicBytes.cs @@ -1,504 +1,514 @@ // ReSharper disable InconsistentNaming -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +[Obsolete("Use new Imazen.Common.FileTypeDetection.FileTypeDetector().GuessMimeType(data) instead")] +internal static class MagicBytes { - [Obsolete("Use new Imazen.Common.FileTypeDetection.FileTypeDetector().GuessMimeType(data) instead")] - internal static class MagicBytes + internal enum ImageFormat { - internal enum ImageFormat - { - Jpeg, - Gif, - Png, - WebP, - Tiff, - Avif, - AvifSequence, - Heif, - HeifSequence, - Heic, - HeicSequence, - Bitmap, - Ico, - QuickTimeMovie, - M4V, - Woff, - Woff2, - OpenTypeFont, - PostScript, - Mp3, - Pdf, - // ReSharper disable once IdentifierTypo - FLIF, - AIFF, - TrueTypeFont, - JpegXL, - Jpeg2000, - MP4, - OggContainer, - Mpeg1, - Mpeg2, - MatroskaOrWebM, - WaveformAudio, - AudioVideoInterleave, - Flac, - M4P, - M4B, - M4A, - F4V, - F4P, - F4B, - F4A, - Aac, - ThreeGPP - } + Jpeg, + Gif, + Png, + WebP, + Tiff, + Avif, + AvifSequence, + Heif, + HeifSequence, + Heic, + HeicSequence, + Bitmap, + Ico, + QuickTimeMovie, + M4V, + Woff, + Woff2, + OpenTypeFont, + PostScript, + Mp3, + Pdf, + // ReSharper disable once IdentifierTypo + FLIF, + AIFF, + TrueTypeFont, + JpegXL, + Jpeg2000, + MP4, + OggContainer, + Mpeg1, + Mpeg2, + MatroskaOrWebM, + WaveformAudio, + AudioVideoInterleave, + Flac, + M4P, + M4B, + M4A, + F4V, + F4P, + F4B, + F4A, + Aac, + ThreeGPP + } - /// - /// Returns null if not a recognized file type - /// - /// First 12 or more bytes of the file - /// - /// - private static ImageFormat? GetImageFormat(byte[] first12Bytes) - { + /// + /// Returns null if not a recognized file type + /// + /// First 12 or more bytes of the file + /// + /// + private static ImageFormat? GetImageFormat(byte[] first12Bytes) + { - // Useful resources: https://chromium.googlesource.com/chromium/src/+/HEAD/net/base/mime_sniffer.cc - // https://github.com/velocityzen/FileType/blob/master/Sources/FileType/FileTypeMatch.swift - // https://en.wikipedia.org/wiki/List_of_file_signatures - // https://mimetype.io/ + // Useful resources: https://chromium.googlesource.com/chromium/src/+/HEAD/net/base/mime_sniffer.cc + // https://github.com/velocityzen/FileType/blob/master/Sources/FileType/FileTypeMatch.swift + // https://en.wikipedia.org/wiki/List_of_file_signatures + // https://mimetype.io/ - // We may want to support these from Chrome's sniffer, at some point, after research - // MAGIC_MASK("video/mpeg", "\x00\x00\x01\xB0", "\xFF\xFF\xFF\xF0"), - // MAGIC_MASK("audio/mpeg", "\xFF\xE0", "\xFF\xE0"), - // MAGIC_NUMBER("video/quicktime", "....moov"), - // MAGIC_NUMBER("application/x-shockwave-flash", "CWS"), - // MAGIC_NUMBER("application/x-shockwave-flash", "FWS"), - // MAGIC_NUMBER("video/x-flv", "FLV"), + // We may want to support these from Chrome's sniffer, at some point, after research + // MAGIC_MASK("video/mpeg", "\x00\x00\x01\xB0", "\xFF\xFF\xFF\xF0"), + // MAGIC_MASK("audio/mpeg", "\xFF\xE0", "\xFF\xE0"), + // MAGIC_NUMBER("video/quicktime", "....moov"), + // MAGIC_NUMBER("application/x-shockwave-flash", "CWS"), + // MAGIC_NUMBER("application/x-shockwave-flash", "FWS"), + // MAGIC_NUMBER("video/x-flv", "FLV"), + // Choosing not to detect mime types for text, svg, javascript, or executable formats + // 00 61 73 6D (WebAssembly) - // Choosing not to detect mime types for text, svg, javascript, or executable formats - // 00 61 73 6D (WebAssembly) + // With just 12 bytes, we also can't tell PNG from APNG, Ogg audio from video, or what's in a matroska or mpeg container - // With just 12 bytes, we also can't tell PNG from APNG, Ogg audio from video, or what's in a matroska or mpeg container + var bytes = first12Bytes; + if (bytes.Length < 12) + { + throw new ArgumentException("The byte array must contain at least 12 bytes", + nameof(first12Bytes)); + } + if (bytes[0] == 0xff && bytes[1] == 0xd8 && bytes[2] == 0xff) + { + //bytes[3] could be E0 or E1 for standard jpeg or exif respectively. + //0xE2 or 0xE8 would be a CIFF or SPIFF image + //We'll avoid being picky in case whichever codec you use can get the jpeg data from a ciff/spiff/etc + return ImageFormat.Jpeg; + } - var bytes = first12Bytes; - if (bytes.Length < 12) throw new ArgumentException("The byte array must contain at least 12 bytes", - nameof(first12Bytes)); + if (bytes[0] == 'G' && bytes[1] == 'I' && bytes[2] == 'F' && bytes[3] == '8' && + (bytes[4] == '9' || bytes[4] == '7') && bytes[5] == 'a') + { + return ImageFormat.Gif; + } - if (bytes[0] == 0xff && bytes[1] == 0xd8 && bytes[2] == 0xff) - { - //bytes[3] could be E0 or E1 for standard jpeg or exif respectively. - //0xE2 or 0xE8 would be a CIFF or SPIFF image - //We'll avoid being picky in case whichever codec you use can get the jpeg data from a ciff/spiff/etc - return ImageFormat.Jpeg; - } + if (bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4e && bytes[3] == 0x47 && + bytes[4] == 0x0d && bytes[5] == 0x0a && bytes[6] == 0x1a && bytes[7] == 0x0a) + { + return ImageFormat.Png; + } - if (bytes[0] == 'G' && bytes[1] == 'I' && bytes[2] == 'F' && bytes[3] == '8' && - (bytes[4] == '9' || bytes[4] == '7') && bytes[5] == 'a') + if (bytes[0] == 'R' && bytes[1] == 'I' && bytes[2] == 'F' && bytes[3] == 'F') + { + if (bytes[8] == 'W' && bytes[9] == 'E' && bytes[10] == 'B' && bytes[11] == 'P') { - return ImageFormat.Gif; + return ImageFormat.WebP; } - - if (bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4e && bytes[3] == 0x47 && - bytes[4] == 0x0d && bytes[5] == 0x0a && bytes[6] == 0x1a && bytes[7] == 0x0a) + if (bytes[8] == 'W' && bytes[9] == 'A' && bytes[10] == 'V' && bytes[11] == 'E') { - return ImageFormat.Png; + return ImageFormat.WaveformAudio; } - - if (bytes[0] == 'R' && bytes[1] == 'I' && bytes[2] == 'F' && bytes[3] == 'F') + if (bytes[8] == 'A' && bytes[9] == 'V' && bytes[10] == 'I' && bytes[11] == 0x20) { - if (bytes[8] == 'W' && bytes[9] == 'E' && bytes[10] == 'B' && bytes[11] == 'P') - { - return ImageFormat.WebP; - } - if (bytes[8] == 'W' && bytes[9] == 'A' && bytes[10] == 'V' && bytes[11] == 'E') - { - return ImageFormat.WaveformAudio; - } - if (bytes[8] == 'A' && bytes[9] == 'V' && bytes[10] == 'I' && bytes[11] == 0x20) - { - return ImageFormat.AudioVideoInterleave; - } + return ImageFormat.AudioVideoInterleave; } + } - if ((bytes[0] == 'M' && bytes[1] == 'M' && bytes[2] == 0x00 && bytes[3] == '*') || - (bytes[0] == 'I' && bytes[1] == 'I' && bytes[2] == '*' && bytes[3] == 0x00) || - (bytes[0] == 'I' && bytes[1] == ' ' && bytes[2] == 'I')) - { - // From chrome's mime sniffer we got MAGIC_NUMBER("image/tiff", "I I"), - return ImageFormat.Tiff; - } + if ((bytes[0] == 'M' && bytes[1] == 'M' && bytes[2] == 0x00 && bytes[3] == '*') || + (bytes[0] == 'I' && bytes[1] == 'I' && bytes[2] == '*' && bytes[3] == 0x00) || + (bytes[0] == 'I' && bytes[1] == ' ' && bytes[2] == 'I')) + { + // From chrome's mime sniffer we got MAGIC_NUMBER("image/tiff", "I I"), + return ImageFormat.Tiff; + } - if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x01 && bytes[3] == 0x00) - { - return ImageFormat.Ico; - } + if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x01 && bytes[3] == 0x00) + { + return ImageFormat.Ico; + } - if (bytes[0] == 'w' && bytes[1] == 'O' && bytes[2] == 'F') + if (bytes[0] == 'w' && bytes[1] == 'O' && bytes[2] == 'F') + { + if (bytes[3] == 'F') { - if (bytes[3] == 'F') return ImageFormat.Woff; - if (bytes[3] == '2') return ImageFormat.Woff2; + return ImageFormat.Woff; } - if (bytes[0] == 'O' && bytes[1] == 'T' && bytes[2] == 'T' && bytes[3] == 'O') + if (bytes[3] == '2') { - return ImageFormat.OpenTypeFont; + return ImageFormat.Woff2; } + } - if (bytes[0] == 'f' && bytes[1] == 'L' && bytes[2] == 'a' && bytes[3] == 'C') - { - return ImageFormat.Flac; - } - if (bytes[0] == 0x00 && bytes[1] == 0x01 && bytes[2] == 0x00 && bytes[3] == 0x00) - { - return ImageFormat.TrueTypeFont; - } + if (bytes[0] == 'O' && bytes[1] == 'T' && bytes[2] == 'T' && bytes[3] == 'O') + { + return ImageFormat.OpenTypeFont; + } + if (bytes[0] == 'f' && bytes[1] == 'L' && bytes[2] == 'a' && bytes[3] == 'C') + { + return ImageFormat.Flac; + } + if (bytes[0] == 0x00 && bytes[1] == 0x01 && bytes[2] == 0x00 && bytes[3] == 0x00) + { + return ImageFormat.TrueTypeFont; + } - if (bytes[0] == 0x1A && bytes[1] == 0x45 && bytes[2] == 0xDF && bytes[3] == 0xA3) - { - return ImageFormat.MatroskaOrWebM; - } + if (bytes[0] == 0x1A && bytes[1] == 0x45 && bytes[2] == 0xDF && bytes[3] == 0xA3) + { + return ImageFormat.MatroskaOrWebM; + } - if (bytes[0] == '%' && bytes[1] == 'P' && bytes[2] == 'D' && bytes[3] == 'F' && bytes[4] == '-') - { - return ImageFormat.Pdf; - } - if (bytes[0] == '%' && bytes[1] == '!' && bytes[2] == 'P' && bytes[3] == 'S' && bytes[4] == '-' && bytes[5] == 'A' - && bytes[6] == 'd' && bytes[7] == 'o' && bytes[8] == 'b' && bytes[9] == 'e' && bytes[10] == '-') - { - return ImageFormat.PostScript; - } + if (bytes[0] == '%' && bytes[1] == 'P' && bytes[2] == 'D' && bytes[3] == 'F' && bytes[4] == '-') + { + return ImageFormat.Pdf; + } + if (bytes[0] == '%' && bytes[1] == '!' && bytes[2] == 'P' && bytes[3] == 'S' && bytes[4] == '-' && bytes[5] == 'A' + && bytes[6] == 'd' && bytes[7] == 'o' && bytes[8] == 'b' && bytes[9] == 'e' && bytes[10] == '-') + { + return ImageFormat.PostScript; + } - if (bytes[0] == 'F' && bytes[1] == 'L' && bytes[2] == 'I' && bytes[3] == 'F') - { - return ImageFormat.FLIF; - } + if (bytes[0] == 'F' && bytes[1] == 'L' && bytes[2] == 'I' && bytes[3] == 'F') + { + return ImageFormat.FLIF; + } - if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x00) + if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x00) + { + if (bytes[3] == 0x0C + && bytes[7] == 0x20 && bytes[8] == 0x0D && bytes[9] == 0x0A && bytes[10] == 0x87 + && bytes[11] == 0x0A) { - if (bytes[3] == 0x0C - && bytes[7] == 0x20 && bytes[8] == 0x0D && bytes[9] == 0x0A && bytes[10] == 0x87 - && bytes[11] == 0x0A) + if (bytes[4] == 0x4A && bytes[5] == 0x58 && bytes[6] == 0x4C) { - if (bytes[4] == 0x4A && bytes[5] == 0x58 && bytes[6] == 0x4C) - { - return ImageFormat.JpegXL; - } + return ImageFormat.JpegXL; + } - if (bytes[4] == 0x6A && bytes[5] == 0x50 && bytes[6] == 0x20) - { - return ImageFormat.Jpeg2000; - } + if (bytes[4] == 0x6A && bytes[5] == 0x50 && bytes[6] == 0x20) + { + return ImageFormat.Jpeg2000; } + } + } - } + if (bytes[0] == 0xFF && bytes[1] == 0x0A) + { + return ImageFormat.JpegXL; + } - if (bytes[0] == 0xFF && bytes[1] == 0x0A) - { - return ImageFormat.JpegXL; - } + if (bytes[0] == 'B' && bytes[1] == 'M') + { + return ImageFormat.Bitmap; + } - if (bytes[0] == 'B' && bytes[1] == 'M') - { - return ImageFormat.Bitmap; - } + if (bytes[0] == 'O' && bytes[1] == 'g' && bytes[2] == 'g' && bytes[3] == 'S' && bytes[4] == 0x00) + { + return ImageFormat.OggContainer; // Could be audio or video, no idea, Chrome presumes audio + } + if (bytes[0] == 0x49 && bytes[1] == 0x44 && bytes[2] == 0x33) + { + return ImageFormat.Mp3; + } + if (bytes[0] == 0xFF && (bytes[1] == 0xFB || bytes[1] == 0xF3 || bytes[1] == 0xF2)) + { + return ImageFormat.Mp3; + } - if (bytes[0] == 'O' && bytes[1] == 'g' && bytes[2] == 'g' && bytes[3] == 'S' && bytes[4] == 0x00) + if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x01 && bytes[3] == 0xBA) + { + if ((bytes[4] & 0xF1) == 0x21) { - return ImageFormat.OggContainer; // Could be audio or video, no idea, Chrome presumes audio + // MPEG-PS, MPEG-1 Part 1 + return ImageFormat.Mpeg1; } - if (bytes[0] == 0x49 && bytes[1] == 0x44 && bytes[2] == 0x33) + + if ((bytes[4] & 0xC4) == 0x44) { - return ImageFormat.Mp3; + //MPEG-PS, MPEG-2 Part 1 + return ImageFormat.Mpeg2; } + } - if (bytes[0] == 0xFF && (bytes[1] == 0xFB || bytes[1] == 0xF3 || bytes[1] == 0xF2)) + if (bytes[0] == 0xFF && (bytes[1] == 0xF1 || bytes[1] == 0xF9)) + { + return ImageFormat.Aac; + } + + if (bytes[0] == 'F' && bytes[1] == 'O' && bytes[2] == 'R' && bytes[3] == 'M') + { + return ImageFormat.AIFF; + } + if (bytes[4] == 'f' && bytes[5] == 't' && bytes[6] == 'y' && bytes[7] == 'p') + { + if (bytes[8] == '3' && bytes[9] == 'g') { - return ImageFormat.Mp3; + return ImageFormat.ThreeGPP; } - - if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x01 && bytes[3] == 0xBA) + if (bytes[8] == 'a' && bytes[9] == 'v' && bytes[10] == 'i') { - if ((bytes[4] & 0xF1) == 0x21) + if (bytes[11] == 'f') { - // MPEG-PS, MPEG-1 Part 1 - return ImageFormat.Mpeg1; + return ImageFormat.Avif; } - if ((bytes[4] & 0xC4) == 0x44) + if (bytes[11] == 's') { - //MPEG-PS, MPEG-2 Part 1 - return ImageFormat.Mpeg2; + return ImageFormat.AvifSequence; } } - - if (bytes[0] == 0xFF && (bytes[1] == 0xF1 || bytes[1] == 0xF9)) - { - return ImageFormat.Aac; - } - - if (bytes[0] == 'F' && bytes[1] == 'O' && bytes[2] == 'R' && bytes[3] == 'M') - { - return ImageFormat.AIFF; - } - if (bytes[4] == 'f' && bytes[5] == 't' && bytes[6] == 'y' && bytes[7] == 'p') + // HEIF/HEIC has.. a lot of variations + // http://nokiatech.github.io/heif/technical.html + // https://mimetype.io/image/heic + if (bytes[8] == 'm' && bytes[10] == 'f' & bytes[11] == '1') { - if (bytes[8] == '3' && bytes[9] == 'g') + if (bytes[9] == 'i') { - return ImageFormat.ThreeGPP; + return ImageFormat.Heif; } - if (bytes[8] == 'a' && bytes[9] == 'v' && bytes[10] == 'i') + + if (bytes[9] == 's') { - if (bytes[11] == 'f') - { - return ImageFormat.Avif; - } + return ImageFormat.HeifSequence; + } + } - if (bytes[11] == 's') + if (bytes[8] == 'h' && bytes[9] == 'e') + { + if (bytes[10] == 'i') + { + if (bytes[11] == 'c' || + bytes[11] == 'x' || + bytes[11] == 'm' || + bytes[11] == 's') { - return ImageFormat.AvifSequence; + return ImageFormat.Heic; } } - // HEIF/HEIC has.. a lot of variations - // http://nokiatech.github.io/heif/technical.html - // https://mimetype.io/image/heic - if (bytes[8] == 'm' && bytes[10] == 'f' & bytes[11] == '1') + if (bytes[10] == 'v') { - if (bytes[9] == 'i') - return ImageFormat.Heif; - if (bytes[9] == 's') - return ImageFormat.HeifSequence; + if (bytes[11] == 'c' || + bytes[11] == 'x' || + bytes[11] == 'm' || + bytes[11] == 's') + { + return ImageFormat.HeicSequence; + } } + } - if (bytes[8] == 'h' && bytes[9] == 'e') - { - if (bytes[10] == 'i') - { - if (bytes[11] == 'c' || - bytes[11] == 'x' || - bytes[11] == 'm' || - bytes[11] == 's') - return ImageFormat.Heic; - } + if (bytes[8] == 'q' && bytes[9] == 't') + { - if (bytes[10] == 'v') - { - if (bytes[11] == 'c' || - bytes[11] == 'x' || - bytes[11] == 'm' || - bytes[11] == 's') - return ImageFormat.HeicSequence; - } + return ImageFormat.QuickTimeMovie; + } + if (bytes[8] == 'M' && bytes[9] == '4') + { + if (bytes[9] == 'V') + { + return ImageFormat.M4V; } - if (bytes[8] == 'q' && bytes[9] == 't') + if (bytes[9] == 'P') { - - return ImageFormat.QuickTimeMovie; + return ImageFormat.M4P; } - if (bytes[8] == 'M' && bytes[9] == '4') + if (bytes[9] == 'B') { - if (bytes[9] == 'V') - { - return ImageFormat.M4V; - } - - if (bytes[9] == 'P') - { - return ImageFormat.M4P; - } - - if (bytes[9] == 'B') - { - return ImageFormat.M4B; - } - - if (bytes[9] == 'A') - { - return ImageFormat.M4A; - } + return ImageFormat.M4B; } - if (bytes[8] == 'F' && bytes[9] == '4') + if (bytes[9] == 'A') { - //These are adobe flash video/audio formats, meh.. - if (bytes[9] == 'V') - { - return ImageFormat.F4V; - } - - if (bytes[9] == 'P') - { - return ImageFormat.F4P; - } - - if (bytes[9] == 'B') - { - return ImageFormat.F4B; - } - - if (bytes[9] == 'A') - { - return ImageFormat.F4A; - } + return ImageFormat.M4A; } + } - if (bytes[8] == 'm' && bytes[9] == 'm' && bytes[10] == 'p' && bytes[11] == '4') + if (bytes[8] == 'F' && bytes[9] == '4') + { + //These are adobe flash video/audio formats, meh.. + if (bytes[9] == 'V') { - return ImageFormat.MP4; + return ImageFormat.F4V; } - if (bytes[8] == 'i' && bytes[9] == 's' && bytes[10] == 'o' && bytes[11] == 'm') + + if (bytes[9] == 'P') { - return ImageFormat.MP4; + return ImageFormat.F4P; } - if (bytes[8] == 'a' && bytes[9] == 'v' && bytes[10] == 'c' && bytes[11] == 'l') + if (bytes[9] == 'B') { - return ImageFormat.ThreeGPP; + return ImageFormat.F4B; } - if (bytes[8] == '3' && bytes[9] == 'g') + if (bytes[9] == 'A') { - return ImageFormat.ThreeGPP; + return ImageFormat.F4A; } } - //TODO: Add 3GP -> https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers#3gp + if (bytes[8] == 'm' && bytes[9] == 'm' && bytes[10] == 'p' && bytes[11] == '4') + { + return ImageFormat.MP4; + } + if (bytes[8] == 'i' && bytes[9] == 's' && bytes[10] == 'o' && bytes[11] == 'm') + { + return ImageFormat.MP4; + } - return null; - } + if (bytes[8] == 'a' && bytes[9] == 'v' && bytes[10] == 'c' && bytes[11] == 'l') + { + return ImageFormat.ThreeGPP; + } - [Obsolete("Use new Imazen.Common.FileTypeDetection.FileTypeDetector().GuessMimeType(data) instead")] - internal static string? GetImageContentType(byte[] first12Bytes) - { - switch (GetImageFormat(first12Bytes)) + if (bytes[8] == '3' && bytes[9] == 'g') { - case ImageFormat.Jpeg: - return "image/jpeg"; - case ImageFormat.Gif: - return "image/gif"; - case ImageFormat.Png: - return "image/png"; - case ImageFormat.WebP: - return "image/webp"; - case ImageFormat.Tiff: - return "image/tiff"; - case ImageFormat.Bitmap: - return "image/bmp"; - case ImageFormat.Avif: - return "image/avif"; - case ImageFormat.AvifSequence: - return "image/avif-sequence"; - case ImageFormat.Heif: - return "image/heif"; - case ImageFormat.HeifSequence: - return "image/heif-sequence"; - case ImageFormat.Heic: - return "image/heic"; - case ImageFormat.HeicSequence: - return "image/heic-sequence"; - case ImageFormat.Ico: - return "image/x-icon"; - case ImageFormat.Woff: - return "font/woff"; - case ImageFormat.Woff2: - return "font/woff2"; - case ImageFormat.OpenTypeFont: - return "font/otf"; - case ImageFormat.PostScript: - return "application/postscript"; - case ImageFormat.Pdf: - return "application/pdf"; - case ImageFormat.TrueTypeFont: - return "font/ttf"; - case ImageFormat.JpegXL: - return "image/jxl"; - case ImageFormat.Jpeg2000: - return "image/jp2"; - case ImageFormat.MP4: - return "video/mp4"; //or application/mp4 - case ImageFormat.OggContainer: - return "audio/ogg"; //There are more specific options for video/audio but we can't differentiate - case ImageFormat.WaveformAudio: - return "audio/wav"; - case ImageFormat.AudioVideoInterleave: - return "video/x-msvideo"; - case ImageFormat.Mp3: - return "audio/mpeg"; - case ImageFormat.QuickTimeMovie: - return "video/quicktime"; - - case ImageFormat.FLIF: - return "image/flif"; - case ImageFormat.AIFF: - return "audio/aiff"; - case ImageFormat.Flac: - return "audio/flac"; - case ImageFormat.Mpeg1: - return "video/mpeg"; // could be "audio/mpeg", we can't know - case ImageFormat.Mpeg2: - return "video/mpeg"; // could be "audio/mpeg", we can't know - case ImageFormat.MatroskaOrWebM: - return "video/webm"; //Chrome makes the presumption that matroska files are WebM, even if untrua - case ImageFormat.M4P: - return "audio/mp4"; - case ImageFormat.M4B: - return "audio/mp4"; - case ImageFormat.M4V: - return "video/mp4"; - case ImageFormat.M4A: - return "audio/mp4"; - case ImageFormat.F4V: - return "video/mp4"; - case ImageFormat.F4P: - return "audio/mp4"; - case ImageFormat.F4B: - return "audio/mp4"; - case ImageFormat.F4A: - return "audio/mp4"; - case ImageFormat.Aac: - return "audio/aac"; - case ImageFormat.ThreeGPP: - return "video/3gpp"; - case null: - break; - - - default: - throw new ArgumentOutOfRangeException(); + return ImageFormat.ThreeGPP; } - return null; } - /// - /// Returns true if Imageflow can likely decode the image based on the given file header - /// - /// - /// - /// - [Obsolete("Bad idea: imageflow may eventually support formats via OS codecs, so this is not predictable")] - internal static bool IsDecodable(byte[] first12Bytes) + //TODO: Add 3GP -> https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers#3gp + + return null; + } + + [Obsolete("Use new Imazen.Common.FileTypeDetection.FileTypeDetector().GuessMimeType(data) instead")] + internal static string? GetImageContentType(byte[] first12Bytes) + { + switch (GetImageFormat(first12Bytes)) { - switch (GetImageFormat(first12Bytes)) - { - case ImageFormat.Jpeg: - case ImageFormat.Gif: - case ImageFormat.Png: - case ImageFormat.WebP: - return true; - case null: - return false; - default: - throw new NotImplementedException(); - } + case ImageFormat.Jpeg: + return "image/jpeg"; + case ImageFormat.Gif: + return "image/gif"; + case ImageFormat.Png: + return "image/png"; + case ImageFormat.WebP: + return "image/webp"; + case ImageFormat.Tiff: + return "image/tiff"; + case ImageFormat.Bitmap: + return "image/bmp"; + case ImageFormat.Avif: + return "image/avif"; + case ImageFormat.AvifSequence: + return "image/avif-sequence"; + case ImageFormat.Heif: + return "image/heif"; + case ImageFormat.HeifSequence: + return "image/heif-sequence"; + case ImageFormat.Heic: + return "image/heic"; + case ImageFormat.HeicSequence: + return "image/heic-sequence"; + case ImageFormat.Ico: + return "image/x-icon"; + case ImageFormat.Woff: + return "font/woff"; + case ImageFormat.Woff2: + return "font/woff2"; + case ImageFormat.OpenTypeFont: + return "font/otf"; + case ImageFormat.PostScript: + return "application/postscript"; + case ImageFormat.Pdf: + return "application/pdf"; + case ImageFormat.TrueTypeFont: + return "font/ttf"; + case ImageFormat.JpegXL: + return "image/jxl"; + case ImageFormat.Jpeg2000: + return "image/jp2"; + case ImageFormat.MP4: + return "video/mp4"; //or application/mp4 + case ImageFormat.OggContainer: + return "audio/ogg"; //There are more specific options for video/audio but we can't differentiate + case ImageFormat.WaveformAudio: + return "audio/wav"; + case ImageFormat.AudioVideoInterleave: + return "video/x-msvideo"; + case ImageFormat.Mp3: + return "audio/mpeg"; + case ImageFormat.QuickTimeMovie: + return "video/quicktime"; + + case ImageFormat.FLIF: + return "image/flif"; + case ImageFormat.AIFF: + return "audio/aiff"; + case ImageFormat.Flac: + return "audio/flac"; + case ImageFormat.Mpeg1: + return "video/mpeg"; // could be "audio/mpeg", we can't know + case ImageFormat.Mpeg2: + return "video/mpeg"; // could be "audio/mpeg", we can't know + case ImageFormat.MatroskaOrWebM: + return "video/webm"; //Chrome makes the presumption that matroska files are WebM, even if untrua + case ImageFormat.M4P: + return "audio/mp4"; + case ImageFormat.M4B: + return "audio/mp4"; + case ImageFormat.M4V: + return "video/mp4"; + case ImageFormat.M4A: + return "audio/mp4"; + case ImageFormat.F4V: + return "video/mp4"; + case ImageFormat.F4P: + return "audio/mp4"; + case ImageFormat.F4B: + return "audio/mp4"; + case ImageFormat.F4A: + return "audio/mp4"; + case ImageFormat.Aac: + return "audio/aac"; + case ImageFormat.ThreeGPP: + return "video/3gpp"; + case null: + break; + + default: + throw new ArgumentOutOfRangeException(); + } + return null; + } + + /// + /// Returns true if Imageflow can likely decode the image based on the given file header + /// + /// + /// + /// + [Obsolete("Bad idea: imageflow may eventually support formats via OS codecs, so this is not predictable")] + internal static bool IsDecodable(byte[] first12Bytes) + { + switch (GetImageFormat(first12Bytes)) + { + case ImageFormat.Jpeg: + case ImageFormat.Gif: + case ImageFormat.Png: + case ImageFormat.WebP: + return true; + case null: + return false; + default: + throw new NotImplementedException(); } } } diff --git a/src/Imageflow/Fluent/MemorySource.cs b/src/Imageflow/Fluent/MemorySource.cs index a14d98f..09c82e1 100644 --- a/src/Imageflow/Fluent/MemorySource.cs +++ b/src/Imageflow/Fluent/MemorySource.cs @@ -21,7 +21,6 @@ public static IAsyncMemorySource TakeOwnership(IMemoryOwner ownedMemory, M return new MemorySource(null, ownedMemory, promise); } - private MemorySource(ReadOnlyMemory? borrowedMemory, IMemoryOwner? ownedMemory, MemoryLifetimePromise promise) { @@ -59,7 +58,6 @@ internal MemorySource(ArraySegment bytes) _borrowedMemory = new ReadOnlyMemory(bytes.Array, bytes.Offset, bytes.Count); } - public static IAsyncMemorySource Borrow(ReadOnlyMemory borrowedMemory, MemoryLifetimePromise promise) { if (promise == MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource) diff --git a/src/Imageflow/Fluent/PerformanceDetails.cs b/src/Imageflow/Fluent/PerformanceDetails.cs index 085fe8f..24f2825 100644 --- a/src/Imageflow/Fluent/PerformanceDetails.cs +++ b/src/Imageflow/Fluent/PerformanceDetails.cs @@ -1,51 +1,53 @@ using System.Text; using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public class PerformanceDetails { - public class PerformanceDetails + internal PerformanceDetails(JsonNode? perf) { - internal PerformanceDetails(JsonNode? perf) + var obj = perf?.AsObject(); + if (obj == null) { - var obj = perf?.AsObject(); - if (obj == null) return; - - // foreach (var f in perf.frames) - // { - // frames.Add(new PerformanceDetailsFrame(f)); - // } - if (obj.TryGetPropertyValue("frames", out var framesValue)) - { - foreach (var f in framesValue?.AsArray() ?? []) - { - _frames.Add(new PerformanceDetailsFrame(f)); - } - } + return; } - private readonly List _frames = new List(); - public ICollection Frames => _frames; - public string GetFirstFrameSummary() + // foreach (var f in perf.frames) + // { + // frames.Add(new PerformanceDetailsFrame(f)); + // } + if (obj.TryGetPropertyValue("frames", out var framesValue)) { - var sb = new StringBuilder(); - if (Frames.Count > 1) + foreach (var f in framesValue?.AsArray() ?? []) { - sb.Append($"First of {Frames.Count} frames: "); - } - else if (Frames.Count == 0) - { - sb.Append("No frames found"); + _frames.Add(new PerformanceDetailsFrame(f)); } + } + } + private readonly List _frames = new List(); + public ICollection Frames => _frames; - foreach (var n in Frames.First().Nodes) - { - sb.Append(n.Name); - sb.Append("("); - sb.Append((n.WallMicroseconds / 1000.0).ToString("0.####")); - sb.Append("ms) "); - } + public string GetFirstFrameSummary() + { + var sb = new StringBuilder(); + if (Frames.Count > 1) + { + sb.Append($"First of {Frames.Count} frames: "); + } + else if (Frames.Count == 0) + { + sb.Append("No frames found"); + } - return sb.ToString(); + foreach (var n in Frames.First().Nodes) + { + sb.Append(n.Name); + sb.Append("("); + sb.Append((n.WallMicroseconds / 1000.0).ToString("0.####")); + sb.Append("ms) "); } + + return sb.ToString(); } } diff --git a/src/Imageflow/Fluent/PerformanceDetailsFrame.cs b/src/Imageflow/Fluent/PerformanceDetailsFrame.cs index 448b76b..241f781 100644 --- a/src/Imageflow/Fluent/PerformanceDetailsFrame.cs +++ b/src/Imageflow/Fluent/PerformanceDetailsFrame.cs @@ -2,45 +2,52 @@ using Imageflow.Bindings; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public class PerformanceDetailsFrame { - public class PerformanceDetailsFrame + internal PerformanceDetailsFrame(JsonNode? frame) { - internal PerformanceDetailsFrame(JsonNode? frame) + if (frame == null) { - if (frame == null) return; - var obj = frame.AsObject(); - // foreach (var n in frame.nodes) - // { - // _nodes.Add(new PerformanceDetailsNode() - // { - // Name = n.name, - // WallMicroseconds = n.wall_microseconds - // }); - // } - if (obj.TryGetPropertyValue("nodes", out var nodesValue)) + return; + } + + var obj = frame.AsObject(); + // foreach (var n in frame.nodes) + // { + // _nodes.Add(new PerformanceDetailsNode() + // { + // Name = n.name, + // WallMicroseconds = n.wall_microseconds + // }); + // } + if (obj.TryGetPropertyValue("nodes", out var nodesValue)) + { + foreach (var n in nodesValue?.AsArray() ?? []) { - foreach (var n in nodesValue?.AsArray() ?? []) + if (n == null) { - if (n == null) continue; - var name = n.AsObject().TryGetPropertyValue("name", out var nameValue) - ? nameValue?.GetValue() - : throw new ImageflowAssertionFailed("PerformanceDetailsFrame node name is null"); - - var microseconds = n.AsObject().TryGetPropertyValue("wall_microseconds", out var microsecondsValue) - ? microsecondsValue?.GetValue() - : throw new ImageflowAssertionFailed("PerformanceDetailsFrame node wall_microseconds is null"); - _nodes.Add(new PerformanceDetailsNode() - { - Name = name!, - WallMicroseconds = microseconds!.Value - }); + continue; } + + var name = n.AsObject().TryGetPropertyValue("name", out var nameValue) + ? nameValue?.GetValue() + : throw new ImageflowAssertionFailed("PerformanceDetailsFrame node name is null"); + + var microseconds = n.AsObject().TryGetPropertyValue("wall_microseconds", out var microsecondsValue) + ? microsecondsValue?.GetValue() + : throw new ImageflowAssertionFailed("PerformanceDetailsFrame node wall_microseconds is null"); + _nodes.Add(new PerformanceDetailsNode() + { + Name = name!, + WallMicroseconds = microseconds!.Value + }); } } + } - private readonly List _nodes = new List(); + private readonly List _nodes = new List(); - public ICollection Nodes => _nodes; - } + public ICollection Nodes => _nodes; } diff --git a/src/Imageflow/Fluent/PerformanceDetailsNode.cs b/src/Imageflow/Fluent/PerformanceDetailsNode.cs index 7c3a07b..98c8244 100644 --- a/src/Imageflow/Fluent/PerformanceDetailsNode.cs +++ b/src/Imageflow/Fluent/PerformanceDetailsNode.cs @@ -1,9 +1,8 @@ -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public record struct PerformanceDetailsNode { - public record struct PerformanceDetailsNode - { - public string Name { get; internal init; } - public long WallMicroseconds { get; internal init; } - } + public string Name { get; internal init; } + public long WallMicroseconds { get; internal init; } } diff --git a/src/Imageflow/Fluent/ResampleHints.cs b/src/Imageflow/Fluent/ResampleHints.cs index 4adaeb4..1e2b2e1 100644 --- a/src/Imageflow/Fluent/ResampleHints.cs +++ b/src/Imageflow/Fluent/ResampleHints.cs @@ -1,133 +1,153 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public class ResampleHints { - public class ResampleHints + public SharpenWhen? SharpenWhen { get; set; } + public ResampleWhen? ResampleWhen { get; set; } + + public ScalingFloatspace? InterpolationColorspace { get; set; } + public InterpolationFilter? UpFilter { get; set; } + public InterpolationFilter? DownFilter { get; set; } + public float? SharpenPercent { get; set; } + + public ResampleHints(float? sharpenPercent, SharpenWhen? sharpenWhen, ResampleWhen? resampleWhen, InterpolationFilter? downFilter, InterpolationFilter? upFilter, ScalingFloatspace? interpolationColorspace) { - public SharpenWhen? SharpenWhen { get; set; } - public ResampleWhen? ResampleWhen { get; set; } + SharpenPercent = sharpenPercent; + DownFilter = downFilter; + UpFilter = upFilter; + InterpolationColorspace = interpolationColorspace; + ResampleWhen = resampleWhen; + SharpenWhen = sharpenWhen; + } - public ScalingFloatspace? InterpolationColorspace { get; set; } - public InterpolationFilter? UpFilter { get; set; } - public InterpolationFilter? DownFilter { get; set; } - public float? SharpenPercent { get; set; } + public ResampleHints() + { - public ResampleHints(float? sharpenPercent, SharpenWhen? sharpenWhen, ResampleWhen? resampleWhen, InterpolationFilter? downFilter, InterpolationFilter? upFilter, ScalingFloatspace? interpolationColorspace) - { - SharpenPercent = sharpenPercent; - DownFilter = downFilter; - UpFilter = upFilter; - InterpolationColorspace = interpolationColorspace; - ResampleWhen = resampleWhen; - SharpenWhen = sharpenWhen; - } + } - public ResampleHints() - { + [Obsolete("Use SetSharpen instead")] + public ResampleHints Sharpen(float? sharpenPercent, SharpenWhen? sharpenWhen) + => SetSharpen(sharpenPercent, sharpenWhen); + public ResampleHints SetSharpen(float? sharpenPercent, SharpenWhen? sharpenWhen) + { + SharpenPercent = sharpenPercent; + SharpenWhen = sharpenWhen; + return this; + } - } + public ResampleHints SetSharpenPercent(float? sharpenPercent) + { + SharpenPercent = sharpenPercent; + return this; + } + public ResampleHints SetSharpenWhen(SharpenWhen? sharpenWhen) + { + SharpenWhen = sharpenWhen; + return this; + } + [Obsolete("Use SetResampleFilters instead")] + public ResampleHints ResampleFilter(InterpolationFilter? downFilter, InterpolationFilter? upFilter) + { + DownFilter = downFilter; + UpFilter = upFilter; + return this; + } - [Obsolete("Use SetSharpen instead")] - public ResampleHints Sharpen(float? sharpenPercent, SharpenWhen? sharpenWhen) - => SetSharpen(sharpenPercent, sharpenWhen); - public ResampleHints SetSharpen(float? sharpenPercent, SharpenWhen? sharpenWhen) - { - SharpenPercent = sharpenPercent; - SharpenWhen = sharpenWhen; - return this; - } + public ResampleHints SetResampleFilters(InterpolationFilter? downFilter, InterpolationFilter? upFilter) + { + DownFilter = downFilter; + UpFilter = upFilter; + return this; + } - public ResampleHints SetSharpenPercent(float? sharpenPercent) - { - SharpenPercent = sharpenPercent; - return this; - } - public ResampleHints SetSharpenWhen(SharpenWhen? sharpenWhen) - { - SharpenWhen = sharpenWhen; - return this; - } + public ResampleHints SetUpSamplingFilter(InterpolationFilter? upFilter) + { + UpFilter = upFilter; + return this; + } + public ResampleHints SetDownSamplingFilter(InterpolationFilter? downFilter) + { + DownFilter = downFilter; + return this; + } - [Obsolete("Use SetResampleFilters instead")] - public ResampleHints ResampleFilter(InterpolationFilter? downFilter, InterpolationFilter? upFilter) - { - DownFilter = downFilter; - UpFilter = upFilter; - return this; - } + [Obsolete("Use SetResampleWhen instead")] + public ResampleHints Resample(ResampleWhen? resampleWhen) + { - public ResampleHints SetResampleFilters(InterpolationFilter? downFilter, InterpolationFilter? upFilter) - { - DownFilter = downFilter; - UpFilter = upFilter; - return this; - } + ResampleWhen = resampleWhen; + return this; + } + public ResampleHints SetResampleWhen(ResampleWhen? resampleWhen) + { - public ResampleHints SetUpSamplingFilter(InterpolationFilter? upFilter) - { - UpFilter = upFilter; - return this; - } - public ResampleHints SetDownSamplingFilter(InterpolationFilter? downFilter) - { - DownFilter = downFilter; - return this; - } + ResampleWhen = resampleWhen; + return this; + } - [Obsolete("Use SetResampleWhen instead")] - public ResampleHints Resample(ResampleWhen? resampleWhen) - { + [Obsolete("Use SetInterpolationColorspace instead")] + public ResampleHints ResampleColorspace(ScalingFloatspace? interpolationColorspace) + { + InterpolationColorspace = interpolationColorspace; + return this; + } - ResampleWhen = resampleWhen; - return this; - } - public ResampleHints SetResampleWhen(ResampleWhen? resampleWhen) + public ResampleHints SetInterpolationColorspace(ScalingFloatspace? interpolationColorspace) + { + InterpolationColorspace = interpolationColorspace; + return this; + } + + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() + { + return new { + sharpen_percent = SharpenPercent, + down_filter = DownFilter?.ToString().ToLowerInvariant(), + up_filter = UpFilter?.ToString().ToLowerInvariant(), + scaling_colorspace = InterpolationColorspace?.ToString().ToLowerInvariant(), + resample_when = ResampleWhen?.ToString().ToLowerInvariant(), + sharpen_when = SharpenWhen?.ToString().ToLowerInvariant() + }; + } - ResampleWhen = resampleWhen; - return this; + public JsonNode ToJsonNode() + { + var obj = new JsonObject(); + if (SharpenPercent != null) + { + obj.Add("sharpen_percent", SharpenPercent); } - [Obsolete("Use SetInterpolationColorspace instead")] - public ResampleHints ResampleColorspace(ScalingFloatspace? interpolationColorspace) + if (DownFilter != null) { - InterpolationColorspace = interpolationColorspace; - return this; + obj.Add("down_filter", DownFilter?.ToString().ToLowerInvariant()); } - public ResampleHints SetInterpolationColorspace(ScalingFloatspace? interpolationColorspace) + if (UpFilter != null) { - InterpolationColorspace = interpolationColorspace; - return this; + obj.Add("up_filter", UpFilter?.ToString().ToLowerInvariant()); } + if (InterpolationColorspace != null) + { + obj.Add("scaling_colorspace", InterpolationColorspace?.ToString().ToLowerInvariant()); + } - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() + if (ResampleWhen != null) { - return new - { - sharpen_percent = SharpenPercent, - down_filter = DownFilter?.ToString().ToLowerInvariant(), - up_filter = UpFilter?.ToString().ToLowerInvariant(), - scaling_colorspace = InterpolationColorspace?.ToString().ToLowerInvariant(), - resample_when = ResampleWhen?.ToString().ToLowerInvariant(), - sharpen_when = SharpenWhen?.ToString().ToLowerInvariant() - }; + obj.Add("resample_when", ResampleWhen?.ToString().ToLowerInvariant()); } - public JsonNode ToJsonNode() + if (SharpenWhen != null) { - var obj = new JsonObject(); - if (SharpenPercent != null) obj.Add("sharpen_percent", SharpenPercent); - if (DownFilter != null) obj.Add("down_filter", DownFilter?.ToString().ToLowerInvariant()); - if (UpFilter != null) obj.Add("up_filter", UpFilter?.ToString().ToLowerInvariant()); - if (InterpolationColorspace != null) obj.Add("scaling_colorspace", InterpolationColorspace?.ToString().ToLowerInvariant()); - if (ResampleWhen != null) obj.Add("resample_when", ResampleWhen?.ToString().ToLowerInvariant()); - if (SharpenWhen != null) obj.Add("sharpen_when", SharpenWhen?.ToString().ToLowerInvariant()); - return obj; + obj.Add("sharpen_when", SharpenWhen?.ToString().ToLowerInvariant()); } - } + return obj; + } } diff --git a/src/Imageflow/Fluent/SecurityOptions.cs b/src/Imageflow/Fluent/SecurityOptions.cs index 07ede61..c9aeb15 100644 --- a/src/Imageflow/Fluent/SecurityOptions.cs +++ b/src/Imageflow/Fluent/SecurityOptions.cs @@ -1,50 +1,61 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public class SecurityOptions { - public class SecurityOptions - { - public FrameSizeLimit? MaxDecodeSize { get; set; } + public FrameSizeLimit? MaxDecodeSize { get; set; } - public FrameSizeLimit? MaxFrameSize { get; set; } + public FrameSizeLimit? MaxFrameSize { get; set; } - public FrameSizeLimit? MaxEncodeSize { get; set; } + public FrameSizeLimit? MaxEncodeSize { get; set; } - public SecurityOptions SetMaxDecodeSize(FrameSizeLimit? limit) - { - MaxDecodeSize = limit; - return this; - } - public SecurityOptions SetMaxFrameSize(FrameSizeLimit? limit) + public SecurityOptions SetMaxDecodeSize(FrameSizeLimit? limit) + { + MaxDecodeSize = limit; + return this; + } + public SecurityOptions SetMaxFrameSize(FrameSizeLimit? limit) + { + MaxFrameSize = limit; + return this; + } + public SecurityOptions SetMaxEncodeSize(FrameSizeLimit? limit) + { + MaxEncodeSize = limit; + return this; + } + + [Obsolete("Use ToJsonNode() instead")] + internal object ToImageflowDynamic() + { + return new { - MaxFrameSize = limit; - return this; - } - public SecurityOptions SetMaxEncodeSize(FrameSizeLimit? limit) + max_decode_size = MaxDecodeSize?.ToImageflowDynamic(), + max_frame_size = MaxFrameSize?.ToImageflowDynamic(), + max_encode_size = MaxEncodeSize?.ToImageflowDynamic() + }; + } + + internal JsonNode ToJsonNode() + { + var node = new JsonObject(); + if (MaxDecodeSize != null) { - MaxEncodeSize = limit; - return this; + node.Add("max_decode_size", MaxDecodeSize?.ToJsonNode()); } - [Obsolete("Use ToJsonNode() instead")] - internal object ToImageflowDynamic() + if (MaxFrameSize != null) { - return new - { - max_decode_size = MaxDecodeSize?.ToImageflowDynamic(), - max_frame_size = MaxFrameSize?.ToImageflowDynamic(), - max_encode_size = MaxEncodeSize?.ToImageflowDynamic() - }; + node.Add("max_frame_size", MaxFrameSize?.ToJsonNode()); } - internal JsonNode ToJsonNode() + if (MaxEncodeSize != null) { - var node = new JsonObject(); - if (MaxDecodeSize != null) node.Add("max_decode_size", MaxDecodeSize?.ToJsonNode()); - if (MaxFrameSize != null) node.Add("max_frame_size", MaxFrameSize?.ToJsonNode()); - if (MaxEncodeSize != null) node.Add("max_encode_size", MaxEncodeSize?.ToJsonNode()); - return node; + node.Add("max_encode_size", MaxEncodeSize?.ToJsonNode()); } + + return node; } } diff --git a/src/Imageflow/Fluent/SrgbColor.cs b/src/Imageflow/Fluent/SrgbColor.cs index 260b54e..e0719f0 100644 --- a/src/Imageflow/Fluent/SrgbColor.cs +++ b/src/Imageflow/Fluent/SrgbColor.cs @@ -2,65 +2,71 @@ using Imageflow.Bindings; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +/// +/// Represents a color in the sRGB colorspace. Default value: transparent black. +/// +public readonly struct SrgbColor(byte r, byte g, byte b, byte a) { - /// - /// Represents a color in the sRGB colorspace. Default value: transparent black. - /// - public readonly struct SrgbColor(byte r, byte g, byte b, byte a) - { - public byte A { get; private init; } = a; - public byte R { get; private init; } = r; - public byte G { get; private init; } = g; - public byte B { get; private init; } = b; + public byte A { get; private init; } = a; + public byte R { get; private init; } = r; + public byte G { get; private init; } = g; + public byte B { get; private init; } = b; - private static byte Mask8(uint v, int index) + private static byte Mask8(uint v, int index) + { + var shift = index * 8; + var mask = 0xff << shift; + var result = (v & mask) >> shift; + if (result > 255) { - var shift = index * 8; - var mask = 0xff << shift; - var result = (v & mask) >> shift; - if (result > 255) throw new ImageflowAssertionFailed("Integer overflow in color parsing"); - return (byte)result; + throw new ImageflowAssertionFailed("Integer overflow in color parsing"); } - private static byte Expand4(uint v, int index) + + return (byte)result; + } + private static byte Expand4(uint v, int index) + { + var shift = index * 4; + var mask = 0xf << shift; + var result = (v & mask) >> shift; + result = result | result << 4; // Duplicate lower 4 bits into upper + if (result > 255) { - var shift = index * 4; - var mask = 0xf << shift; - var result = (v & mask) >> shift; - result = result | result << 4; // Duplicate lower 4 bits into upper - if (result > 255) throw new ImageflowAssertionFailed("Integer overflow in color parsing"); - return (byte)result; + throw new ImageflowAssertionFailed("Integer overflow in color parsing"); } - /// - /// Parses a hexadecimal color in the form RGB, RGBA, RRGGBB, or RRGGBBAA - /// - /// - /// - /// - public static SrgbColor FromHex(string s) + return (byte)result; + } + + /// + /// Parses a hexadecimal color in the form RGB, RGBA, RRGGBB, or RRGGBBAA + /// + /// + /// + /// + public static SrgbColor FromHex(string s) + { + s = s.TrimStart('#'); + var v = uint.Parse(s, NumberStyles.HexNumber); + switch (s.Length) { - s = s.TrimStart('#'); - var v = uint.Parse(s, NumberStyles.HexNumber); - switch (s.Length) - { - case 3: return RGBA(Expand4(v, 2), Expand4(v, 1), Expand4(v, 0), 0xff); - case 6: return RGBA(Mask8(v, 2), Mask8(v, 1), Mask8(v, 0), 0xff); - case 4: return RGBA(Expand4(v, 3), Expand4(v, 2), Expand4(v, 1), Expand4(v, 0)); - case 8: return RGBA(Mask8(v, 3), Mask8(v, 2), Mask8(v, 1), Mask8(v, 0)); - default: throw new ImageflowAssertionFailed("TODO: invalid hex color"); - } + case 3: return RGBA(Expand4(v, 2), Expand4(v, 1), Expand4(v, 0), 0xff); + case 6: return RGBA(Mask8(v, 2), Mask8(v, 1), Mask8(v, 0), 0xff); + case 4: return RGBA(Expand4(v, 3), Expand4(v, 2), Expand4(v, 1), Expand4(v, 0)); + case 8: return RGBA(Mask8(v, 3), Mask8(v, 2), Mask8(v, 1), Mask8(v, 0)); + default: throw new ImageflowAssertionFailed("TODO: invalid hex color"); } + } - public string ToHexUnprefixed() => A == 0xff ? $"{R:x2}{G:x2}{B:x2}" : $"{R:x2}{G:x2}{B:x2}{A:x2}"; - - public static SrgbColor BGRA(byte b, byte g, byte r, byte a) => - new SrgbColor() { A = a, R = r, G = g, B = b }; - public static SrgbColor RGBA(byte r, byte g, byte b, byte a) => - new SrgbColor() { A = a, R = r, G = g, B = b }; - public static SrgbColor RGB(byte r, byte g, byte b) => - new SrgbColor() { A = 255, R = r, G = g, B = b }; + public string ToHexUnprefixed() => A == 0xff ? $"{R:x2}{G:x2}{B:x2}" : $"{R:x2}{G:x2}{B:x2}{A:x2}"; + public static SrgbColor BGRA(byte b, byte g, byte r, byte a) => + new SrgbColor() { A = a, R = r, G = g, B = b }; + public static SrgbColor RGBA(byte r, byte g, byte b, byte a) => + new SrgbColor() { A = a, R = r, G = g, B = b }; + public static SrgbColor RGB(byte r, byte g, byte b) => + new SrgbColor() { A = 255, R = r, G = g, B = b }; - } } diff --git a/src/Imageflow/Fluent/StreamDestination.cs b/src/Imageflow/Fluent/StreamDestination.cs index 763f398..db5d4a9 100644 --- a/src/Imageflow/Fluent/StreamDestination.cs +++ b/src/Imageflow/Fluent/StreamDestination.cs @@ -7,18 +7,29 @@ public class StreamDestination(Stream underlying, bool disposeUnderlying) : IOut { public void Dispose() { - if (disposeUnderlying) underlying?.Dispose(); + if (disposeUnderlying) + { + underlying?.Dispose(); + } } public Task RequestCapacityAsync(int bytes) { - if (underlying is { CanSeek: true, CanWrite: true }) underlying.SetLength(bytes); + if (underlying is { CanSeek: true, CanWrite: true }) + { + underlying.SetLength(bytes); + } + return Task.CompletedTask; } public Task WriteAsync(ArraySegment bytes, CancellationToken cancellationToken) { - if (bytes.Array == null) throw new ImageflowAssertionFailed("StreamDestination.WriteAsync called with null array"); + if (bytes.Array == null) + { + throw new ImageflowAssertionFailed("StreamDestination.WriteAsync called with null array"); + } + return underlying.WriteAsync(bytes.Array, bytes.Offset, bytes.Count, cancellationToken); } @@ -36,7 +47,10 @@ public Task FlushAsync(CancellationToken cancellationToken) { if (underlying is { CanSeek: true, CanWrite: true } && underlying.Position < underlying.Length) + { underlying.SetLength(underlying.Position); + } + return underlying.FlushAsync(cancellationToken); } } diff --git a/src/Imageflow/Fluent/StreamSource.cs b/src/Imageflow/Fluent/StreamSource.cs index 15d23e3..5a19ce9 100644 --- a/src/Imageflow/Fluent/StreamSource.cs +++ b/src/Imageflow/Fluent/StreamSource.cs @@ -37,7 +37,10 @@ public async Task> GetBytesAsync(CancellationToken cancellati (int)_copy.Length); } var length = underlying.CanSeek ? underlying.Length : 0; - if (length >= int.MaxValue) throw new OverflowException("Streams cannot exceed 2GB"); + if (length >= int.MaxValue) + { + throw new OverflowException("Streams cannot exceed 2GB"); + } if (underlying is MemoryStream underlyingMemoryStream && underlyingMemoryStream.TryGetBufferSliceAllWrittenData(out var underlyingBuffer)) diff --git a/src/Imageflow/Fluent/WatermarkFitBox.cs b/src/Imageflow/Fluent/WatermarkFitBox.cs index f4616c9..c036a57 100644 --- a/src/Imageflow/Fluent/WatermarkFitBox.cs +++ b/src/Imageflow/Fluent/WatermarkFitBox.cs @@ -1,108 +1,107 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +/// +/// Defines +/// +public class WatermarkFitBox : IWatermarkConstraintBox { - /// - /// Defines - /// - public class WatermarkFitBox : IWatermarkConstraintBox - { - public WatermarkFitBox() { } + public WatermarkFitBox() { } - public WatermarkFitBox(WatermarkAlign relativeTo, float x1, float y1, float x2, float y2) - { - RelativeTo = relativeTo; - X1 = x1; - Y1 = y1; - X2 = x2; - Y2 = y2; - } - public WatermarkAlign RelativeTo { get; set; } = WatermarkAlign.Image; + public WatermarkFitBox(WatermarkAlign relativeTo, float x1, float y1, float x2, float y2) + { + RelativeTo = relativeTo; + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + } + public WatermarkAlign RelativeTo { get; set; } = WatermarkAlign.Image; - public float X1 { get; set; } - public float Y1 { get; set; } - public float X2 { get; set; } = 100; - public float Y2 { get; set; } = 100; + public float X1 { get; set; } + public float Y1 { get; set; } + public float X2 { get; set; } = 100; + public float Y2 { get; set; } = 100; - public WatermarkFitBox SetRelativeTo(WatermarkAlign relativeTo) - { - RelativeTo = relativeTo; - return this; - } + public WatermarkFitBox SetRelativeTo(WatermarkAlign relativeTo) + { + RelativeTo = relativeTo; + return this; + } - public WatermarkFitBox SetTopLeft(float x1, float y1) - { - X1 = x1; - Y1 = y1; - return this; - } - public WatermarkFitBox SetBottomRight(float x2, float y2) - { - X2 = x2; - Y2 = y2; - return this; - } - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() + public WatermarkFitBox SetTopLeft(float x1, float y1) + { + X1 = x1; + Y1 = y1; + return this; + } + public WatermarkFitBox SetBottomRight(float x2, float y2) + { + X2 = x2; + Y2 = y2; + return this; + } + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() + { + switch (RelativeTo) { - switch (RelativeTo) - { - case WatermarkAlign.Canvas: - return new + case WatermarkAlign.Canvas: + return new + { + canvas_percentage = new { - canvas_percentage = new - { - x1 = X1, - y1 = Y1, - x2 = X2, - y2 = Y2 - } + x1 = X1, + y1 = Y1, + x2 = X2, + y2 = Y2 + } - }; - case WatermarkAlign.Image: - return new + }; + case WatermarkAlign.Image: + return new + { + image_percentage = new { - image_percentage = new - { - x1 = X1, - y1 = Y1, - x2 = X2, - y2 = Y2 - } + x1 = X1, + y1 = Y1, + x2 = X2, + y2 = Y2 + } - }; - default: - throw new ArgumentOutOfRangeException(); - } + }; + default: + throw new ArgumentOutOfRangeException(); } + } - public JsonNode ToJsonNode() + public JsonNode ToJsonNode() + { + var node = new JsonObject(); + switch (RelativeTo) { - var node = new JsonObject(); - switch (RelativeTo) - { - case WatermarkAlign.Canvas: - node.Add("canvas_percentage", new JsonObject - { - {"x1", X1}, - {"y1", Y1}, - {"x2", X2}, - {"y2", Y2} - }); - break; - case WatermarkAlign.Image: - node.Add("image_percentage", new JsonObject - { - {"x1", X1}, - {"y1", Y1}, - {"x2", X2}, - {"y2", Y2} - }); - break; - default: - throw new ArgumentOutOfRangeException(); - } - return node; + case WatermarkAlign.Canvas: + node.Add("canvas_percentage", new JsonObject + { + {"x1", X1}, + {"y1", Y1}, + {"x2", X2}, + {"y2", Y2} + }); + break; + case WatermarkAlign.Image: + node.Add("image_percentage", new JsonObject + { + {"x1", X1}, + {"y1", Y1}, + {"x2", X2}, + {"y2", Y2} + }); + break; + default: + throw new ArgumentOutOfRangeException(); } + return node; } } diff --git a/src/Imageflow/Fluent/WatermarkMargins.cs b/src/Imageflow/Fluent/WatermarkMargins.cs index ee88cb7..2de9ee1 100644 --- a/src/Imageflow/Fluent/WatermarkMargins.cs +++ b/src/Imageflow/Fluent/WatermarkMargins.cs @@ -1,127 +1,126 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +/// +/// Defines a set of margins in terms of pixels inward from the edges of the canvas or image +/// +public class WatermarkMargins : IWatermarkConstraintBox { + public WatermarkMargins() + { + } + /// - /// Defines a set of margins in terms of pixels inward from the edges of the canvas or image + /// Apply margins in terms of pixels from the edge of the canvas or image /// - public class WatermarkMargins : IWatermarkConstraintBox + /// + /// + /// + /// + /// + public WatermarkMargins(WatermarkAlign relativeTo, uint left, uint top, uint right, uint bottom) { - public WatermarkMargins() - { - } - - /// - /// Apply margins in terms of pixels from the edge of the canvas or image - /// - /// - /// - /// - /// - /// - public WatermarkMargins(WatermarkAlign relativeTo, uint left, uint top, uint right, uint bottom) - { - RelativeTo = relativeTo; - Left = left; - Top = top; - Right = right; - Bottom = bottom; - } + RelativeTo = relativeTo; + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } - public WatermarkAlign RelativeTo { get; set; } = WatermarkAlign.Image; - public uint Left { get; set; } - public uint Top { get; set; } - public uint Right { get; set; } - public uint Bottom { get; set; } + public WatermarkAlign RelativeTo { get; set; } = WatermarkAlign.Image; + public uint Left { get; set; } + public uint Top { get; set; } + public uint Right { get; set; } + public uint Bottom { get; set; } - public WatermarkMargins SetRelativeTo(WatermarkAlign relativeTo) - { - RelativeTo = relativeTo; - return this; - } + public WatermarkMargins SetRelativeTo(WatermarkAlign relativeTo) + { + RelativeTo = relativeTo; + return this; + } - public WatermarkMargins SetLeft(uint pixels) - { - Left = pixels; - return this; - } - public WatermarkMargins SetTop(uint pixels) - { - Top = pixels; - return this; - } - public WatermarkMargins SetRight(uint pixels) - { - Right = pixels; - return this; - } - public WatermarkMargins SetBottom(uint pixels) - { - Bottom = pixels; - return this; - } + public WatermarkMargins SetLeft(uint pixels) + { + Left = pixels; + return this; + } + public WatermarkMargins SetTop(uint pixels) + { + Top = pixels; + return this; + } + public WatermarkMargins SetRight(uint pixels) + { + Right = pixels; + return this; + } + public WatermarkMargins SetBottom(uint pixels) + { + Bottom = pixels; + return this; + } - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic() + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic() + { + switch (RelativeTo) { - switch (RelativeTo) - { - case WatermarkAlign.Canvas: - return new + case WatermarkAlign.Canvas: + return new + { + canvas_margins = new { - canvas_margins = new - { - left = Left, - top = Top, - right = Right, - bottom = Bottom - } + left = Left, + top = Top, + right = Right, + bottom = Bottom + } - }; - case WatermarkAlign.Image: - return new + }; + case WatermarkAlign.Image: + return new + { + image_margins = new { - image_margins = new - { - left = Left, - top = Top, - right = Right, - bottom = Bottom - } + left = Left, + top = Top, + right = Right, + bottom = Bottom + } - }; - default: - throw new ArgumentOutOfRangeException(); - } + }; + default: + throw new ArgumentOutOfRangeException(); } + } - public JsonNode ToJsonNode() + public JsonNode ToJsonNode() + { + var node = new JsonObject(); + switch (RelativeTo) { - var node = new JsonObject(); - switch (RelativeTo) - { - case WatermarkAlign.Canvas: - node.Add("canvas_margins", new JsonObject - { - {"left", Left}, - {"top", Top}, - {"right", Right}, - {"bottom", Bottom} - }); - break; - case WatermarkAlign.Image: - node.Add("image_margins", new JsonObject - { - {"left", Left}, - {"top", Top}, - {"right", Right}, - {"bottom", Bottom} - }); - break; - default: - throw new ArgumentOutOfRangeException(); - } - return node; + case WatermarkAlign.Canvas: + node.Add("canvas_margins", new JsonObject + { + {"left", Left}, + {"top", Top}, + {"right", Right}, + {"bottom", Bottom} + }); + break; + case WatermarkAlign.Image: + node.Add("image_margins", new JsonObject + { + {"left", Left}, + {"top", Top}, + {"right", Right}, + {"bottom", Bottom} + }); + break; + default: + throw new ArgumentOutOfRangeException(); } + return node; } } diff --git a/src/Imageflow/Fluent/WatermarkOptions.cs b/src/Imageflow/Fluent/WatermarkOptions.cs index ee809f4..e04908f 100644 --- a/src/Imageflow/Fluent/WatermarkOptions.cs +++ b/src/Imageflow/Fluent/WatermarkOptions.cs @@ -1,197 +1,224 @@ using System.Text.Json.Nodes; -namespace Imageflow.Fluent +namespace Imageflow.Fluent; + +public class WatermarkOptions { - public class WatermarkOptions + + public ConstraintGravity? Gravity { get; set; } + public IWatermarkConstraintBox? FitBox { get; set; } + public WatermarkConstraintMode? FitMode { get; set; } + + /// + /// Range 0..1, where 1 is fully opaque and 0 is transparent. + /// + public float? Opacity { get; set; } + public ResampleHints? Hints { get; set; } + + public uint? MinCanvasWidth { get; set; } + public uint? MinCanvasHeight { get; set; } + + [Obsolete("Use Set___ methods instead")] + public WatermarkOptions WithHints(ResampleHints hints) { + Hints = hints; + return this; + } - public ConstraintGravity? Gravity { get; set; } - public IWatermarkConstraintBox? FitBox { get; set; } - public WatermarkConstraintMode? FitMode { get; set; } + /// + /// Set the opacity to a value between 0 and 1 + /// + /// + /// + [Obsolete("Use Set___ methods instead")] + public WatermarkOptions WithOpacity(float opacity) + { + Opacity = opacity; + return this; + } + [Obsolete("Use Set___ methods instead")] + public WatermarkOptions WithMargins(WatermarkMargins margins) + { + FitBox = margins; + return this; + } + [Obsolete("Use Set___ methods instead")] + public WatermarkOptions WithFitBox(WatermarkFitBox fitBox) + { + FitBox = fitBox; + return this; + } + [Obsolete("Use Set___ methods instead")] + public WatermarkOptions WithFitMode(WatermarkConstraintMode fitMode) + { + FitMode = fitMode; + return this; + } + [Obsolete("Use Set___ methods instead")] + public WatermarkOptions WithGravity(ConstraintGravity gravity) + { + Gravity = gravity; + return this; + } + [Obsolete("Use Set___ methods instead")] + public WatermarkOptions LayoutWithFitBox(WatermarkFitBox fitBox, WatermarkConstraintMode fitMode, + ConstraintGravity gravity) + { + FitBox = fitBox; + FitMode = fitMode; + Gravity = gravity; + return this; + } + [Obsolete("Use Set___ methods instead")] + public WatermarkOptions LayoutWithMargins(WatermarkMargins margins, WatermarkConstraintMode fitMode, + ConstraintGravity gravity) + { + FitBox = margins; + FitMode = fitMode; + Gravity = gravity; + return this; + } - /// - /// Range 0..1, where 1 is fully opaque and 0 is transparent. - /// - public float? Opacity { get; set; } - public ResampleHints? Hints { get; set; } + /// + /// Hide the watermark if the canvas is smaller in either dimension + /// + /// + /// + /// + [Obsolete("Use Set___ methods instead")] + public WatermarkOptions WithMinCanvasSize(uint? minWidth, uint? minHeight) + { + MinCanvasWidth = minWidth; + MinCanvasHeight = minHeight; + return this; + } - public uint? MinCanvasWidth { get; set; } - public uint? MinCanvasHeight { get; set; } + public WatermarkOptions SetHints(ResampleHints hints) + { + Hints = hints; + return this; + } - [Obsolete("Use Set___ methods instead")] - public WatermarkOptions WithHints(ResampleHints hints) - { - Hints = hints; - return this; - } + /// + /// Set the opacity to a value between 0 and 1 + /// + /// + /// + public WatermarkOptions SetOpacity(float opacity) + { + Opacity = opacity; + return this; + } - /// - /// Set the opacity to a value between 0 and 1 - /// - /// - /// - [Obsolete("Use Set___ methods instead")] - public WatermarkOptions WithOpacity(float opacity) - { - Opacity = opacity; - return this; - } - [Obsolete("Use Set___ methods instead")] - public WatermarkOptions WithMargins(WatermarkMargins margins) - { - FitBox = margins; - return this; - } - [Obsolete("Use Set___ methods instead")] - public WatermarkOptions WithFitBox(WatermarkFitBox fitBox) - { - FitBox = fitBox; - return this; - } - [Obsolete("Use Set___ methods instead")] - public WatermarkOptions WithFitMode(WatermarkConstraintMode fitMode) - { - FitMode = fitMode; - return this; - } - [Obsolete("Use Set___ methods instead")] - public WatermarkOptions WithGravity(ConstraintGravity gravity) - { - Gravity = gravity; - return this; - } - [Obsolete("Use Set___ methods instead")] - public WatermarkOptions LayoutWithFitBox(WatermarkFitBox fitBox, WatermarkConstraintMode fitMode, - ConstraintGravity gravity) - { - FitBox = fitBox; - FitMode = fitMode; - Gravity = gravity; - return this; - } - [Obsolete("Use Set___ methods instead")] - public WatermarkOptions LayoutWithMargins(WatermarkMargins margins, WatermarkConstraintMode fitMode, - ConstraintGravity gravity) - { - FitBox = margins; - FitMode = fitMode; - Gravity = gravity; - return this; - } + public WatermarkOptions SetMargins(WatermarkMargins margins) + { + FitBox = margins; + return this; + } - /// - /// Hide the watermark if the canvas is smaller in either dimension - /// - /// - /// - /// - [Obsolete("Use Set___ methods instead")] - public WatermarkOptions WithMinCanvasSize(uint? minWidth, uint? minHeight) - { - MinCanvasWidth = minWidth; - MinCanvasHeight = minHeight; - return this; - } + public WatermarkOptions SetFitBox(WatermarkFitBox fitBox) + { + FitBox = fitBox; + return this; + } - public WatermarkOptions SetHints(ResampleHints hints) - { - Hints = hints; - return this; - } + public WatermarkOptions SetFitMode(WatermarkConstraintMode fitMode) + { + FitMode = fitMode; + return this; + } - /// - /// Set the opacity to a value between 0 and 1 - /// - /// - /// - public WatermarkOptions SetOpacity(float opacity) - { - Opacity = opacity; - return this; - } + public WatermarkOptions SetGravity(ConstraintGravity gravity) + { + Gravity = gravity; + return this; + } - public WatermarkOptions SetMargins(WatermarkMargins margins) - { - FitBox = margins; - return this; - } + public WatermarkOptions SetFitBoxLayout(WatermarkFitBox fitBox, WatermarkConstraintMode fitMode, + ConstraintGravity gravity) + { + FitBox = fitBox; + FitMode = fitMode; + Gravity = gravity; + return this; + } - public WatermarkOptions SetFitBox(WatermarkFitBox fitBox) - { - FitBox = fitBox; - return this; - } + public WatermarkOptions SetMarginsLayout(WatermarkMargins margins, WatermarkConstraintMode fitMode, + ConstraintGravity gravity) + { + FitBox = margins; + FitMode = fitMode; + Gravity = gravity; + return this; + } - public WatermarkOptions SetFitMode(WatermarkConstraintMode fitMode) + /// + /// Hide the watermark if the canvas is smaller in either dimension + /// + /// + /// + /// + public WatermarkOptions SetMinCanvasSize(uint? minWidth, uint? minHeight) + { + MinCanvasWidth = minWidth; + MinCanvasHeight = minHeight; + return this; + } + + [Obsolete("Use ToJsonNode() instead")] + public object ToImageflowDynamic(int? ioId) + { + return new + { + io_id = ioId, + gravity = Gravity?.ToImageflowDynamic(), + fit_box = FitBox?.ToImageflowDynamic(), + fit_mode = FitMode?.ToString().ToLowerInvariant(), + min_canvas_width = MinCanvasWidth, + min_canvas_height = MinCanvasWidth, + opacity = Opacity, + hints = Hints?.ToImageflowDynamic() + }; + } + + internal JsonNode ToJsonNode(int? ioId) + { + var node = new JsonObject { { "io_id", ioId } }; + if (Gravity != null) { - FitMode = fitMode; - return this; + node.Add("gravity", Gravity.ToJsonNode()); } - public WatermarkOptions SetGravity(ConstraintGravity gravity) + if (FitBox != null) { - Gravity = gravity; - return this; + node.Add("fit_box", FitBox.ToJsonNode()); } - public WatermarkOptions SetFitBoxLayout(WatermarkFitBox fitBox, WatermarkConstraintMode fitMode, - ConstraintGravity gravity) + if (FitMode != null) { - FitBox = fitBox; - FitMode = fitMode; - Gravity = gravity; - return this; + node.Add("fit_mode", FitMode?.ToString().ToLowerInvariant()); } - public WatermarkOptions SetMarginsLayout(WatermarkMargins margins, WatermarkConstraintMode fitMode, - ConstraintGravity gravity) + if (MinCanvasWidth != null) { - FitBox = margins; - FitMode = fitMode; - Gravity = gravity; - return this; + node.Add("min_canvas_width", MinCanvasWidth); } - /// - /// Hide the watermark if the canvas is smaller in either dimension - /// - /// - /// - /// - public WatermarkOptions SetMinCanvasSize(uint? minWidth, uint? minHeight) + if (MinCanvasHeight != null) { - MinCanvasWidth = minWidth; - MinCanvasHeight = minHeight; - return this; + node.Add("min_canvas_height", MinCanvasHeight); } - [Obsolete("Use ToJsonNode() instead")] - public object ToImageflowDynamic(int? ioId) + if (Opacity != null) { - return new - { - io_id = ioId, - gravity = Gravity?.ToImageflowDynamic(), - fit_box = FitBox?.ToImageflowDynamic(), - fit_mode = FitMode?.ToString().ToLowerInvariant(), - min_canvas_width = MinCanvasWidth, - min_canvas_height = MinCanvasWidth, - opacity = Opacity, - hints = Hints?.ToImageflowDynamic() - }; + node.Add("opacity", Opacity); } - internal JsonNode ToJsonNode(int? ioId) + if (Hints != null) { - var node = new JsonObject { { "io_id", ioId } }; - if (Gravity != null) node.Add("gravity", Gravity.ToJsonNode()); - if (FitBox != null) node.Add("fit_box", FitBox.ToJsonNode()); - if (FitMode != null) node.Add("fit_mode", FitMode?.ToString().ToLowerInvariant()); - if (MinCanvasWidth != null) node.Add("min_canvas_width", MinCanvasWidth); - if (MinCanvasHeight != null) node.Add("min_canvas_height", MinCanvasHeight); - if (Opacity != null) node.Add("opacity", Opacity); - if (Hints != null) node.Add("hints", Hints.ToJsonNode()); - return node; + node.Add("hints", Hints.ToJsonNode()); } + + return node; } } diff --git a/src/Imageflow/GlobalSuppressions.cs b/src/Imageflow/GlobalSuppressions.cs index 36d7545..4374f40 100644 --- a/src/Imageflow/GlobalSuppressions.cs +++ b/src/Imageflow/GlobalSuppressions.cs @@ -9,6 +9,5 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2219:Do not raise exceptions in finally clauses", Justification = "", Scope = "member", Target = "~M:Imageflow.Bindings.JobContext.Dispose")] - // internals visible to Imageflow.Test [assembly: InternalsVisibleTo("Imageflow.Test")] diff --git a/src/Imageflow/IO/ProcessEx.cs b/src/Imageflow/IO/ProcessEx.cs index 1b4e722..505ceee 100644 --- a/src/Imageflow/IO/ProcessEx.cs +++ b/src/Imageflow/IO/ProcessEx.cs @@ -3,124 +3,135 @@ using Microsoft.IO; -namespace Imageflow.IO +namespace Imageflow.IO; + +internal sealed + class ProcessResults : IDisposable { - internal sealed - class ProcessResults : IDisposable + public ProcessResults(Process process, string[] standardOutput, string[] standardError) { - public ProcessResults(Process process, string[] standardOutput, string[] standardError) - { - Process = process; - ExitCode = process.ExitCode; - StandardOutput = standardOutput; - StandardError = standardError; - } + Process = process; + ExitCode = process.ExitCode; + StandardOutput = standardOutput; + StandardError = standardError; + } - public Process Process { get; } - public int ExitCode { get; } - public string[] StandardOutput { get; } - public string[] StandardError { get; } - public void Dispose() { Process.Dispose(); } + public Process Process { get; } + public int ExitCode { get; } + public string[] StandardOutput { get; } + public string[] StandardError { get; } + public void Dispose() { Process.Dispose(); } - public MemoryStream GetBufferedOutputStream() => GetStreamForStrings(StandardOutput); - public MemoryStream GetBufferedErrorStream() => GetStreamForStrings(StandardError); + public MemoryStream GetBufferedOutputStream() => GetStreamForStrings(StandardOutput); + public MemoryStream GetBufferedErrorStream() => GetStreamForStrings(StandardError); - public string GetStandardOutputString() => string.Join("", StandardOutput); - public string GetStandardErrorString() => string.Join("", StandardError); + public string GetStandardOutputString() => string.Join("", StandardOutput); + public string GetStandardErrorString() => string.Join("", StandardError); - private static readonly RecyclableMemoryStreamManager Mgr = new RecyclableMemoryStreamManager(); - static MemoryStream GetStreamForStrings(string[] strings) + private static readonly RecyclableMemoryStreamManager Mgr = new RecyclableMemoryStreamManager(); + static MemoryStream GetStreamForStrings(string[] strings) + { + // Initial alloc is optimized for ascii + var stream = Mgr.GetStream("stdout bytes", strings.Sum(s => s.Length)); + var streamWriter = new StreamWriter(stream, Encoding.UTF8, 4096, true); + foreach (var str in strings) { - // Initial alloc is optimized for ascii - var stream = Mgr.GetStream("stdout bytes", strings.Sum(s => s.Length)); - var streamWriter = new StreamWriter(stream, Encoding.UTF8, 4096, true); - foreach (var str in strings) - { - streamWriter.Write(str); - } - streamWriter.Flush(); - stream.Seek(0, SeekOrigin.Begin); - return stream; + streamWriter.Write(str); } + streamWriter.Flush(); + stream.Seek(0, SeekOrigin.Begin); + return stream; } +} - internal static class ProcessEx - { +internal static class ProcessEx +{ - public static Task RunAsync(string fileName) - => RunAsync(new ProcessStartInfo(fileName)); + public static Task RunAsync(string fileName) + => RunAsync(new ProcessStartInfo(fileName)); - public static Task RunAsync(string fileName, string arguments) - => RunAsync(new ProcessStartInfo(fileName, arguments)); + public static Task RunAsync(string fileName, string arguments) + => RunAsync(new ProcessStartInfo(fileName, arguments)); - public static Task RunAsync(ProcessStartInfo processStartInfo) - => RunAsync(processStartInfo, CancellationToken.None); + public static Task RunAsync(ProcessStartInfo processStartInfo) + => RunAsync(processStartInfo, CancellationToken.None); - public static async Task RunAsync(ProcessStartInfo processStartInfo, CancellationToken cancellationToken) - { - // force some settings in the start info so we can capture the output - processStartInfo.UseShellExecute = false; - processStartInfo.RedirectStandardOutput = true; - processStartInfo.RedirectStandardError = true; + public static async Task RunAsync(ProcessStartInfo processStartInfo, CancellationToken cancellationToken) + { + // force some settings in the start info so we can capture the output + processStartInfo.UseShellExecute = false; + processStartInfo.RedirectStandardOutput = true; + processStartInfo.RedirectStandardError = true; - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); - var standardOutput = new List(); - var standardError = new List(); + var standardOutput = new List(); + var standardError = new List(); - var process = new Process - { - StartInfo = processStartInfo, - EnableRaisingEvents = true - }; + var process = new Process + { + StartInfo = processStartInfo, + EnableRaisingEvents = true + }; - var standardOutputResults = new TaskCompletionSource(); - process.OutputDataReceived += (_, args) => + var standardOutputResults = new TaskCompletionSource(); + process.OutputDataReceived += (_, args) => + { + if (args.Data != null) { - if (args.Data != null) - standardOutput.Add(args.Data); - else - standardOutputResults.SetResult(standardOutput.ToArray()); - }; - - var standardErrorResults = new TaskCompletionSource(); - process.ErrorDataReceived += (_, args) => + standardOutput.Add(args.Data); + } + else { - if (args.Data != null) - standardError.Add(args.Data); - else - standardErrorResults.SetResult(standardError.ToArray()); - }; + standardOutputResults.SetResult(standardOutput.ToArray()); + } + }; - process.Exited += (sender, args) => + var standardErrorResults = new TaskCompletionSource(); + process.ErrorDataReceived += (_, args) => + { + if (args.Data != null) { - // Since the Exited event can happen asynchronously to the output and error events, - // we use the task results for stdout/stderr to ensure they both closed - tcs.TrySetResult(new ProcessResults(process, standardOutputResults.Task.Result, standardErrorResults.Task.Result)); - }; + standardError.Add(args.Data); + } + else + { + standardErrorResults.SetResult(standardError.ToArray()); + } + }; + + process.Exited += (sender, args) => + { + // Since the Exited event can happen asynchronously to the output and error events, + // we use the task results for stdout/stderr to ensure they both closed + tcs.TrySetResult(new ProcessResults(process, standardOutputResults.Task.Result, standardErrorResults.Task.Result)); + }; - using (cancellationToken.Register( - callback: () => + using (cancellationToken.Register( + callback: () => + { + tcs.TrySetCanceled(); + try { - tcs.TrySetCanceled(); - try + if (!process.HasExited) { - if (!process.HasExited) - process.Kill(); + process.Kill(); } - catch (InvalidOperationException) { } - })) - { - cancellationToken.ThrowIfCancellationRequested(); + } + catch (InvalidOperationException) { } + })) + { + cancellationToken.ThrowIfCancellationRequested(); - if (process.Start() == false) - tcs.TrySetException(new InvalidOperationException("Failed to start process")); + if (process.Start() == false) + { + tcs.TrySetException(new InvalidOperationException("Failed to start process")); + } - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); - return await tcs.Task; - } + return await tcs.Task; } } } diff --git a/src/Imageflow/IO/TemporaryFileProviders.cs b/src/Imageflow/IO/TemporaryFileProviders.cs index f7a200a..4d4793b 100644 --- a/src/Imageflow/IO/TemporaryFileProviders.cs +++ b/src/Imageflow/IO/TemporaryFileProviders.cs @@ -1,124 +1,123 @@ using System.IO.MemoryMappedFiles; -namespace Imageflow.IO +namespace Imageflow.IO; + +internal interface ITemporaryFileProvider +{ + ITemporaryFile Create(bool cleanup, long capacity); +} + +internal interface ITemporaryFile : IDisposable { + string Path { get; } + Stream WriteFromBeginning(); + Stream ReadFromBeginning(); +} - internal interface ITemporaryFileProvider +internal class TemporaryMemoryFileProvider : ITemporaryFileProvider +{ + public ITemporaryFile Create(bool cleanup, long capacity) { - ITemporaryFile Create(bool cleanup, long capacity); + if (!cleanup) + { + throw new InvalidOperationException("Memory Mapped Files cannot be persisted"); + } + + var name = Guid.NewGuid().ToString(); + var file = MemoryMappedFile.CreateNew(name, capacity); + return new TemporaryMemoryFile(file, name); } +} + +internal class TemporaryMemoryFile : ITemporaryFile +{ + private readonly MemoryMappedFile _file; + public string Path { get; } - internal interface ITemporaryFile : IDisposable + internal TemporaryMemoryFile(MemoryMappedFile file, string path) { - string Path { get; } - Stream WriteFromBeginning(); - Stream ReadFromBeginning(); + _file = file; + Path = path; } - internal class TemporaryMemoryFileProvider : ITemporaryFileProvider + public Stream WriteFromBeginning() { - public ITemporaryFile Create(bool cleanup, long capacity) - { - if (!cleanup) throw new InvalidOperationException("Memory Mapped Files cannot be persisted"); - var name = Guid.NewGuid().ToString(); - var file = MemoryMappedFile.CreateNew(name, capacity); - return new TemporaryMemoryFile(file, name); - } + return ReadFromBeginning(); } - internal class TemporaryMemoryFile : ITemporaryFile + public Stream ReadFromBeginning() { - private readonly MemoryMappedFile _file; - public string Path { get; } - - - internal TemporaryMemoryFile(MemoryMappedFile file, string path) - { - _file = file; - Path = path; - } - - public Stream WriteFromBeginning() - { - return ReadFromBeginning(); - } - - public Stream ReadFromBeginning() - { - var stream = _file.CreateViewStream(); - stream.Seek(0, SeekOrigin.Begin); - return stream; - } - - public static ITemporaryFileProvider CreateProvider() - { - return new TemporaryMemoryFileProvider(); - } - - public void Dispose() - { - _file?.Dispose(); - } + var stream = _file.CreateViewStream(); + stream.Seek(0, SeekOrigin.Begin); + return stream; } - internal class TemporaryFileProvider : ITemporaryFileProvider + public static ITemporaryFileProvider CreateProvider() { - public ITemporaryFile Create(bool cleanup, long capacity) - { - return new TemporaryFile(Path.GetTempFileName(), cleanup); - } + return new TemporaryMemoryFileProvider(); + } + public void Dispose() + { + _file?.Dispose(); } +} - internal class TemporaryFile : ITemporaryFile +internal class TemporaryFileProvider : ITemporaryFileProvider +{ + public ITemporaryFile Create(bool cleanup, long capacity) { - private readonly List> _cleanup = new List>(2); - private bool _deleteOnDispose; + return new TemporaryFile(Path.GetTempFileName(), cleanup); + } - internal TemporaryFile(string path, bool deleteOnDispose) - { - Path = path; - _deleteOnDispose = deleteOnDispose; - } +} - public string Path { get; private set; } +internal class TemporaryFile : ITemporaryFile +{ + private readonly List> _cleanup = new List>(2); + private bool _deleteOnDispose; - public Stream WriteFromBeginning() - { - var fs = new FileStream(Path, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 4096, true); - _cleanup.Add(new WeakReference(fs)); - return fs; - } + internal TemporaryFile(string path, bool deleteOnDispose) + { + Path = path; + _deleteOnDispose = deleteOnDispose; + } - public Stream ReadFromBeginning() - { - var fs = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); - _cleanup.Add(new WeakReference(fs)); - return fs; - } + public string Path { get; private set; } + public Stream WriteFromBeginning() + { + var fs = new FileStream(Path, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 4096, true); + _cleanup.Add(new WeakReference(fs)); + return fs; + } + public Stream ReadFromBeginning() + { + var fs = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); + _cleanup.Add(new WeakReference(fs)); + return fs; + } - public static ITemporaryFileProvider CreateProvider() - { - return new TemporaryFileProvider(); - } + public static ITemporaryFileProvider CreateProvider() + { + return new TemporaryFileProvider(); + } - public void Dispose() + public void Dispose() + { + foreach (var d in _cleanup) { - foreach (var d in _cleanup) + if (d.TryGetTarget(out var disposable)) { - if (d.TryGetTarget(out var disposable)) - { - disposable.Dispose(); - } - } - _cleanup.Clear(); - if (_deleteOnDispose) - { - File.Delete(Path); - _deleteOnDispose = false; + disposable.Dispose(); } } + _cleanup.Clear(); + if (_deleteOnDispose) + { + File.Delete(Path); + _deleteOnDispose = false; + } } } diff --git a/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs b/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs index ddb6e01..f22e862 100644 --- a/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs +++ b/src/Imageflow/Internal.Helpers/ArraySegmentExtensions.cs @@ -8,8 +8,15 @@ internal static bool TryGetBufferSliceAfterPosition(this MemoryStream stream, ou { var remainingStreamLength = stream.Length - (int)stream.Position; if (remainingStreamLength > int.MaxValue || stream.Position > int.MaxValue) + { throw new OverflowException("Streams cannot exceed 2GB"); - if (stream.Position < 0) throw new InvalidOperationException("Stream position cannot be negative"); + } + + if (stream.Position < 0) + { + throw new InvalidOperationException("Stream position cannot be negative"); + } + segment = new ArraySegment(wholeStream.Array, (int)stream.Position, (int)remainingStreamLength); return true; } @@ -22,7 +29,11 @@ internal static bool TryGetBufferSliceAllWrittenData(this MemoryStream stream, o { if (stream.TryGetBuffer(out var wholeStream) && wholeStream is { Count: > 0, Array: not null }) { - if (stream.Length > int.MaxValue) throw new OverflowException("Streams cannot exceed 2GB"); + if (stream.Length > int.MaxValue) + { + throw new OverflowException("Streams cannot exceed 2GB"); + } + segment = new ArraySegment(wholeStream.Array, 0, (int)stream.Length); return true; } diff --git a/src/Imageflow/Internal.Helpers/IAssertReady.cs b/src/Imageflow/Internal.Helpers/IAssertReady.cs index e919c6d..f2210dd 100644 --- a/src/Imageflow/Internal.Helpers/IAssertReady.cs +++ b/src/Imageflow/Internal.Helpers/IAssertReady.cs @@ -1,7 +1,6 @@ -namespace Imageflow.Internal.Helpers +namespace Imageflow.Internal.Helpers; + +internal interface IAssertReady { - internal interface IAssertReady - { - void AssertReady(); - } + void AssertReady(); } diff --git a/src/Imageflow/Internal.Helpers/PinBox.cs b/src/Imageflow/Internal.Helpers/PinBox.cs index 7ef996b..8517ade 100644 --- a/src/Imageflow/Internal.Helpers/PinBox.cs +++ b/src/Imageflow/Internal.Helpers/PinBox.cs @@ -1,39 +1,41 @@ using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; -namespace Imageflow.Internal.Helpers +namespace Imageflow.Internal.Helpers; + +internal class PinBox : CriticalFinalizerObject, IDisposable { - internal class PinBox : CriticalFinalizerObject, IDisposable + private List? _pinned; + internal void AddPinnedData(GCHandle handle) { - private List? _pinned; - internal void AddPinnedData(GCHandle handle) - { - _pinned ??= new List(); - _pinned.Add(handle); - } + _pinned ??= new List(); + _pinned.Add(handle); + } - public void Dispose() - { - UnpinAll(); - GC.SuppressFinalize(this); - } + public void Dispose() + { + UnpinAll(); + GC.SuppressFinalize(this); + } - private void UnpinAll() + private void UnpinAll() + { + //Unpin GCHandles + if (_pinned != null) { - //Unpin GCHandles - if (_pinned != null) + foreach (var active in _pinned) { - foreach (var active in _pinned) + if (active.IsAllocated) { - if (active.IsAllocated) active.Free(); + active.Free(); } - _pinned = null; } + _pinned = null; } + } - ~PinBox() - { - UnpinAll(); - } + ~PinBox() + { + UnpinAll(); } } diff --git a/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs b/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs index 928d825..1d337ec 100644 --- a/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs +++ b/src/Imageflow/Internal.Helpers/SafeHandleMemoryManager.cs @@ -115,5 +115,4 @@ protected override void Dispose(bool disposing) } } - } diff --git a/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs b/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs index 9cb4c80..c81ac96 100644 --- a/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs +++ b/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs @@ -41,7 +41,6 @@ static async Task WriteAsyncFallback(Stream stream, ReadOnlyMemory buffer, #endif } - internal static void WriteMemory(this Stream stream, ReadOnlyMemory buffer) { #if NETSTANDARD2_1_OR_GREATER diff --git a/src/Imageflow/Internal.Helpers/TextHelpers.cs b/src/Imageflow/Internal.Helpers/TextHelpers.cs index dea6467..c0a38de 100644 --- a/src/Imageflow/Internal.Helpers/TextHelpers.cs +++ b/src/Imageflow/Internal.Helpers/TextHelpers.cs @@ -58,6 +58,4 @@ internal static bool TryEncodeAsciiNullTerminatedIntoBuffer(ReadOnlySpan t return true; } - - } diff --git a/tests/Imageflow.TestDotNetFull/TestDotNetClassicLibraryLoading.cs b/tests/Imageflow.TestDotNetFull/TestDotNetClassicLibraryLoading.cs index cc3ca03..bb3797b 100644 --- a/tests/Imageflow.TestDotNetFull/TestDotNetClassicLibraryLoading.cs +++ b/tests/Imageflow.TestDotNetFull/TestDotNetClassicLibraryLoading.cs @@ -1,14 +1,13 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Imageflow.TestDotNetFull +namespace Imageflow.TestDotNetFull; + +[TestClass] +public class TestDotNetClassicLibraryLoading { - [TestClass] - public class TestDotNetClassicLibraryLoading + [TestMethod] + public void TestAccessAbi() { - [TestMethod] - public void TestAccessAbi() - { - using (var j = new Bindings.JobContext()) { } - } + using (var j = new Bindings.JobContext()) { } } } diff --git a/tests/Imageflow.TestDotNetFullPackageReference/TestDotNetClassicPackageReferenceLibraryLoading.cs b/tests/Imageflow.TestDotNetFullPackageReference/TestDotNetClassicPackageReferenceLibraryLoading.cs index 49298c3..9c6f2f8 100644 --- a/tests/Imageflow.TestDotNetFullPackageReference/TestDotNetClassicPackageReferenceLibraryLoading.cs +++ b/tests/Imageflow.TestDotNetFullPackageReference/TestDotNetClassicPackageReferenceLibraryLoading.cs @@ -1,14 +1,11 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +namespace Imageflow.TestDotNetFullPackageReference; -namespace Imageflow.TestDotNetFullPackageReference +[Microsoft.VisualStudio.TestTools.UnitTesting.TestClass] +public class TestDotNetClassicPackageReferenceLibraryLoading { - [TestClass] - public class TestDotNetClassicPackageReferenceLibraryLoading - { - [TestMethod] - public void TestAccessAbi() - { - using (var j = new Bindings.JobContext()) { } - } +[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod] +public void TestAccessAbi() +{ + using (var j = new Bindings.JobContext()) { } } } From a21119892e639cb88fea151aae9bb57f8539e180 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 22:43:58 -0700 Subject: [PATCH 15/22] Apply ConfigureAwait(false) and Argument.ThrowIfNull --- src/Imageflow/Bindings/JobContext.cs | 15 ++----- src/Imageflow/Fluent/BufferedStreamSource.cs | 2 +- src/Imageflow/Fluent/FinishJobBuilder.cs | 2 +- .../Fluent/IOutputDestinationExtensions.cs | 18 ++++----- src/Imageflow/Fluent/IOutputSink.cs | 6 +-- src/Imageflow/Fluent/ImageJob.cs | 40 +++++++++---------- src/Imageflow/Fluent/JobExecutionOptions.cs | 2 +- src/Imageflow/Fluent/MemorySource.cs | 6 +-- src/Imageflow/Fluent/PerformanceDetails.cs | 2 +- src/Imageflow/Fluent/StreamSource.cs | 2 +- src/Imageflow/IO/ProcessEx.cs | 2 +- .../ArgumentNullThrowHelperPolyfill.cs | 6 +-- 12 files changed, 47 insertions(+), 56 deletions(-) diff --git a/src/Imageflow/Bindings/JobContext.cs b/src/Imageflow/Bindings/JobContext.cs index 13105a3..95f227b 100644 --- a/src/Imageflow/Bindings/JobContext.cs +++ b/src/Imageflow/Bindings/JobContext.cs @@ -451,10 +451,7 @@ public void AddInputBytes(int ioId, ArraySegment buffer) /// public void AddInputBytes(int ioId, byte[] buffer, long offset, long count) { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } + Argument.ThrowIfNull(buffer); if (offset > int.MaxValue) { @@ -520,10 +517,7 @@ public void AddInputBytes(int ioId, ReadOnlySpan data) /// public void AddInputBytesPinned(int ioId, byte[] buffer) { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } + Argument.ThrowIfNull(buffer); AddInputBytesPinned(ioId, new ReadOnlyMemory(buffer), MemoryLifetimePromise.MemoryIsOwnedByRuntime); } @@ -553,10 +547,7 @@ public void AddInputBytesPinned(int ioId, ArraySegment buffer) /// public void AddInputBytesPinned(int ioId, byte[] buffer, long offset, long count) { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } + Argument.ThrowIfNull(buffer); if (offset > int.MaxValue) { diff --git a/src/Imageflow/Fluent/BufferedStreamSource.cs b/src/Imageflow/Fluent/BufferedStreamSource.cs index 178cb42..208f560 100644 --- a/src/Imageflow/Fluent/BufferedStreamSource.cs +++ b/src/Imageflow/Fluent/BufferedStreamSource.cs @@ -83,7 +83,7 @@ public async ValueTask> BorrowReadOnlyMemoryAsync(Cancellat _underlying.Seek(0, SeekOrigin.Begin); } _copy = new RecyclableMemoryStream(Mgr, "BufferedStreamSource: IMemorySource", _underlying.CanSeek ? _underlying.Length : 0); - await _underlying.CopyToAsync(_copy, 81920, cancellationToken); + await _underlying.CopyToAsync(_copy, 81920, cancellationToken).ConfigureAwait(false); _copy.Seek(0, SeekOrigin.Begin); if (!TryGetWrittenMemory(out segment)) { diff --git a/src/Imageflow/Fluent/FinishJobBuilder.cs b/src/Imageflow/Fluent/FinishJobBuilder.cs index c1c63b2..d2cbe48 100644 --- a/src/Imageflow/Fluent/FinishJobBuilder.cs +++ b/src/Imageflow/Fluent/FinishJobBuilder.cs @@ -89,7 +89,7 @@ public async Task InProcessAndDisposeAsync() BuildJobResult r; try { - r = await InProcessAsync(); + r = await InProcessAsync().ConfigureAwait(false); } finally { diff --git a/src/Imageflow/Fluent/IOutputDestinationExtensions.cs b/src/Imageflow/Fluent/IOutputDestinationExtensions.cs index 37d64fd..1f0aff5 100644 --- a/src/Imageflow/Fluent/IOutputDestinationExtensions.cs +++ b/src/Imageflow/Fluent/IOutputDestinationExtensions.cs @@ -25,14 +25,14 @@ internal static async ValueTask AdaptiveWriteAllAsync(this IOutputDestination de { if (dest is IAsyncOutputSink sink) { - await sink.FastRequestCapacityAsync(data.Length); - await sink.FastWriteAsync(data, cancellationToken); - await sink.FastFlushAsync(cancellationToken); + await sink.FastRequestCapacityAsync(data.Length).ConfigureAwait(false); + await sink.FastWriteAsync(data, cancellationToken).ConfigureAwait(false); + await sink.FastFlushAsync(cancellationToken).ConfigureAwait(false); return; } - await dest.RequestCapacityAsync(data.Length); - await dest.AdaptedWriteAsync(data, cancellationToken); - await dest.FlushAsync(cancellationToken); + await dest.RequestCapacityAsync(data.Length).ConfigureAwait(false); + await dest.AdaptedWriteAsync(data, cancellationToken).ConfigureAwait(false); + await dest.FlushAsync(cancellationToken).ConfigureAwait(false); } internal static async ValueTask AdaptedWriteAsync(this IOutputDestination dest, ReadOnlyMemory data, CancellationToken cancellationToken) @@ -86,14 +86,14 @@ internal static void AdaptedWrite(this IOutputDestination dest, ReadOnlySpan await dest.CopyFromStreamAsyncInternal(stream, cancellationToken); + => await dest.CopyFromStreamAsyncInternal(stream, cancellationToken).ConfigureAwait(false); internal static async Task CopyFromStreamAsyncInternal(this IOutputDestination dest, Stream stream, CancellationToken cancellationToken) { if (stream is { CanRead: true, CanSeek: true }) { - await dest.RequestCapacityAsync((int)stream.Length); + await dest.RequestCapacityAsync((int)stream.Length).ConfigureAwait(false); } const int bufferSize = 81920; @@ -107,6 +107,6 @@ await dest.WriteAsync(new ArraySegment(buffer, 0, bytesRead), cancellation .ConfigureAwait(false); } - await dest.FlushAsync(cancellationToken); + await dest.FlushAsync(cancellationToken).ConfigureAwait(false); } } diff --git a/src/Imageflow/Fluent/IOutputSink.cs b/src/Imageflow/Fluent/IOutputSink.cs index 4942207..f561b2a 100644 --- a/src/Imageflow/Fluent/IOutputSink.cs +++ b/src/Imageflow/Fluent/IOutputSink.cs @@ -18,8 +18,8 @@ internal static class OutputSinkExtensions { public static async ValueTask WriteAllAsync(this IAsyncOutputSink sink, ReadOnlyMemory data, CancellationToken cancellationToken) { - await sink.FastRequestCapacityAsync(data.Length); - await sink.FastWriteAsync(data, cancellationToken); - await sink.FastFlushAsync(cancellationToken); + await sink.FastRequestCapacityAsync(data.Length).ConfigureAwait(false); + await sink.FastWriteAsync(data, cancellationToken).ConfigureAwait(false); + await sink.FastFlushAsync(cancellationToken).ConfigureAwait(false); } } diff --git a/src/Imageflow/Fluent/ImageJob.cs b/src/Imageflow/Fluent/ImageJob.cs index 0d40cd0..b5cc545 100644 --- a/src/Imageflow/Fluent/ImageJob.cs +++ b/src/Imageflow/Fluent/ImageJob.cs @@ -23,7 +23,7 @@ internal void AddInput(int ioId, IAsyncMemorySource source) { if (_inputs.ContainsKey(ioId) || _outputs.ContainsKey(ioId)) { - throw new ArgumentException("ioId", $"ioId {ioId} has already been assigned"); + throw new ArgumentException($"ioId {ioId} has already been assigned", nameof(ioId)); } _inputs.Add(ioId, source); @@ -32,7 +32,7 @@ internal void AddOutput(int ioId, IOutputDestination destination) { if (_inputs.ContainsKey(ioId) || _outputs.ContainsKey(ioId)) { - throw new ArgumentException("ioId", $"ioId {ioId} has already been assigned"); + throw new ArgumentException($"ioId {ioId} has already been assigned", nameof(ioId)); } _outputs.Add(ioId, destination); @@ -148,7 +148,7 @@ public BuildEndpoint BuildCommandString(IBytesSource source, int? sourceIoId, IO public BuildEndpoint BuildCommandString(byte[] source, IOutputDestination dest, string commandString) => BuildCommandString(new MemorySource(source), dest, commandString); /// - /// Modify the input image (source) with the given command string and watermarks and encode to the (dest) + /// Modify the input image (source) with the given command string and watermarks and encode to the (dest) /// /// /// @@ -265,7 +265,7 @@ internal string ToJsonDebug(SecurityOptions? securityOptions = default) internal async Task FinishAsync(JobExecutionOptions executionOptions, SecurityOptions? securityOptions, CancellationToken cancellationToken) { var inputByteArrays = await Task.WhenAll( - _inputs.Select(async pair => new KeyValuePair>(pair.Key, await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken)))); + _inputs.Select(async pair => new KeyValuePair>(pair.Key, await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken).ConfigureAwait(false)))).ConfigureAwait(false); using (var ctx = new JobContext()) { foreach (var pair in inputByteArrays) @@ -282,7 +282,7 @@ internal async Task FinishAsync(JobExecutionOptions executionOpt var message = CreateJsonNodeForFramewiseWithSecurityOptions(securityOptions); var response = executionOptions.OffloadCpuToThreadPool - ? await Task.Run(() => ctx.InvokeExecute(message), cancellationToken) + ? await Task.Run(() => ctx.InvokeExecute(message), cancellationToken).ConfigureAwait(false) : ctx.InvokeExecute(message); // TODO: Should we handle failure before copying out the buffers?? @@ -292,7 +292,7 @@ internal async Task FinishAsync(JobExecutionOptions executionOpt foreach (var pair in _outputs) { using var memOwner = ctx.BorrowOutputBufferMemoryAndAddReference(pair.Key); - await pair.Value.AdaptiveWriteAllAsync(memOwner.Memory, cancellationToken); + await pair.Value.AdaptiveWriteAllAsync(memOwner.Memory, cancellationToken).ConfigureAwait(false); } return BuildJobResult.From(response, _outputs); } @@ -377,7 +377,7 @@ internal async Task CopyOutputsToDestinations(CancellationToken token) { using (var stream = pair.Key.ReadFromBeginning()) { - await pair.Value.CopyFromStreamAsyncInternal(stream, token); + await pair.Value.CopyFromStreamAsyncInternal(stream, token).ConfigureAwait(false); } } } @@ -404,7 +404,7 @@ private async Task PrepareForSubprocessAsync(Cancellati var inputFiles = (await Task.WhenAll(_inputs.Select(async pair => { - var bytes = await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken); + var bytes = await pair.Value.BorrowReadOnlyMemoryAsync(cancellationToken).ConfigureAwait(false); var file = job.Provider.Create(cleanupFiles, bytes.Length); job.Cleanup.Add(file); @@ -412,7 +412,7 @@ private async Task PrepareForSubprocessAsync(Cancellati io: new JsonObject { { "file", file.Path } }, bytes, bytes.Length, File: file); - }))).ToArray(); + })).ConfigureAwait(false)).ToArray(); var outputCapacity = outputBufferCapacity ?? inputFiles.Max(v => v.Length) * 2; var outputFiles = _outputs.Select(pair => @@ -427,7 +427,7 @@ private async Task PrepareForSubprocessAsync(Cancellati foreach (var f in inputFiles) { using var accessor = f.File.WriteFromBeginning(); - await accessor.WriteMemoryAsync(f.bytes, cancellationToken); + await accessor.WriteMemoryAsync(f.bytes, cancellationToken).ConfigureAwait(false); } // job.JobMessage = new @@ -505,7 +505,7 @@ internal async Task FinishInSubprocessAsync(SecurityOptions? sec throw new FileNotFoundException("Cannot find imageflow_tool using path \"" + imageflowToolPath + "\" and currect folder \"" + Directory.GetCurrentDirectory() + "\""); } - using (var job = await PrepareForSubprocessAsync(cancellationToken, securityOptions, true, outputBufferCapacity)) + using (var job = await PrepareForSubprocessAsync(cancellationToken, securityOptions, true, outputBufferCapacity).ConfigureAwait(false)) { var startInfo = new ProcessStartInfo @@ -517,7 +517,7 @@ internal async Task FinishInSubprocessAsync(SecurityOptions? sec FileName = imageflowToolPath }; - var results = await ProcessEx.RunAsync(startInfo, cancellationToken); + var results = await ProcessEx.RunAsync(startInfo, cancellationToken).ConfigureAwait(false); var output = results.GetBufferedOutputStream(); var errors = results.GetStandardErrorString(); @@ -535,7 +535,7 @@ internal async Task FinishInSubprocessAsync(SecurityOptions? sec } } - await job.CopyOutputsToDestinations(cancellationToken); + await job.CopyOutputsToDestinations(cancellationToken).ConfigureAwait(false); var outputMemory = output.GetWrittenMemory(); return BuildJobResult.From(new MemoryJsonResponse(results.ExitCode, outputMemory), _outputs); @@ -544,7 +544,7 @@ internal async Task FinishInSubprocessAsync(SecurityOptions? sec internal async Task WriteJsonJobAndInputs(CancellationToken cancellationToken, SecurityOptions? securityOptions, bool deleteFilesOnDispose) { - return await PrepareForSubprocessAsync(cancellationToken, securityOptions, deleteFilesOnDispose); + return await PrepareForSubprocessAsync(cancellationToken, securityOptions, deleteFilesOnDispose).ConfigureAwait(false); } private readonly List _nodesCreated = new List(10); @@ -598,10 +598,10 @@ private static IEnumerable> CollectEdges(ICollection internal JsonNode ToFramewise() { var nodes = CollectUnique(); - return ToFramewiseGraph(nodes); + return ImageJob.ToFramewiseGraph(nodes); } - private JsonNode ToFramewiseGraph(ICollection uniqueNodes) + private static JsonNode ToFramewiseGraph(ICollection uniqueNodes) { var lowestUid = LowestUid(uniqueNodes) ?? 0; var edges = CollectEdges(uniqueNodes) @@ -707,7 +707,7 @@ public static async Task GetImageInfo(IBytesSource image, Cancellatio { try { - var inputByteArray = await image.GetBytesAsync(cancellationToken); + var inputByteArray = await image.GetBytesAsync(cancellationToken).ConfigureAwait(false); using (var ctx = new JobContext()) { ctx.AddInputBytesPinned(0, inputByteArray); @@ -758,7 +758,7 @@ public static async ValueTask GetImageInfoAsync(IAsyncMemorySource im { try { - var inputMemory = await image.BorrowReadOnlyMemoryAsync(cancellationToken); + var inputMemory = await image.BorrowReadOnlyMemoryAsync(cancellationToken).ConfigureAwait(false); if (inputMemory.Length == 0) { throw new ArgumentException("Input image is empty"); @@ -782,7 +782,7 @@ public static async ValueTask GetImageInfoAsync(IAsyncMemorySource im } } /// - /// Returns true if it is likely that Imageflow can decode the given image based on the first 12 bytes of the file. + /// Returns true if it is likely that Imageflow can decode the given image based on the first 12 bytes of the file. /// /// The first 12 or more bytes of the file /// @@ -795,7 +795,7 @@ public static bool CanDecodeBytes(byte[] first12Bytes) /// /// Returns a MIME type string such as "image/jpeg" based on the provided first 12 bytes of the file. /// Only guaranteed to work for image types Imageflow supports, but support for more file types may be added - /// later. + /// later. /// /// The first 12 or more bytes of the file /// diff --git a/src/Imageflow/Fluent/JobExecutionOptions.cs b/src/Imageflow/Fluent/JobExecutionOptions.cs index 5f328c4..ad28a3b 100644 --- a/src/Imageflow/Fluent/JobExecutionOptions.cs +++ b/src/Imageflow/Fluent/JobExecutionOptions.cs @@ -2,5 +2,5 @@ namespace Imageflow.Fluent; internal class JobExecutionOptions { - internal bool OffloadCpuToThreadPool { get; set; } = false; + internal bool OffloadCpuToThreadPool { get; set; } } diff --git a/src/Imageflow/Fluent/MemorySource.cs b/src/Imageflow/Fluent/MemorySource.cs index 09c82e1..16d5115 100644 --- a/src/Imageflow/Fluent/MemorySource.cs +++ b/src/Imageflow/Fluent/MemorySource.cs @@ -11,7 +11,7 @@ public sealed class MemorySource : IAsyncMemorySource, IMemorySource public static IAsyncMemorySource TakeOwnership(IMemoryOwner ownedMemory, MemoryLifetimePromise promise) { - ArgumentNullThrowHelper.ThrowIfNull(ownedMemory); + Argument.ThrowIfNull(ownedMemory); if (promise != MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource) { throw new ArgumentException( @@ -26,7 +26,7 @@ private MemorySource(ReadOnlyMemory? borrowedMemory, IMemoryOwner? o { if (promise == MemoryLifetimePromise.MemoryOwnerDisposedByMemorySource) { - ArgumentNullThrowHelper.ThrowIfNull(ownedMemory); + Argument.ThrowIfNull(ownedMemory); if (borrowedMemory.HasValue) { throw new ArgumentException( @@ -39,7 +39,7 @@ private MemorySource(ReadOnlyMemory? borrowedMemory, IMemoryOwner? o throw new ArgumentNullException(nameof(borrowedMemory)); } - ArgumentNullThrowHelper.ThrowIfNull(borrowedMemory); + Argument.ThrowIfNull(borrowedMemory); _borrowedMemory = borrowedMemory; _ownedMemory = ownedMemory; } diff --git a/src/Imageflow/Fluent/PerformanceDetails.cs b/src/Imageflow/Fluent/PerformanceDetails.cs index 24f2825..6b04410 100644 --- a/src/Imageflow/Fluent/PerformanceDetails.cs +++ b/src/Imageflow/Fluent/PerformanceDetails.cs @@ -43,7 +43,7 @@ public string GetFirstFrameSummary() foreach (var n in Frames.First().Nodes) { sb.Append(n.Name); - sb.Append("("); + sb.Append('('); sb.Append((n.WallMicroseconds / 1000.0).ToString("0.####")); sb.Append("ms) "); } diff --git a/src/Imageflow/Fluent/StreamSource.cs b/src/Imageflow/Fluent/StreamSource.cs index 5a19ce9..a648720 100644 --- a/src/Imageflow/Fluent/StreamSource.cs +++ b/src/Imageflow/Fluent/StreamSource.cs @@ -51,7 +51,7 @@ public async Task> GetBytesAsync(CancellationToken cancellati if (_copy == null) { _copy = new RecyclableMemoryStream(Mgr, "StreamSource: IBytesSource", length); - await underlying.CopyToAsync(_copy, 81920, cancellationToken); + await underlying.CopyToAsync(_copy, 81920, cancellationToken).ConfigureAwait(false); } return new ArraySegment(_copy.GetBuffer(), 0, diff --git a/src/Imageflow/IO/ProcessEx.cs b/src/Imageflow/IO/ProcessEx.cs index 505ceee..00b0b4f 100644 --- a/src/Imageflow/IO/ProcessEx.cs +++ b/src/Imageflow/IO/ProcessEx.cs @@ -131,7 +131,7 @@ public static async Task RunAsync(ProcessStartInfo processStartI process.BeginOutputReadLine(); process.BeginErrorReadLine(); - return await tcs.Task; + return await tcs.Task.ConfigureAwait(false); } } } diff --git a/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs b/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs index fde52b8..cd6c5f3 100644 --- a/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs +++ b/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs @@ -7,7 +7,7 @@ namespace Imageflow.Internal.Helpers; // The .NET Foundation licenses this file to you under the MIT license. // Imazen licenses any changes to th -internal static partial class ArgumentNullThrowHelper +internal static class Argument { /// Throws an if is null. /// The reference type argument to validate as non-null. @@ -24,7 +24,7 @@ public static void ThrowIfNull( Throw(paramName); } #else - ArgumentNullException.ThrowIfNull(argument, paramName); + Argument.ThrowIfNull(argument, paramName); #endif } @@ -44,7 +44,7 @@ public static void ThrowIf([DoesNotReturnIf(true)] bool condition, object instan #if NET8_0_OR_GREATER ObjectDisposedException.ThrowIf(condition, instance); -#else +#else if (condition) { throw new ObjectDisposedException(instance?.GetType().Name); From 953b5de526e7cc29f6ac4e5269b77b9f8489066c Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 23:03:21 -0700 Subject: [PATCH 16/22] Fix various code warnings, ObjectDisposedHelper, Culture Invariance, Exception usage --- .editorconfig | 4 +-- src/Imageflow/Bindings/ImageflowException.cs | 2 +- src/Imageflow/Bindings/JobContext.cs | 23 +++++----------- src/Imageflow/Bindings/JobContextHandle.cs | 7 ++--- src/Imageflow/Bindings/JsonResponse.cs | 5 +--- src/Imageflow/Bindings/JsonResponseHandle.cs | 15 +++-------- .../Bindings/NativeLibraryLoading.cs | 26 +++++++++---------- .../Fluent/IOutputDestinationExtensions.cs | 2 ++ src/Imageflow/Fluent/ImageJob.cs | 8 +++--- src/Imageflow/Fluent/MagicBytes.cs | 2 +- src/Imageflow/Fluent/PerformanceDetails.cs | 7 ++--- src/Imageflow/Fluent/SrgbColor.cs | 2 +- .../ArgumentNullThrowHelperPolyfill.cs | 4 ++- .../StreamMemoryExtensionsPolyfills.cs | 2 ++ 14 files changed, 45 insertions(+), 64 deletions(-) diff --git a/.editorconfig b/.editorconfig index 97e8c72..53ba635 100644 --- a/.editorconfig +++ b/.editorconfig @@ -94,8 +94,8 @@ charset = utf-8-bom [*.{cs,vb}] -# SYSLIB1054: Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time -dotnet_diagnostic.SYSLIB1054.severity = warning +# # SYSLIB1054: Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time +# dotnet_diagnostic.SYSLIB1054.severity = warning # CA1018: Mark attributes with AttributeUsageAttribute dotnet_diagnostic.CA1018.severity = warning diff --git a/src/Imageflow/Bindings/ImageflowException.cs b/src/Imageflow/Bindings/ImageflowException.cs index 7704e76..391cb82 100644 --- a/src/Imageflow/Bindings/ImageflowException.cs +++ b/src/Imageflow/Bindings/ImageflowException.cs @@ -65,7 +65,7 @@ internal static ImageflowException FromContext(JobContextHandle c, ulong default return new ImageflowException("Imageflow context has no error stored; cannot fetch error message"); case ErrorFetchResult.BufferTooSmall: break; default: - throw new ArgumentOutOfRangeException(); + throw new NotImplementedException($"Unknown error fetching error: {result}"); } } diff --git a/src/Imageflow/Bindings/JobContext.cs b/src/Imageflow/Bindings/JobContext.cs index 95f227b..331fae9 100644 --- a/src/Imageflow/Bindings/JobContext.cs +++ b/src/Imageflow/Bindings/JobContext.cs @@ -19,11 +19,7 @@ private JobContextHandle Handle { get { - if (!_handle.IsValid) - { - throw new ObjectDisposedException("Imageflow JobContext"); - } - + ObjectDisposedHelper.ThrowIf(!_handle.IsValid, this); return _handle; } } @@ -354,11 +350,7 @@ private unsafe ImageflowJsonResponse InvokeInternal(ReadOnlySpan nullTermi public void AssertReady() { - if (!_handle.IsValid) - { - throw new ObjectDisposedException("Imageflow JobContext"); - } - + ObjectDisposedHelper.ThrowIf(!_handle.IsValid, this); if (HasError) { throw ImageflowException.FromContext(Handle); @@ -646,7 +638,7 @@ public void AddOutputBuffer(int ioId) [Obsolete("Use a higher-level wrapper like the Fluent API instead; they can use faster code paths")] public Stream GetOutputBuffer(int ioId) { - if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) + if (!_ioSet.TryGetValue(ioId, out var value) || value != IoKind.OutputBuffer) { throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); } @@ -669,7 +661,7 @@ public Stream GetOutputBuffer(int ioId) /// internal unsafe Span BorrowOutputBuffer(int ioId) { - if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) + if (!_ioSet.TryGetValue(ioId, out var value) || value != IoKind.OutputBuffer) { throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); } @@ -693,7 +685,7 @@ internal unsafe Span BorrowOutputBuffer(int ioId) /// internal IMemoryOwner BorrowOutputBufferMemoryAndAddReference(int ioId) { - if (!_ioSet.ContainsKey(ioId) || _ioSet[ioId] != IoKind.OutputBuffer) + if (!_ioSet.TryGetValue(ioId, out var value) || value != IoKind.OutputBuffer) { throw new ArgumentException($"ioId {ioId} does not correspond to an output buffer", nameof(ioId)); } @@ -721,10 +713,7 @@ internal void RemoveRef() public bool IsDisposed => !_handle.IsValid; public void Dispose() { - if (IsDisposed) - { - throw new ObjectDisposedException("Imageflow JobContext"); - } + ObjectDisposedHelper.ThrowIf(IsDisposed, this); if (Interlocked.Exchange(ref _refCount, 0) > 0) { diff --git a/src/Imageflow/Bindings/JobContextHandle.cs b/src/Imageflow/Bindings/JobContextHandle.cs index 377ea7e..0d29321 100644 --- a/src/Imageflow/Bindings/JobContextHandle.cs +++ b/src/Imageflow/Bindings/JobContextHandle.cs @@ -9,7 +9,7 @@ namespace Imageflow.Bindings; /// /// The handle is ready even if there is an error condition stored in the context. /// -/// AddRef and Release should be called. +/// AddRef and Release should be called. /// internal sealed class JobContextHandle : SafeHandleZeroOrMinusOneIsInvalid, IAssertReady { @@ -39,10 +39,7 @@ public JobContextHandle() public void AssertReady() { - if (!IsValid) - { - throw new ObjectDisposedException("Imageflow JobContextHandle"); - } + ObjectDisposedHelper.ThrowIf(!IsValid, this); } public ImageflowException? DisposeAllowingException() diff --git a/src/Imageflow/Bindings/JsonResponse.cs b/src/Imageflow/Bindings/JsonResponse.cs index 2c984f4..09a8ccf 100644 --- a/src/Imageflow/Bindings/JsonResponse.cs +++ b/src/Imageflow/Bindings/JsonResponse.cs @@ -99,10 +99,7 @@ internal ImageflowJsonResponse(JsonResponseHandle ptr) public void AssertReady() { _handle.ParentContext.AssertReady(); - if (!_handle.IsValid) - { - throw new ObjectDisposedException("Imageflow JsonResponse"); - } + ObjectDisposedHelper.ThrowIf(!_handle.IsValid, this); } private void Read(out int statusCode, out IntPtr utf8Buffer, out UIntPtr bufferSize) diff --git a/src/Imageflow/Bindings/JsonResponseHandle.cs b/src/Imageflow/Bindings/JsonResponseHandle.cs index b522763..a91407f 100644 --- a/src/Imageflow/Bindings/JsonResponseHandle.cs +++ b/src/Imageflow/Bindings/JsonResponseHandle.cs @@ -8,7 +8,7 @@ namespace Imageflow.Bindings; /// /// A child SafeHandle that increments the reference count on JobContextHandle when created and decrements it when disposed. -/// +/// /// internal sealed class JsonResponseHandle : SafeHandleZeroOrMinusOneIsInvalid, IAssertReady { @@ -33,15 +33,8 @@ public JsonResponseHandle(JobContextHandle parent, IntPtr ptr) public void AssertReady() { - if (!ParentContext.IsValid) - { - throw new ObjectDisposedException("Imageflow JobContextHandle"); - } - - if (!IsValid) - { - throw new ObjectDisposedException("Imageflow JsonResponseHandle"); - } + ObjectDisposedHelper.ThrowIf(!ParentContext.IsValid, ParentContext); + ObjectDisposedHelper.ThrowIf(!IsValid, this); } #pragma warning disable SYSLIB0004 @@ -51,7 +44,7 @@ protected override bool ReleaseHandle() { // The base class, the caller, handles interlocked / sync and preventing multiple calls. // We check ParentContext just in case someone went wild with DangerousRelease elsewhere. - // It's a process-ending error if ParentContext is invalid. + // It's a process-ending error if ParentContext is invalid. if (ParentContext.IsValid) { NativeMethods.imageflow_json_response_destroy(ParentContext, handle); diff --git a/src/Imageflow/Bindings/NativeLibraryLoading.cs b/src/Imageflow/Bindings/NativeLibraryLoading.cs index 0b048c8..c17849f 100644 --- a/src/Imageflow/Bindings/NativeLibraryLoading.cs +++ b/src/Imageflow/Bindings/NativeLibraryLoading.cs @@ -45,7 +45,7 @@ internal void RaiseException() { var sb = new StringBuilder(_log.Select((e) => e.Basename?.Length ?? 0 + e.FullPath?.Length ?? 0 + 20) .Sum()); - sb.AppendFormat("Looking for \"{0}\" RID=\"{1}-{2}\", IsUnix={3}, IsDotNetCore={4} RelativeSearchPath=\"{5}\"\n", + sb.AppendFormat(CultureInfo.InvariantCulture, "Looking for \"{0}\" RID=\"{1}-{2}\", IsUnix={3}, IsDotNetCore={4} RelativeSearchPath=\"{5}\"\n", Filename, RuntimeFileLocator.PlatformRuntimePrefix.Value, RuntimeFileLocator.ArchitectureSubdir.Value, RuntimeFileLocator.IsUnix, @@ -53,18 +53,18 @@ internal void RaiseException() AppDomain.CurrentDomain.RelativeSearchPath); if (FirstException != null) { - sb.AppendFormat("Before searching: {0}\n", FirstException.Message); + sb.AppendFormat(CultureInfo.InvariantCulture, "Before searching: {0}\n", FirstException.Message); } foreach (var e in _log) { if (e.PreviouslyLoaded) { - sb.AppendFormat("\"{0}\" is already {1}", e.Basename, Verb); + sb.AppendFormat(CultureInfo.InvariantCulture, "\"{0}\" is already {1}", e.Basename, Verb); } else if (!e.FileExists) { - sb.AppendFormat("File not found: {0}", e.FullPath); + sb.AppendFormat(CultureInfo.InvariantCulture, "File not found: {0}", e.FullPath); } else if (e.LoadErrorCode.HasValue) { @@ -72,7 +72,7 @@ internal void RaiseException() ? string.Format(CultureInfo.InvariantCulture, "0x{0:X8}", e.LoadErrorCode.Value) : e.LoadErrorCode.Value.ToString(CultureInfo.InvariantCulture); - sb.AppendFormat("Error \"{0}\" ({1}) loading {2} from {3}", + sb.AppendFormat(CultureInfo.InvariantCulture, "Error \"{0}\" ({1}) loading {2} from {3}", new Win32Exception(e.LoadErrorCode.Value).Message, errorCode, e.Basename, e.FullPath); @@ -83,7 +83,7 @@ internal void RaiseException() var installed = Environment.Is64BitProcess ? "32-bit (x86)" : "64-bit (x86_64)"; var needed = Environment.Is64BitProcess ? "64-bit (x86_64)" : "32-bit (x86)"; - sb.AppendFormat("\n> You have installed a {0} copy of imageflow.dll but need the {1} version", + sb.AppendFormat(CultureInfo.InvariantCulture, "\n> You have installed a {0} copy of imageflow.dll but need the {1} version", installed, needed); } @@ -93,12 +93,12 @@ internal void RaiseException() var crtLink = "https://aka.ms/vs/16/release/vc_redist." + (Environment.Is64BitProcess ? "x64.exe" : "x86.exe"); - sb.AppendFormat("\n> You may need to install the C Runtime from {0}", crtLink); + sb.AppendFormat(CultureInfo.InvariantCulture, "\n> You may need to install the C Runtime from {0}", crtLink); } } else { - sb.AppendFormat("{0} {1} in {2}", Verb, e.Basename, e.FullPath); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0} {1} in {2}", Verb, e.Basename, e.FullPath); } sb.Append('\n'); } @@ -262,7 +262,7 @@ private static IEnumerable> BaseFolders(IEnumerable? try{ // Look in the folder that *this* assembly is located. assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - + } catch (NotImplementedException){ // ignored } @@ -352,7 +352,7 @@ private static string GetFilenameWithoutDirectory(string basename) => RuntimeFil private static readonly Lazy> ExecutablePathsByName = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); - // Not yet implemented. + // Not yet implemented. // static readonly Lazy> LibraryHandlesByFullPath = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); /// @@ -429,7 +429,7 @@ internal static class NativeLibraryLoader caughtException = b; } - //Try loading + //Try loading var logger = new LoadLogger { FirstException = caughtException, Filename = GetFilenameWithoutDirectory(basename) }; if (TryLoadByBasename(basename, logger, out _, customSearchDirectories)) @@ -449,7 +449,7 @@ internal static class NativeLibraryLoader private static readonly Lazy> LibraryHandlesByBasename = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); - // Not yet implemented. + // Not yet implemented. // static readonly Lazy> LibraryHandlesByFullPath = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly); /// @@ -536,7 +536,7 @@ internal static class WindowsLoadLibrary public static IntPtr Execute(string fileName) { - // Look in the library dir instead of the process dir + // Look in the library dir instead of the process dir const uint loadWithAlteredSearchPath = 0x00000008; return LoadLibraryEx(fileName, IntPtr.Zero, loadWithAlteredSearchPath); } diff --git a/src/Imageflow/Fluent/IOutputDestinationExtensions.cs b/src/Imageflow/Fluent/IOutputDestinationExtensions.cs index 1f0aff5..3ffe650 100644 --- a/src/Imageflow/Fluent/IOutputDestinationExtensions.cs +++ b/src/Imageflow/Fluent/IOutputDestinationExtensions.cs @@ -101,7 +101,9 @@ internal static async Task CopyFromStreamAsyncInternal(this IOutputDestination d int bytesRead; while ((bytesRead = +#pragma warning disable CA1835 await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) +#pragma warning restore CA1835 { await dest.WriteAsync(new ArraySegment(buffer, 0, bytesRead), cancellationToken) .ConfigureAwait(false); diff --git a/src/Imageflow/Fluent/ImageJob.cs b/src/Imageflow/Fluent/ImageJob.cs index b5cc545..127081d 100644 --- a/src/Imageflow/Fluent/ImageJob.cs +++ b/src/Imageflow/Fluent/ImageJob.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using System.Diagnostics; +using System.Globalization; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; @@ -623,7 +624,7 @@ private static JsonNode ToFramewiseGraph(ICollection uniqueNodes) var nodes = new JsonObject(); foreach (var n in uniqueNodes) { - nodes.Add((n.Uid - lowestUid).ToString(), n.NodeData); + nodes.Add((n.Uid - lowestUid).ToString(CultureInfo.InvariantCulture), n.NodeData); } // return new // { @@ -645,10 +646,7 @@ private static JsonNode ToFramewiseGraph(ICollection uniqueNodes) private void AssertReady() { - if (_disposed) - { - throw new ObjectDisposedException("ImageJob"); - } + ObjectDisposedHelper.ThrowIf(_disposed, this); } public void Dispose() diff --git a/src/Imageflow/Fluent/MagicBytes.cs b/src/Imageflow/Fluent/MagicBytes.cs index c72e2cb..69b51d4 100644 --- a/src/Imageflow/Fluent/MagicBytes.cs +++ b/src/Imageflow/Fluent/MagicBytes.cs @@ -484,7 +484,7 @@ internal enum ImageFormat break; default: - throw new ArgumentOutOfRangeException(); + throw new NotImplementedException(); } return null; } diff --git a/src/Imageflow/Fluent/PerformanceDetails.cs b/src/Imageflow/Fluent/PerformanceDetails.cs index 6b04410..2409c36 100644 --- a/src/Imageflow/Fluent/PerformanceDetails.cs +++ b/src/Imageflow/Fluent/PerformanceDetails.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Text; using System.Text.Json.Nodes; @@ -16,7 +17,7 @@ internal PerformanceDetails(JsonNode? perf) // foreach (var f in perf.frames) // { // frames.Add(new PerformanceDetailsFrame(f)); - // } + // } if (obj.TryGetPropertyValue("frames", out var framesValue)) { foreach (var f in framesValue?.AsArray() ?? []) @@ -33,7 +34,7 @@ public string GetFirstFrameSummary() var sb = new StringBuilder(); if (Frames.Count > 1) { - sb.Append($"First of {Frames.Count} frames: "); + sb.Append(string.Format(CultureInfo.InvariantCulture,"First of {0} frames: ", Frames.Count)); } else if (Frames.Count == 0) { @@ -44,7 +45,7 @@ public string GetFirstFrameSummary() { sb.Append(n.Name); sb.Append('('); - sb.Append((n.WallMicroseconds / 1000.0).ToString("0.####")); + sb.Append((n.WallMicroseconds / 1000.0).ToString( "0.####",CultureInfo.InvariantCulture)); sb.Append("ms) "); } diff --git a/src/Imageflow/Fluent/SrgbColor.cs b/src/Imageflow/Fluent/SrgbColor.cs index e0719f0..886ca65 100644 --- a/src/Imageflow/Fluent/SrgbColor.cs +++ b/src/Imageflow/Fluent/SrgbColor.cs @@ -49,7 +49,7 @@ private static byte Expand4(uint v, int index) public static SrgbColor FromHex(string s) { s = s.TrimStart('#'); - var v = uint.Parse(s, NumberStyles.HexNumber); + var v = uint.Parse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture); switch (s.Length) { case 3: return RGBA(Expand4(v, 2), Expand4(v, 1), Expand4(v, 0), 0xff); diff --git a/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs b/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs index cd6c5f3..0921757 100644 --- a/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs +++ b/src/Imageflow/Internal.Helpers/ArgumentNullThrowHelperPolyfill.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -24,7 +25,7 @@ public static void ThrowIfNull( Throw(paramName); } #else - Argument.ThrowIfNull(argument, paramName); + ArgumentNullException.ThrowIfNull(argument, paramName); #endif } @@ -39,6 +40,7 @@ internal static void Throw(string? paramName) => internal static class ObjectDisposedHelper { + [StackTraceHidden] public static void ThrowIf([DoesNotReturnIf(true)] bool condition, object instance) { #if NET8_0_OR_GREATER diff --git a/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs b/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs index c81ac96..05bf65f 100644 --- a/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs +++ b/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs @@ -29,7 +29,9 @@ static async Task WriteAsyncFallback(Stream stream, ReadOnlyMemory buffer, { buffer.Span.CopyTo(rent); +#pragma warning disable CA1835 await stream.WriteAsync(rent, 0, buffer.Length, cancellationToken).ConfigureAwait(false); +#pragma warning restore CA1835 } finally { From aaf2fe813dfd5052974ed29284e099143a8cbac4 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 23:11:19 -0700 Subject: [PATCH 17/22] If used with an incompatible Imageflow ABI, now throws NotSupportedException instead of Exception. If JobContext creation fails, an ImageflowException is thrown instead of an OutOfMemoryException --- src/Imageflow/Bindings/JobContextHandle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Imageflow/Bindings/JobContextHandle.cs b/src/Imageflow/Bindings/JobContextHandle.cs index 0d29321..ab09291 100644 --- a/src/Imageflow/Bindings/JobContextHandle.cs +++ b/src/Imageflow/Bindings/JobContextHandle.cs @@ -25,11 +25,11 @@ public JobContextHandle() if (NativeMethods.imageflow_abi_compatible(NativeMethods.ABI_MAJOR, NativeMethods.ABI_MINOR)) { - throw new OutOfMemoryException("Failed to create Imageflow JobContext"); + throw new ImageflowException("Failed to create Imageflow JobContext (yet other calls succeed); this may indicate an out-of-memory condition."); } var major = NativeMethods.imageflow_abi_version_major(); var minor = NativeMethods.imageflow_abi_version_minor(); - throw new Exception( + throw new NotSupportedException( $".NET Imageflow bindings only support ABI {NativeMethods.ABI_MAJOR}.{NativeMethods.ABI_MINOR}. libimageflow ABI {major}.{minor} is loaded."); } SetHandle(ptr); From 3db69d6cbbf495df25d3259fdd845fb1fec12489 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 23:11:43 -0700 Subject: [PATCH 18/22] Reduce test warnings --- tests/Imageflow.Test/TestApi.cs | 6 ++++-- tests/Imageflow.Test/TestJson.cs | 5 +++-- tests/Imageflow.TestWebAOT/Program.cs | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/Imageflow.Test/TestApi.cs b/tests/Imageflow.Test/TestApi.cs index 06dc773..6aeed4b 100644 --- a/tests/Imageflow.Test/TestApi.cs +++ b/tests/Imageflow.Test/TestApi.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Drawing; using Imageflow.Bindings; @@ -8,6 +9,7 @@ namespace Imageflow.Test { + [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task")] public class TestApi { private readonly ITestOutputHelper output; @@ -49,7 +51,7 @@ public async void TestGetImageInfoAsync() Assert.Equal(PixelFormat.Bgra_32, info.FrameDecodesInto); } - // Test GetImageInfo + // Test GetImageInfo [Fact] public void TestGetImageInfoSync() { @@ -403,7 +405,7 @@ public async Task TestFilesystemJobPrep() using (var file = System.IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(jsonPath)) #pragma warning restore CA1416 { - } // Will throw filenotfoundexception if missing + } // Will throw filenotfoundexception if missing } } diff --git a/tests/Imageflow.Test/TestJson.cs b/tests/Imageflow.Test/TestJson.cs index db9e07f..57f4ce8 100644 --- a/tests/Imageflow.Test/TestJson.cs +++ b/tests/Imageflow.Test/TestJson.cs @@ -50,7 +50,8 @@ public void TestAllJob() .RoundAllImageCornersPercent(100, AnyColor.Black) .RoundAllImageCorners(1, AnyColor.Transparent) .ConstrainWithin(5, 5) - .Watermark(new MemorySource(new byte[] { }), + // ReSharper disable once UseCollectionExpression + .Watermark(new MemorySource(Array.Empty()), new WatermarkOptions() .SetMarginsLayout( new WatermarkMargins(WatermarkAlign.Image, 1, 1, 1, 1), @@ -144,7 +145,7 @@ public void TestAllJob() { return null; } - throw new Exception("Unexpected node type: " + n?.GetType()); + throw new NotSupportedException("Unexpected node type: " + n?.GetType()); } } diff --git a/tests/Imageflow.TestWebAOT/Program.cs b/tests/Imageflow.TestWebAOT/Program.cs index c895161..cf3edbe 100644 --- a/tests/Imageflow.TestWebAOT/Program.cs +++ b/tests/Imageflow.TestWebAOT/Program.cs @@ -24,7 +24,7 @@ }); imageflowApi.MapGet("/resize/width/{width}", async (int width) => { - var resultMemory = await Helpers.SizeIcon(width); + var resultMemory = await Helpers.SizeIcon(width).ConfigureAwait(false); return Results.Bytes(resultMemory, "image/jpeg"); }); @@ -44,7 +44,7 @@ public static async Task> SizeIcon(int width) var job = await new ImageJob() .Decode(imgBytes) .Constrain(new Constraint((uint)width, 0)) - .EncodeToBytes(new MozJpegEncoder(90)).Finish().InProcessAsync(); + .EncodeToBytes(new MozJpegEncoder(90)).Finish().InProcessAsync().ConfigureAwait(false); var resultBytes = job.First!.TryGetBytes()!.Value; return new Memory(resultBytes.Array, resultBytes.Offset, resultBytes.Count); } @@ -65,4 +65,4 @@ private static byte[] GetResourceBytes(string resourceName) [JsonSerializable(typeof(VersionInfo))] -internal partial class AppJsonSerializerContext : JsonSerializerContext; \ No newline at end of file +internal sealed partial class AppJsonSerializerContext : JsonSerializerContext; From efb2ec1028f65f111203d8c78869504dfc52d03d Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 23:29:09 -0700 Subject: [PATCH 19/22] Move unused code to Unused internal namespace --- src/Imageflow/Fluent/BufferedStreamSource.cs | 8 ++-- src/Imageflow/Fluent/ImageJob.cs | 35 +---------------- .../StreamMemoryExtensionsPolyfills.cs | 2 + .../{ => Unused}/OutputDestinationWrapper.cs | 0 .../Internal.Helpers/{ => Unused}/PinBox.cs | 2 +- .../Unused/StreamJsonSpanProvider.cs | 39 +++++++++++++++++++ .../{ => Unused}/UnmanagedMemoryManager.cs | 2 +- tests/Imageflow.Test/TestJobContext.cs | 3 +- 8 files changed, 50 insertions(+), 41 deletions(-) rename src/Imageflow/Internal.Helpers/{ => Unused}/OutputDestinationWrapper.cs (100%) rename src/Imageflow/Internal.Helpers/{ => Unused}/PinBox.cs (94%) create mode 100644 src/Imageflow/Internal.Helpers/Unused/StreamJsonSpanProvider.cs rename src/Imageflow/Internal.Helpers/{ => Unused}/UnmanagedMemoryManager.cs (96%) diff --git a/src/Imageflow/Fluent/BufferedStreamSource.cs b/src/Imageflow/Fluent/BufferedStreamSource.cs index 208f560..cd2b69b 100644 --- a/src/Imageflow/Fluent/BufferedStreamSource.cs +++ b/src/Imageflow/Fluent/BufferedStreamSource.cs @@ -6,7 +6,7 @@ namespace Imageflow.Fluent; public sealed class BufferedStreamSource : IAsyncMemorySource, IMemorySource { - private BufferedStreamSource(Stream stream, bool disposeUnderlying, bool seekToStart = true) + private BufferedStreamSource(Stream stream, bool disposeUnderlying, bool seekToStart) { if (stream.Position != 0 && !stream.CanSeek && seekToStart) { @@ -129,7 +129,7 @@ public static IAsyncMemorySource BorrowEntireStream(Stream stream) } /// /// You remain responsible for disposing and cleaning up the stream after the job is disposed. - /// Only reads from the current position to the end of the image file. + /// Only reads from the current position to the end of the image file. /// You swear not to close, dispose, or reuse the stream or its underlying memory/stream until after this wrapper and the job are disposed. /// /// @@ -154,8 +154,8 @@ public static IAsyncMemorySource UseEntireStreamAndDisposeWithSource(Stream stre /// /// The stream will be closed and disposed with the BufferedStreamSource. - /// You must not close, dispose, or reuse the stream or its underlying streams/buffers until after the job and the owning objects are disposed. - /// strong>The BufferedStreamSource will still need to be disposed after the job, either with a using declaration or by transferring ownership of it to the job (which should be in a using declaration). + /// You must not close, dispose, or reuse the stream or its underlying streams/buffers until after the job and the owning objects are disposed. + /// The BufferedStreamSource will still need to be disposed after the job, either with a using declaration or by transferring ownership of it to the job (which should be in a using declaration). /// /// /// diff --git a/src/Imageflow/Fluent/ImageJob.cs b/src/Imageflow/Fluent/ImageJob.cs index 127081d..1d455b1 100644 --- a/src/Imageflow/Fluent/ImageJob.cs +++ b/src/Imageflow/Fluent/ImageJob.cs @@ -300,40 +300,6 @@ internal async Task FinishAsync(JobExecutionOptions executionOpt } } -#pragma warning disable CS0618 // Type or member is obsolete - private class StreamJsonSpanProvider : IJsonResponseProvider, IJsonResponseSpanProvider, IJsonResponse -#pragma warning restore CS0618 // Type or member is obsolete - { - private readonly MemoryStream _ms; - - public StreamJsonSpanProvider(int statusCode, MemoryStream ms) - { - ImageflowErrorCode = statusCode; - _ms = ms; - } - public void Dispose() => _ms.Dispose(); - public Stream GetStream() => _ms; - public ReadOnlySpan BorrowBytes() - { - return _ms.TryGetBufferSliceAllWrittenData(out var slice) ? slice : _ms.ToArray(); - } - - public int ImageflowErrorCode { get; } - public string CopyString() - { - return BorrowBytes().Utf8ToString(); - } - - public JsonNode? Parse() - { - return BorrowBytes().ParseJsonNode(); - } - - public byte[] CopyBytes() - { - return BorrowBytes().ToArray(); - } - } // private object BuildJsonWithPlaceholders() // { @@ -803,3 +769,4 @@ public static bool CanDecodeBytes(byte[] first12Bytes) return MagicBytes.GetImageContentType(first12Bytes); } } + diff --git a/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs b/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs index 05bf65f..acfb682 100644 --- a/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs +++ b/src/Imageflow/Internal.Helpers/StreamMemoryExtensionsPolyfills.cs @@ -1,5 +1,7 @@ +#if !NETSTANDARD2_1_OR_GREATER using System.Buffers; using System.Runtime.InteropServices; +#endif namespace Imageflow.Internal.Helpers; diff --git a/src/Imageflow/Internal.Helpers/OutputDestinationWrapper.cs b/src/Imageflow/Internal.Helpers/Unused/OutputDestinationWrapper.cs similarity index 100% rename from src/Imageflow/Internal.Helpers/OutputDestinationWrapper.cs rename to src/Imageflow/Internal.Helpers/Unused/OutputDestinationWrapper.cs diff --git a/src/Imageflow/Internal.Helpers/PinBox.cs b/src/Imageflow/Internal.Helpers/Unused/PinBox.cs similarity index 94% rename from src/Imageflow/Internal.Helpers/PinBox.cs rename to src/Imageflow/Internal.Helpers/Unused/PinBox.cs index 8517ade..e15fd76 100644 --- a/src/Imageflow/Internal.Helpers/PinBox.cs +++ b/src/Imageflow/Internal.Helpers/Unused/PinBox.cs @@ -1,7 +1,7 @@ using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; -namespace Imageflow.Internal.Helpers; +namespace Imageflow.Internal.Helpers.Unused; internal class PinBox : CriticalFinalizerObject, IDisposable { diff --git a/src/Imageflow/Internal.Helpers/Unused/StreamJsonSpanProvider.cs b/src/Imageflow/Internal.Helpers/Unused/StreamJsonSpanProvider.cs new file mode 100644 index 0000000..b2410f9 --- /dev/null +++ b/src/Imageflow/Internal.Helpers/Unused/StreamJsonSpanProvider.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Nodes; +using Imageflow.Bindings; + +namespace Imageflow.Internal.Helpers.Unused; + +#pragma warning disable CS0618 +internal class StreamJsonSpanProvider : IJsonResponseProvider, IJsonResponseSpanProvider, IJsonResponse +#pragma warning restore CS0618 // Type or member is obsolete +{ + private readonly MemoryStream _ms; + + public StreamJsonSpanProvider(int statusCode, MemoryStream ms) + { + ImageflowErrorCode = statusCode; + _ms = ms; + } + public void Dispose() => _ms.Dispose(); + public Stream GetStream() => _ms; + public ReadOnlySpan BorrowBytes() + { + return _ms.TryGetBufferSliceAllWrittenData(out var slice) ? slice : _ms.ToArray(); + } + + public int ImageflowErrorCode { get; } + public string CopyString() + { + return BorrowBytes().Utf8ToString(); + } + + public JsonNode? Parse() + { + return BorrowBytes().ParseJsonNode(); + } + + public byte[] CopyBytes() + { + return BorrowBytes().ToArray(); + } +} diff --git a/src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs b/src/Imageflow/Internal.Helpers/Unused/UnmanagedMemoryManager.cs similarity index 96% rename from src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs rename to src/Imageflow/Internal.Helpers/Unused/UnmanagedMemoryManager.cs index 460274a..fd23a79 100644 --- a/src/Imageflow/Internal.Helpers/UnmanagedMemoryManager.cs +++ b/src/Imageflow/Internal.Helpers/Unused/UnmanagedMemoryManager.cs @@ -1,7 +1,7 @@ using System.Buffers; using System.Runtime.CompilerServices; -namespace Imageflow.Internal.Helpers; +namespace Imageflow.Internal.Helpers.Unused; internal unsafe class UnmanagedMemoryManager : MemoryManager { diff --git a/tests/Imageflow.Test/TestJobContext.cs b/tests/Imageflow.Test/TestJobContext.cs index 2d853c3..7149267 100644 --- a/tests/Imageflow.Test/TestJobContext.cs +++ b/tests/Imageflow.Test/TestJobContext.cs @@ -8,8 +8,9 @@ using Xunit; using Xunit.Abstractions; - +#if NET8_0_OR_GREATER using JsonNamingPolicy = System.Text.Json.JsonNamingPolicy; +#endif using JsonSerializerOptions = System.Text.Json.JsonSerializerOptions; #pragma warning disable CS0618 // Type or member is obsolete From 05eb4ad403359ee6d4022e32cd6489c00fa63917 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 23:29:40 -0700 Subject: [PATCH 20/22] Make StreamDestination refuse to work after disposal --- src/Imageflow/Fluent/StreamDestination.cs | 29 ++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Imageflow/Fluent/StreamDestination.cs b/src/Imageflow/Fluent/StreamDestination.cs index db5d4a9..8fca871 100644 --- a/src/Imageflow/Fluent/StreamDestination.cs +++ b/src/Imageflow/Fluent/StreamDestination.cs @@ -5,19 +5,23 @@ namespace Imageflow.Fluent; public class StreamDestination(Stream underlying, bool disposeUnderlying) : IOutputDestination { + private Stream? _underlying = underlying; + public void Dispose() { if (disposeUnderlying) { - underlying?.Dispose(); + _underlying?.Dispose(); } + _underlying = null!; } public Task RequestCapacityAsync(int bytes) { - if (underlying is { CanSeek: true, CanWrite: true }) + ObjectDisposedHelper.ThrowIf(_underlying == null, this); + if (_underlying is { CanSeek: true, CanWrite: true }) { - underlying.SetLength(bytes); + _underlying.SetLength(bytes); } return Task.CompletedTask; @@ -29,28 +33,31 @@ public Task WriteAsync(ArraySegment bytes, CancellationToken cancellationT { throw new ImageflowAssertionFailed("StreamDestination.WriteAsync called with null array"); } - - return underlying.WriteAsync(bytes.Array, bytes.Offset, bytes.Count, cancellationToken); + ObjectDisposedHelper.ThrowIf(_underlying == null, this); + return _underlying.WriteAsync(bytes.Array, bytes.Offset, bytes.Count, cancellationToken); } public ValueTask WriteAsync(ReadOnlyMemory bytes, CancellationToken cancellationToken) { - return underlying.WriteMemoryAsync(bytes, cancellationToken); + ObjectDisposedHelper.ThrowIf(_underlying == null, this); + return _underlying.WriteMemoryAsync(bytes, cancellationToken); } public void Write(ReadOnlySpan bytes) { - underlying.WriteSpan(bytes); + ObjectDisposedHelper.ThrowIf(_underlying == null, this); + _underlying.WriteSpan(bytes); } public Task FlushAsync(CancellationToken cancellationToken) { - if (underlying is { CanSeek: true, CanWrite: true } - && underlying.Position < underlying.Length) + ObjectDisposedHelper.ThrowIf(_underlying == null, this); + if (_underlying is { CanSeek: true, CanWrite: true } + && _underlying.Position < _underlying.Length) { - underlying.SetLength(underlying.Position); + _underlying.SetLength(_underlying.Position); } - return underlying.FlushAsync(cancellationToken); + return _underlying.FlushAsync(cancellationToken); } } From 9b90b5f37d09d4745e8f3100b268d28c59ce9e11 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 23:30:00 -0700 Subject: [PATCH 21/22] Make StreamSource refuse to work after disposal --- src/Imageflow/Fluent/StreamSource.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Imageflow/Fluent/StreamSource.cs b/src/Imageflow/Fluent/StreamSource.cs index a648720..52f7aea 100644 --- a/src/Imageflow/Fluent/StreamSource.cs +++ b/src/Imageflow/Fluent/StreamSource.cs @@ -5,21 +5,24 @@ namespace Imageflow.Fluent; /// -/// Represents a image source that is backed by a Stream. +/// Represents a image source that is backed by a Stream. /// [Obsolete("Use BufferedStreamSource.UseStreamRemainderAndDispose() or BufferedStreamSource.BorrowStreamRemainder() instead")] public class StreamSource(Stream underlying, bool disposeUnderlying) : IBytesSource { - private static readonly RecyclableMemoryStreamManager Mgr + private static readonly RecyclableMemoryStreamManager _mgr = new(); private RecyclableMemoryStream? _copy; + private Stream? _underlying = underlying; public void Dispose() { if (disposeUnderlying) { - underlying?.Dispose(); + _underlying?.Dispose(); } + + _underlying = null; _copy?.Dispose(); } @@ -31,18 +34,19 @@ public void Dispose() /// public async Task> GetBytesAsync(CancellationToken cancellationToken) { + ObjectDisposedHelper.ThrowIf(_underlying == null, this); if (_copy != null) { return new ArraySegment(_copy.GetBuffer(), 0, (int)_copy.Length); } - var length = underlying.CanSeek ? underlying.Length : 0; + var length = _underlying.CanSeek ? _underlying.Length : 0; if (length >= int.MaxValue) { throw new OverflowException("Streams cannot exceed 2GB"); } - if (underlying is MemoryStream underlyingMemoryStream && + if (_underlying is MemoryStream underlyingMemoryStream && underlyingMemoryStream.TryGetBufferSliceAllWrittenData(out var underlyingBuffer)) { return underlyingBuffer; @@ -50,15 +54,15 @@ public async Task> GetBytesAsync(CancellationToken cancellati if (_copy == null) { - _copy = new RecyclableMemoryStream(Mgr, "StreamSource: IBytesSource", length); - await underlying.CopyToAsync(_copy, 81920, cancellationToken).ConfigureAwait(false); + _copy = new RecyclableMemoryStream(_mgr, "StreamSource: IBytesSource", length); + await _underlying.CopyToAsync(_copy, 81920, cancellationToken).ConfigureAwait(false); } return new ArraySegment(_copy.GetBuffer(), 0, (int)_copy.Length); } - internal bool AsyncPreferred => _copy != null && underlying is not MemoryStream && underlying is not UnmanagedMemoryStream; + internal bool AsyncPreferred => _copy != null && _underlying is not MemoryStream && _underlying is not UnmanagedMemoryStream; public static implicit operator BytesSourceAdapter(StreamSource source) { From 7b8e19d88ccf1fa62cb367a155de57cc402668b4 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 7 Mar 2024 23:30:46 -0700 Subject: [PATCH 22/22] Fix some warnings --- src/Imageflow/Bindings/NativeLibraryLoading.cs | 9 ++++----- src/Imageflow/IO/ProcessEx.cs | 4 ++-- tests/Imageflow.Test/TestApi.cs | 8 ++++---- tests/Imageflow.TestWebAOT/Program.cs | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Imageflow/Bindings/NativeLibraryLoading.cs b/src/Imageflow/Bindings/NativeLibraryLoading.cs index c17849f..fbf6453 100644 --- a/src/Imageflow/Bindings/NativeLibraryLoading.cs +++ b/src/Imageflow/Bindings/NativeLibraryLoading.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using System.Security; using System.Text; +using Imageflow.Internal.Helpers; #if !NET8_0_OR_GREATER using System.Reflection; #endif @@ -31,6 +32,7 @@ private struct LogEntry public void NotifyAttempt(string basename, string? fullPath, bool fileExists, bool previouslyLoaded, int? loadErrorCode) { + Argument.ThrowIfNull(basename); _log.Add(new LogEntry { Basename = basename, @@ -43,7 +45,7 @@ public void NotifyAttempt(string basename, string? fullPath, bool fileExists, bo internal void RaiseException() { - var sb = new StringBuilder(_log.Select((e) => e.Basename?.Length ?? 0 + e.FullPath?.Length ?? 0 + 20) + var sb = new StringBuilder(_log.Select((e) => e.Basename.Length + (e.FullPath?.Length ?? 0) + 20) .Sum()); sb.AppendFormat(CultureInfo.InvariantCulture, "Looking for \"{0}\" RID=\"{1}-{2}\", IsUnix={3}, IsDotNetCore={4} RelativeSearchPath=\"{5}\"\n", Filename, @@ -368,10 +370,7 @@ private static string GetFilenameWithoutDirectory(string basename) => RuntimeFil internal static bool TryLoadByBasename(string basename, ILibraryLoadLogger log, out string? exePath, IEnumerable? customSearchDirectories = null) { - if (string.IsNullOrEmpty(basename)) - { - throw new ArgumentNullException(nameof(basename)); - } + Argument.ThrowIfNull(basename); if (ExecutablePathsByName.Value.TryGetValue(basename, out exePath)) { diff --git a/src/Imageflow/IO/ProcessEx.cs b/src/Imageflow/IO/ProcessEx.cs index 00b0b4f..efafb14 100644 --- a/src/Imageflow/IO/ProcessEx.cs +++ b/src/Imageflow/IO/ProcessEx.cs @@ -100,9 +100,9 @@ public static async Task RunAsync(ProcessStartInfo processStartI } }; - process.Exited += (sender, args) => + process.Exited += (_, args) => { - // Since the Exited event can happen asynchronously to the output and error events, + // Since the Exited event can happen asynchronously to the output and error events, // we use the task results for stdout/stderr to ensure they both closed tcs.TrySetResult(new ProcessResults(process, standardOutputResults.Task.Result, standardErrorResults.Task.Result)); }; diff --git a/tests/Imageflow.Test/TestApi.cs b/tests/Imageflow.Test/TestApi.cs index 6aeed4b..cba1968 100644 --- a/tests/Imageflow.Test/TestApi.cs +++ b/tests/Imageflow.Test/TestApi.cs @@ -402,7 +402,7 @@ public async Task TestFilesystemJobPrep() else { #pragma warning disable CA1416 - using (var file = System.IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(jsonPath)) + using (var _ = System.IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(jsonPath)) #pragma warning restore CA1416 { } // Will throw filenotfoundexception if missing @@ -416,11 +416,11 @@ public async Task TestFilesystemJobPrep() else { - Assert.Throws(delegate () + Assert.Throws(delegate { #pragma warning disable CA1416 - using (var file = System.IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(jsonPath)) + using (var _ = System.IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(jsonPath)) #pragma warning restore CA1416 { } @@ -473,7 +473,7 @@ public async Task TestCustomDownscalingAndDecodeEncodeResults() Assert.Equal(5, r.First!.Width); Assert.True(r.First.TryGetBytes().HasValue); - Assert.Equal(1, r.DecodeResults.First()!.Width); + Assert.Equal(1, r.DecodeResults.First().Width); Assert.Equal(1, r.DecodeResults.First().Height); Assert.Equal("png", r.DecodeResults.First().PreferredExtension); Assert.Equal("image/png", r.DecodeResults.First().PreferredMimeType); diff --git a/tests/Imageflow.TestWebAOT/Program.cs b/tests/Imageflow.TestWebAOT/Program.cs index cf3edbe..1d7c928 100644 --- a/tests/Imageflow.TestWebAOT/Program.cs +++ b/tests/Imageflow.TestWebAOT/Program.cs @@ -32,7 +32,7 @@ using var c = new JobContext(); var _ = c.GetVersionInfo(); var t = Helpers.SizeIcon(10); -var _2 = t.Result; +var unused = t.Result; app.Run();