Browse Source

Merge branch 'master' into bp/tiff12bit

pull/1647/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
128371073b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      src/ImageSharp/Image.cs
  2. 108
      src/ImageSharp/ImageFrameCollection.cs
  3. 88
      src/ImageSharp/ImageFrameCollection{TPixel}.cs
  4. 66
      src/ImageSharp/Image{TPixel}.cs
  5. 42
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
  6. 36
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
  7. 69
      tests/ImageSharp.Tests/Image/ImageTests.cs

23
src/ImageSharp/Image.cs

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
@ -19,6 +20,8 @@ namespace SixLabors.ImageSharp
/// </summary>
public abstract partial class Image : IImage, IConfigurationProvider
{
private bool isDisposed;
private Size size;
private readonly Configuration configuration;
@ -80,8 +83,15 @@ namespace SixLabors.ImageSharp
/// <inheritdoc />
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.Dispose(true);
GC.SuppressFinalize(this);
this.isDisposed = true;
}
/// <summary>
@ -89,7 +99,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream or encoder is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if the stream or encoder is null.</exception>
public void Save(Stream stream, IImageEncoder encoder)
{
Guard.NotNull(stream, nameof(stream));
@ -148,7 +158,13 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Throws <see cref="ObjectDisposedException"/> if the image is disposed.
/// </summary>
internal abstract void EnsureNotDisposed();
internal void EnsureNotDisposed()
{
if (this.isDisposed)
{
ThrowObjectDisposedException(this.GetType());
}
}
/// <summary>
/// Accepts a <see cref="IImageVisitor"/>.
@ -167,6 +183,9 @@ namespace SixLabors.ImageSharp
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
internal abstract Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken);
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name);
private class EncodeVisitor : IImageVisitor, IImageVisitorAsync
{
private readonly IImageEncoder encoder;

108
src/ImageSharp/ImageFrameCollection.cs

@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp
{
@ -11,8 +12,10 @@ namespace SixLabors.ImageSharp
/// Encapsulates a pixel-agnostic collection of <see cref="ImageFrame"/> instances
/// that make up an <see cref="Image"/>.
/// </summary>
public abstract class ImageFrameCollection : IEnumerable<ImageFrame>
public abstract class ImageFrameCollection : IDisposable, IEnumerable<ImageFrame>
{
private bool isDisposed;
/// <summary>
/// Gets the number of frames.
/// </summary>
@ -21,7 +24,15 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Gets the root frame.
/// </summary>
public ImageFrame RootFrame => this.NonGenericRootFrame;
public ImageFrame RootFrame
{
get
{
this.EnsureNotDisposed();
return this.NonGenericRootFrame;
}
}
/// <summary>
/// Gets the root frame. (Implements <see cref="RootFrame"/>.)
@ -36,7 +47,15 @@ namespace SixLabors.ImageSharp
/// </value>
/// <param name="index">The index.</param>
/// <returns>The <see cref="ImageFrame"/> at the specified index.</returns>
public ImageFrame this[int index] => this.NonGenericGetFrame(index);
public ImageFrame this[int index]
{
get
{
this.EnsureNotDisposed();
return this.NonGenericGetFrame(index);
}
}
/// <summary>
/// Determines the index of a specific <paramref name="frame"/> in the <seealso cref="ImageFrameCollection"/>.
@ -52,14 +71,24 @@ namespace SixLabors.ImageSharp
/// <param name="source">The <seealso cref="ImageFrame"/> to clone and insert into the <seealso cref="ImageFrameCollection"/>.</param>
/// <exception cref="ArgumentException">Frame must have the same dimensions as the image.</exception>
/// <returns>The cloned <see cref="ImageFrame"/>.</returns>
public ImageFrame InsertFrame(int index, ImageFrame source) => this.NonGenericInsertFrame(index, source);
public ImageFrame InsertFrame(int index, ImageFrame source)
{
this.EnsureNotDisposed();
return this.NonGenericInsertFrame(index, source);
}
/// <summary>
/// Clones the <paramref name="source"/> frame and appends the clone to the end of the collection.
/// </summary>
/// <param name="source">The raw pixel data to generate the <seealso cref="ImageFrame{TPixel}"/> from.</param>
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
public ImageFrame AddFrame(ImageFrame source) => this.NonGenericAddFrame(source);
public ImageFrame AddFrame(ImageFrame source)
{
this.EnsureNotDisposed();
return this.NonGenericAddFrame(source);
}
/// <summary>
/// Removes the frame at the specified index and frees all freeable resources associated with it.
@ -91,7 +120,12 @@ namespace SixLabors.ImageSharp
/// <param name="index">The zero-based index of the frame to export.</param>
/// <exception cref="InvalidOperationException">Cannot remove last frame.</exception>
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
public Image ExportFrame(int index) => this.NonGenericExportFrame(index);
public Image ExportFrame(int index)
{
this.EnsureNotDisposed();
return this.NonGenericExportFrame(index);
}
/// <summary>
/// Creates an <see cref="Image{T}"/> with only the frame at the specified index
@ -99,7 +133,12 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="index">The zero-based index of the frame to clone.</param>
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
public Image CloneFrame(int index) => this.NonGenericCloneFrame(index);
public Image CloneFrame(int index)
{
this.EnsureNotDisposed();
return this.NonGenericCloneFrame(index);
}
/// <summary>
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection.
@ -107,7 +146,12 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The new <see cref="ImageFrame{TPixel}" />.
/// </returns>
public ImageFrame CreateFrame() => this.NonGenericCreateFrame();
public ImageFrame CreateFrame()
{
this.EnsureNotDisposed();
return this.NonGenericCreateFrame();
}
/// <summary>
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection.
@ -116,14 +160,55 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The new <see cref="ImageFrame{TPixel}" />.
/// </returns>
public ImageFrame CreateFrame(Color backgroundColor) => this.NonGenericCreateFrame(backgroundColor);
public ImageFrame CreateFrame(Color backgroundColor)
{
this.EnsureNotDisposed();
return this.NonGenericCreateFrame(backgroundColor);
}
/// <inheritdoc />
public IEnumerator<ImageFrame> GetEnumerator() => this.NonGenericGetEnumerator();
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.Dispose(true);
GC.SuppressFinalize(this);
this.isDisposed = true;
}
/// <inheritdoc />
public IEnumerator<ImageFrame> GetEnumerator()
{
this.EnsureNotDisposed();
return this.NonGenericGetEnumerator();
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
/// <summary>
/// Throws <see cref="ObjectDisposedException"/> if the image frame is disposed.
/// </summary>
protected void EnsureNotDisposed()
{
if (this.isDisposed)
{
ThrowObjectDisposedException(this.GetType());
}
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param>
protected abstract void Dispose(bool disposing);
/// <summary>
/// Implements <see cref="GetEnumerator"/>.
/// </summary>
@ -178,5 +263,8 @@ namespace SixLabors.ImageSharp
/// <param name="backgroundColor">The background color.</param>
/// <returns>The new frame.</returns>
protected abstract ImageFrame NonGenericCreateFrame(Color backgroundColor);
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name);
}
}

88
src/ImageSharp/ImageFrameCollection{TPixel}.cs

@ -67,7 +67,26 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Gets the root frame.
/// </summary>
public new ImageFrame<TPixel> RootFrame => this.frames.Count > 0 ? this.frames[0] : null;
public new ImageFrame<TPixel> RootFrame
{
get
{
this.EnsureNotDisposed();
// frame collection would always contain at least 1 frame
// the only exception is when collection is disposed what is checked via EnsureNotDisposed() call
return this.frames[0];
}
}
/// <summary>
/// Gets root frame accessor in unsafe manner without any checks.
/// </summary>
/// <remarks>
/// This property is most likely to be called from <see cref="Image{TPixel}"/> for indexing pixels.
/// <see cref="Image{TPixel}"/> already checks if it was disposed before querying for root frame.
/// </remarks>
internal ImageFrame<TPixel> RootFrameUnsafe => this.frames[0];
/// <inheritdoc />
protected override ImageFrame NonGenericRootFrame => this.RootFrame;
@ -80,12 +99,22 @@ namespace SixLabors.ImageSharp
/// </value>
/// <param name="index">The index.</param>
/// <returns>The <see cref="ImageFrame{TPixel}"/> at the specified index.</returns>
public new ImageFrame<TPixel> this[int index] => this.frames[index];
public new ImageFrame<TPixel> this[int index]
{
get
{
this.EnsureNotDisposed();
return this.frames[index];
}
}
/// <inheritdoc />
public override int IndexOf(ImageFrame frame)
{
return frame is ImageFrame<TPixel> specific ? this.IndexOf(specific) : -1;
this.EnsureNotDisposed();
return frame is ImageFrame<TPixel> specific ? this.frames.IndexOf(specific) : -1;
}
/// <summary>
@ -93,7 +122,12 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="frame">The <seealso cref="ImageFrame{TPixel}"/> to locate in the <seealso cref="ImageFrameCollection{TPixel}"/>.</param>
/// <returns>The index of item if found in the list; otherwise, -1.</returns>
public int IndexOf(ImageFrame<TPixel> frame) => this.frames.IndexOf(frame);
public int IndexOf(ImageFrame<TPixel> frame)
{
this.EnsureNotDisposed();
return this.frames.IndexOf(frame);
}
/// <summary>
/// Clones and inserts the <paramref name="source"/> into the <seealso cref="ImageFrameCollection{TPixel}"/> at the specified <paramref name="index"/>.
@ -104,6 +138,8 @@ namespace SixLabors.ImageSharp
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
public ImageFrame<TPixel> InsertFrame(int index, ImageFrame<TPixel> source)
{
this.EnsureNotDisposed();
this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration());
this.frames.Insert(index, clonedFrame);
@ -117,6 +153,8 @@ namespace SixLabors.ImageSharp
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
public ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> source)
{
this.EnsureNotDisposed();
this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration());
this.frames.Add(clonedFrame);
@ -131,6 +169,8 @@ namespace SixLabors.ImageSharp
/// <returns>The new <see cref="ImageFrame{TPixel}"/>.</returns>
public ImageFrame<TPixel> AddFrame(ReadOnlySpan<TPixel> source)
{
this.EnsureNotDisposed();
var frame = ImageFrame.LoadPixelData(
this.parent.GetConfiguration(),
source,
@ -149,6 +189,7 @@ namespace SixLabors.ImageSharp
public ImageFrame<TPixel> AddFrame(TPixel[] source)
{
Guard.NotNull(source, nameof(source));
return this.AddFrame(source.AsSpan());
}
@ -159,6 +200,8 @@ namespace SixLabors.ImageSharp
/// <exception cref="InvalidOperationException">Cannot remove last frame.</exception>
public override void RemoveFrame(int index)
{
this.EnsureNotDisposed();
if (index == 0 && this.Count == 1)
{
throw new InvalidOperationException("Cannot remove last frame.");
@ -170,8 +213,12 @@ namespace SixLabors.ImageSharp
}
/// <inheritdoc />
public override bool Contains(ImageFrame frame) =>
frame is ImageFrame<TPixel> specific && this.Contains(specific);
public override bool Contains(ImageFrame frame)
{
this.EnsureNotDisposed();
return frame is ImageFrame<TPixel> specific && this.frames.Contains(specific);
}
/// <summary>
/// Determines whether the <seealso cref="ImageFrameCollection{TPixel}"/> contains the <paramref name="frame"/>.
@ -180,7 +227,12 @@ namespace SixLabors.ImageSharp
/// <returns>
/// <c>true</c> if the <seealso cref="ImageFrameCollection{TPixel}"/> contains the specified frame; otherwise, <c>false</c>.
/// </returns>
public bool Contains(ImageFrame<TPixel> frame) => this.frames.Contains(frame);
public bool Contains(ImageFrame<TPixel> frame)
{
this.EnsureNotDisposed();
return this.frames.Contains(frame);
}
/// <summary>
/// Moves an <seealso cref="ImageFrame{TPixel}"/> from <paramref name="sourceIndex"/> to <paramref name="destinationIndex"/>.
@ -189,6 +241,8 @@ namespace SixLabors.ImageSharp
/// <param name="destinationIndex">The index to move the frame to.</param>
public override void MoveFrame(int sourceIndex, int destinationIndex)
{
this.EnsureNotDisposed();
if (sourceIndex == destinationIndex)
{
return;
@ -208,6 +262,8 @@ namespace SixLabors.ImageSharp
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
public new Image<TPixel> ExportFrame(int index)
{
this.EnsureNotDisposed();
ImageFrame<TPixel> frame = this[index];
if (this.Count == 1 && this.frames.Contains(frame))
@ -228,6 +284,8 @@ namespace SixLabors.ImageSharp
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
public new Image<TPixel> CloneFrame(int index)
{
this.EnsureNotDisposed();
ImageFrame<TPixel> frame = this[index];
ImageFrame<TPixel> clonedFrame = frame.Clone();
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { clonedFrame });
@ -241,6 +299,8 @@ namespace SixLabors.ImageSharp
/// </returns>
public new ImageFrame<TPixel> CreateFrame()
{
this.EnsureNotDisposed();
var frame = new ImageFrame<TPixel>(
this.parent.GetConfiguration(),
this.RootFrame.Width,
@ -335,14 +395,18 @@ namespace SixLabors.ImageSharp
}
}
internal void Dispose()
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
foreach (ImageFrame<TPixel> f in this.frames)
if (disposing)
{
f.Dispose();
}
foreach (ImageFrame<TPixel> f in this.frames)
{
f.Dispose();
}
this.frames.Clear();
this.frames.Clear();
}
}
private ImageFrame<TPixel> CopyNonCompatibleFrame(ImageFrame source)

66
src/ImageSharp/Image{TPixel}.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp
public sealed class Image<TPixel> : Image
where TPixel : unmanaged, IPixel<TPixel>
{
private bool isDisposed;
private readonly ImageFrameCollection<TPixel> frames;
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp
internal Image(Configuration configuration, int width, int height, ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
{
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, default(TPixel));
this.frames = new ImageFrameCollection<TPixel>(this, width, height, default(TPixel));
}
/// <summary>
@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
{
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, memoryGroup);
this.frames = new ImageFrameCollection<TPixel>(this, width, height, memoryGroup);
}
/// <summary>
@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
{
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);
this.frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);
}
/// <summary>
@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp
internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable<ImageFrame<TPixel>> frames)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, ValidateFramesAndGetSize(frames))
{
this.Frames = new ImageFrameCollection<TPixel>(this, frames);
this.frames = new ImageFrameCollection<TPixel>(this, frames);
}
/// <inheritdoc />
@ -146,12 +146,19 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Gets the collection of image frames.
/// </summary>
public new ImageFrameCollection<TPixel> Frames { get; }
public new ImageFrameCollection<TPixel> Frames
{
get
{
this.EnsureNotDisposed();
return this.frames;
}
}
/// <summary>
/// Gets the root frame.
/// </summary>
private IPixelSource<TPixel> PixelSource => this.Frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image<TPixel>));
private IPixelSource<TPixel> PixelSourceUnsafe => this.frames.RootFrameUnsafe;
/// <summary>
/// Gets or sets the pixel at the specified position.
@ -165,15 +172,19 @@ namespace SixLabors.ImageSharp
[MethodImpl(InliningOptions.ShortMethod)]
get
{
this.EnsureNotDisposed();
this.VerifyCoords(x, y);
return this.PixelSource.PixelBuffer.GetElementUnsafe(x, y);
return this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y);
}
[MethodImpl(InliningOptions.ShortMethod)]
set
{
this.EnsureNotDisposed();
this.VerifyCoords(x, y);
this.PixelSource.PixelBuffer.GetElementUnsafe(x, y) = value;
this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y) = value;
}
}
@ -189,7 +200,9 @@ namespace SixLabors.ImageSharp
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex));
return this.PixelSource.PixelBuffer.GetRowSpan(rowIndex);
this.EnsureNotDisposed();
return this.PixelSourceUnsafe.PixelBuffer.GetRowSpan(rowIndex);
}
/// <summary>
@ -226,10 +239,10 @@ namespace SixLabors.ImageSharp
{
this.EnsureNotDisposed();
var clonedFrames = new ImageFrame<TPixel>[this.Frames.Count];
var clonedFrames = new ImageFrame<TPixel>[this.frames.Count];
for (int i = 0; i < clonedFrames.Length; i++)
{
clonedFrames[i] = this.Frames[i].Clone(configuration);
clonedFrames[i] = this.frames[i].Clone(configuration);
}
return new Image<TPixel>(configuration, this.Metadata.DeepClone(), clonedFrames);
@ -245,10 +258,10 @@ namespace SixLabors.ImageSharp
{
this.EnsureNotDisposed();
var clonedFrames = new ImageFrame<TPixel2>[this.Frames.Count];
var clonedFrames = new ImageFrame<TPixel2>[this.frames.Count];
for (int i = 0; i < clonedFrames.Length; i++)
{
clonedFrames[i] = this.Frames[i].CloneAs<TPixel2>(configuration);
clonedFrames[i] = this.frames[i].CloneAs<TPixel2>(configuration);
}
return new Image<TPixel2>(configuration, this.Metadata.DeepClone(), clonedFrames);
@ -257,25 +270,9 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.Frames.Dispose();
}
this.isDisposed = true;
}
/// <inheritdoc/>
internal override void EnsureNotDisposed()
{
if (this.isDisposed)
{
throw new ObjectDisposedException("Trying to execute an operation on a disposed image.");
this.frames.Dispose();
}
}
@ -306,9 +303,12 @@ namespace SixLabors.ImageSharp
{
Guard.NotNull(pixelSource, nameof(pixelSource));
for (int i = 0; i < this.Frames.Count; i++)
this.EnsureNotDisposed();
ImageFrameCollection<TPixel> sourceFrames = pixelSource.Frames;
for (int i = 0; i < this.frames.Count; i++)
{
this.Frames[i].SwapOrCopyPixelsBufferFrom(pixelSource.Frames[i]);
this.frames[i].SwapOrCopyPixelsBufferFrom(sourceFrames[i]);
}
this.UpdateSize(pixelSource.Size());

42
tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs

@ -339,6 +339,48 @@ namespace SixLabors.ImageSharp.Tests
using var frame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10);
Assert.False(this.Image.Frames.Contains(frame));
}
[Fact]
public void DisposeCall_NoThrowIfCalledMultiple()
{
var image = new Image<Rgba32>(Configuration.Default, 10, 10);
var frameCollection = image.Frames as ImageFrameCollection;
image.Dispose(); // this should invalidate underlying collection as well
frameCollection.Dispose();
}
[Fact]
public void PublicProperties_ThrowIfDisposed()
{
var image = new Image<Rgba32>(Configuration.Default, 10, 10);
var frameCollection = image.Frames as ImageFrameCollection;
image.Dispose(); // this should invalidate underlying collection as well
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.RootFrame; });
}
[Fact]
public void PublicMethods_ThrowIfDisposed()
{
var image = new Image<Rgba32>(Configuration.Default, 10, 10);
var frameCollection = image.Frames as ImageFrameCollection;
image.Dispose(); // this should invalidate underlying collection as well
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.AddFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CloneFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.Contains(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CreateFrame(); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CreateFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.ExportFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.GetEnumerator(); });
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.IndexOf(default); });
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.InsertFrame(default, default); });
Assert.Throws<ObjectDisposedException>(() => { frameCollection.RemoveFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { frameCollection.MoveFrame(default, default); });
}
}
}
}

