Skip to content

Commit

Permalink
Finish achieving API compatibility and public API changes for source …
Browse files Browse the repository at this point in the history
…providers.
  • Loading branch information
lilith committed Mar 7, 2024
1 parent 92a67e2 commit e994e98
Show file tree
Hide file tree
Showing 25 changed files with 391 additions and 216 deletions.
15 changes: 14 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -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)

Expand All @@ -24,6 +33,10 @@ public static dynamic? DeserializeDynamic(this IJsonResponseProvider p)
public static T? Deserialize<T>(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.
Expand Down
2 changes: 2 additions & 0 deletions src/.idea/.idea.Imageflow.both/.idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Imageflow/Bindings/JobContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ private ImageflowJsonResponse InvokeInternal(ReadOnlySpan<byte> 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<byte>(4096);
var utf8JsonWriter = new Utf8JsonWriter(writer, new JsonWriterOptions
{
Expand Down
13 changes: 5 additions & 8 deletions src/Imageflow/Bindings/JsonResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,12 @@ public Stream GetStream()

public unsafe ReadOnlySpan<byte> BorrowBytes()
{
unsafe
{
Read(out var _, out var utf8Buffer, out var bufferSize);
if (utf8Buffer == IntPtr.Zero) return ReadOnlySpan<byte>.Empty;
if (bufferSize == UIntPtr.Zero) return ReadOnlySpan<byte>.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<byte>.Empty;
if (bufferSize == UIntPtr.Zero) return ReadOnlySpan<byte>.Empty;
if (bufferSize.ToUInt64() > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(bufferSize));

return new ReadOnlySpan<byte>((void*)utf8Buffer, (int)bufferSize);
}
return new ReadOnlySpan<byte>((void*)utf8Buffer, (int)bufferSize);
}

public MemoryManager<byte> BorrowMemory()
Expand Down
2 changes: 0 additions & 2 deletions src/Imageflow/Bindings/JsonResponseHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/Imageflow/Bindings/NativeLibraryLoading.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
74 changes: 74 additions & 0 deletions src/Imageflow/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Imageflow.Bindings.IJsonResponseProviderExtensions.Deserialize``1(Imageflow.Bindings.IJsonResponseProvider)</Target>
<Left>lib/netstandard2.0/Imageflow.Net.dll</Left>
<Right>lib/netstandard2.0/Imageflow.Net.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Imageflow.Bindings.IJsonResponseProviderExtensions.DeserializeDynamic(Imageflow.Bindings.IJsonResponseProvider)</Target>
<Left>lib/netstandard2.0/Imageflow.Net.dll</Left>
<Right>lib/netstandard2.0/Imageflow.Net.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Imageflow.Fluent.BuildEncodeResult.#ctor</Target>
<Left>lib/netstandard2.0/Imageflow.Net.dll</Left>
<Right>lib/netstandard2.0/Imageflow.Net.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Imageflow.Fluent.BuildItemBase.#ctor(Imageflow.Fluent.ImageJob,System.Object,Imageflow.Fluent.BuildNode,Imageflow.Fluent.BuildNode)</Target>
<Left>lib/netstandard2.0/Imageflow.Net.dll</Left>
<Right>lib/netstandard2.0/Imageflow.Net.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Imageflow.Fluent.BuildJobResult.#ctor</Target>
<Left>lib/netstandard2.0/Imageflow.Net.dll</Left>
<Right>lib/netstandard2.0/Imageflow.Net.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Imageflow.Fluent.InputWatermark.get_Source</Target>
<Left>lib/netstandard2.0/Imageflow.Net.dll</Left>
<Right>lib/netstandard2.0/Imageflow.Net.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Imageflow.Fluent.IEncoderPreset.ToJsonNode</Target>
<Left>lib/netstandard2.0/Imageflow.Net.dll</Left>
<Right>lib/netstandard2.0/Imageflow.Net.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Imageflow.Fluent.IWatermarkConstraintBox.ToJsonNode</Target>
<Left>lib/netstandard2.0/Imageflow.Net.dll</Left>
<Right>lib/netstandard2.0/Imageflow.Net.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Imageflow.Fluent.BuildEncodeResult</Target>
<Left>lib/netstandard2.0/Imageflow.Net.dll</Left>
<Right>lib/netstandard2.0/Imageflow.Net.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Imageflow.Fluent.BuildJobResult</Target>
<Left>lib/netstandard2.0/Imageflow.Net.dll</Left>
<Right>lib/netstandard2.0/Imageflow.Net.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
43 changes: 40 additions & 3 deletions src/Imageflow/Fluent/BufferedStreamSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,14 +33,18 @@ public void Dispose()
if (_disposeUnderlying)
{
_underlying?.Dispose();
_underlying = null;
}

_copy?.Dispose();
_copy = null;

}

private bool TryGetWrittenMemory(
out ReadOnlyMemory<byte> memory)
{
ObjectDisposedHelper.ThrowIf(_underlying == null, this);
var memStream = _underlying as MemoryStream ?? _copy;
if (memStream != null)
{
Expand All @@ -65,6 +69,7 @@ private bool TryGetWrittenMemory(
/// <exception cref="OverflowException"></exception>
public async ValueTask<ReadOnlyMemory<byte>> BorrowReadOnlyMemoryAsync(CancellationToken cancellationToken)
{
ObjectDisposedHelper.ThrowIf(_underlying == null, this);
if (TryGetWrittenMemory(out var segment))
{
return segment;
Expand All @@ -85,6 +90,7 @@ public async ValueTask<ReadOnlyMemory<byte>> BorrowReadOnlyMemoryAsync(Cancellat

public ReadOnlyMemory<byte> BorrowReadOnlyMemory()
{
ObjectDisposedHelper.ThrowIf(_underlying == null, this);
if (TryGetWrittenMemory(out var segment))
{
return segment;
Expand All @@ -107,19 +113,50 @@ public ReadOnlyMemory<byte> BorrowReadOnlyMemory()
public bool AsyncPreferred => _underlying is not MemoryStream && _underlying is not UnmanagedMemoryStream;


/// <summary>
/// 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.
/// <strong>You remain responsible for disposing and cleaning up the stream after the job is disposed.</strong>
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static IAsyncMemorySource BorrowEntireStream(Stream stream)
{
return new BufferedStreamSource(stream, false, true);
}
/// <summary>
/// <strong>You remain responsible for disposing and cleaning up the stream after the job is disposed.</strong>
/// 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.
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static IAsyncMemorySource BorrowStreamRemainder(Stream stream)
{
return new BufferedStreamSource(stream, false, false);
}
public static IAsyncMemorySource UseEntireStreamAndDispose(Stream stream)

/// <summary>
/// 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.
/// <remarks>You must not close, dispose, or reuse the stream or its underlying streams/buffers until after the job and the owning objects are disposed. <br/>
///
/// 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).</remarks>
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static IAsyncMemorySource UseEntireStreamAndDisposeWithSource(Stream stream)
{
return new BufferedStreamSource(stream, true, true);
}
public static IAsyncMemorySource UseStreamRemainderAndDispose(Stream stream)

/// <summary>
/// The stream will be closed and disposed with the BufferedStreamSource.
/// <strong>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>
/// 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).</strong>
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static IAsyncMemorySource UseStreamRemainderAndDisposeWithSource(Stream stream)
{
return new BufferedStreamSource(stream, true, false);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Imageflow/Fluent/BuildEncodeResult.cs
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -14,7 +14,7 @@ public class BuildEncodeResult
// Destination = destination;
// }

internal BuildEncodeResult()
public BuildEncodeResult()
{
}
// maps to "preferred_mime_type" in json
Expand Down
8 changes: 8 additions & 0 deletions src/Imageflow/Fluent/BuildJobResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, BuildEncodeResult>();
DecodeResults = new List<BuildDecodeResult>();
EncodeResults = new List<BuildEncodeResult>();
PerformanceDetails = new PerformanceDetails(null);
}

private BuildJobResult(IReadOnlyCollection<BuildDecodeResult> decodeResults,
IReadOnlyCollection<BuildEncodeResult> encodeResults,
Expand Down
2 changes: 1 addition & 1 deletion src/Imageflow/Fluent/BuildNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ public BuildNode CopyRectTo(BuildNode canvas, Rectangle area, Point to)
/// <param name="hints"></param>
/// <param name="blend"></param>
/// <returns></returns>
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
Expand Down
5 changes: 1 addition & 4 deletions src/Imageflow/Fluent/IAsyncMemorySource.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Imageflow.Bindings;

namespace Imageflow.Fluent;
namespace Imageflow.Fluent;

public interface IAsyncMemorySource: IDisposable
{
Expand Down
68 changes: 3 additions & 65 deletions src/Imageflow/Fluent/IBytesSource.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -15,9 +11,9 @@ public interface IBytesSource: IDisposable
}

/// <summary>
/// Represents a source backed by an ArraySegment or byte[] array. Use MemorySource for ReadOnlyMemory<byte> backed memory instead.
/// Represents a source backed by an ArraySegment or byte[] array. Use MemorySource for ReadOnlyMemory&lt;byte> backed memory instead.
/// </summary>
[Obsolete("Use MemorySource instead")]
[Obsolete("Use MemorySource.Borrow(bytes, MemoryLifetimePromise.MemoryValidUntilAfterJobDisposed) instead")]
public readonly struct BytesSource : IBytesSource
{
public BytesSource(byte[] bytes)
Expand Down Expand Up @@ -49,65 +45,7 @@ public static implicit operator MemorySource(BytesSource source)
return new MemorySource(source._bytes);
}
}
/// <summary>
/// Represents a image source that is backed by a Stream.
/// </summary>
[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();
}

/// <summary>
/// Note that bytes will not be valid after StreamSource is disposed
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="OverflowException"></exception>
public async Task<ArraySegment<byte>> GetBytesAsync(CancellationToken cancellationToken)
{
if (_copy != null)
{
return new ArraySegment<byte>(_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<byte>(_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
Expand Down
Loading

0 comments on commit e994e98

Please sign in to comment.