36
tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs

@ -263,6 +263,42 @@ namespace SixLabors.ImageSharp.Tests
Assert.False(this.Image.Frames.Contains(frame));
}
[Fact]
public void PublicProperties_ThrowIfDisposed()
{
var image = new Image<Rgba32>(Configuration.Default, 10, 10);
var frameCollection = image.Frames;
image.Dispose(); // this should invalidate underlying collection as well
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.RootFrame; });
}
[Fact]
public void PublicMethods_ThrowIfDisposed()
{
var image = new Image<Rgba32>(Configuration.Default, 10, 10);
var frameCollection = image.Frames;
var rgba32Array = new Rgba32[0];
image.Dispose(); // this should invalidate underlying collection as well
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.AddFrame((ImageFrame)null); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.AddFrame(rgba32Array); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.AddFrame((ImageFrame<Rgba32>)null); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.AddFrame(rgba32Array.AsSpan()); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CloneFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.Contains(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CreateFrame(); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CreateFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.ExportFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.GetEnumerator(); });
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.IndexOf(default); });
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.InsertFrame(default, default); });
Assert.Throws<ObjectDisposedException>(() => { frameCollection.RemoveFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { frameCollection.MoveFrame(default, default); });
}
/// <summary>
/// Integration test for end-to end API validation.
/// </summary>

69
tests/ImageSharp.Tests/Image/ImageTests.cs

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -169,5 +171,72 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal("y", ex.ParamName);
}
}
public class Dispose
{
private readonly Configuration configuration = Configuration.CreateDefaultInstance();
public void MultipleDisposeCalls()
{
var image = new Image<Rgba32>(this.configuration, 10, 10);
image.Dispose();
image.Dispose();
}
[Fact]
public void NonPrivateProperties_ObjectDisposedException()
{
var image = new Image<Rgba32>(this.configuration, 10, 10);
var genericImage = (Image)image;
image.Dispose();
// Image<TPixel>
Assert.Throws<ObjectDisposedException>(() => { var prop = image.Frames; });
// Image
Assert.Throws<ObjectDisposedException>(() => { var prop = genericImage.Frames; });
}
[Fact]
public void Save_ObjectDisposedException()
{
using var stream = new MemoryStream();
var image = new Image<Rgba32>(this.configuration, 10, 10);
var encoder = new JpegEncoder();
image.Dispose();
// Image<TPixel>
Assert.Throws<ObjectDisposedException>(() => image.Save(stream, encoder));
}
[Fact]
public void AcceptVisitor_ObjectDisposedException()
{
// This test technically should exist but it's impossible to write proper test case without reflection:
// All visitor types are private and can't be created without context of some save/processing operation
// Save_ObjectDisposedException test checks this method with AcceptVisitor(EncodeVisitor) anyway
return;
}
[Fact]
public void NonPrivateMethods_ObjectDisposedException()
{
var image = new Image<Rgba32>(this.configuration, 10, 10);
var genericImage = (Image)image;
image.Dispose();
// Image<TPixel>
Assert.Throws<ObjectDisposedException>(() => { var res = image.Clone(this.configuration); });
Assert.Throws<ObjectDisposedException>(() => { var res = image.CloneAs<Rgba32>(this.configuration); });
Assert.Throws<ObjectDisposedException>(() => { var res = image.GetPixelRowSpan(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = image.TryGetSinglePixelSpan(out var _); });
// Image
Assert.Throws<ObjectDisposedException>(() => { var res = genericImage.CloneAs<Rgba32>(this.configuration); });
}
}
}
}

Loading…
Cancel
Save