diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs
index fbb3ec206..ce6aa69b5 100644
--- a/src/ImageSharp/Image.cs
+++ b/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
///
public abstract partial class Image : IImage, IConfigurationProvider
{
+ private bool isDisposed;
+
private Size size;
private readonly Configuration configuration;
@@ -80,8 +83,15 @@ namespace SixLabors.ImageSharp
///
public void Dispose()
{
+ if (this.isDisposed)
+ {
+ return;
+ }
+
this.Dispose(true);
GC.SuppressFinalize(this);
+
+ this.isDisposed = true;
}
///
@@ -89,7 +99,7 @@ namespace SixLabors.ImageSharp
///
/// The stream to save the image to.
/// The encoder to save the image with.
- /// Thrown if the stream or encoder is null.
+ /// Thrown if the stream or encoder is null.
public void Save(Stream stream, IImageEncoder encoder)
{
Guard.NotNull(stream, nameof(stream));
@@ -148,7 +158,13 @@ namespace SixLabors.ImageSharp
///
/// Throws if the image is disposed.
///
- internal abstract void EnsureNotDisposed();
+ internal void EnsureNotDisposed()
+ {
+ if (this.isDisposed)
+ {
+ ThrowObjectDisposedException(this.GetType());
+ }
+ }
///
/// Accepts a .
@@ -167,6 +183,9 @@ namespace SixLabors.ImageSharp
/// The token to monitor for cancellation requests.
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;
diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs
index 62ecc71f5..07ba8c87f 100644
--- a/src/ImageSharp/ImageFrameCollection.cs
+++ b/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 instances
/// that make up an .
///
- public abstract class ImageFrameCollection : IEnumerable
+ public abstract class ImageFrameCollection : IDisposable, IEnumerable
{
+ private bool isDisposed;
+
///
/// Gets the number of frames.
///
@@ -21,7 +24,15 @@ namespace SixLabors.ImageSharp
///
/// Gets the root frame.
///
- public ImageFrame RootFrame => this.NonGenericRootFrame;
+ public ImageFrame RootFrame
+ {
+ get
+ {
+ this.EnsureNotDisposed();
+
+ return this.NonGenericRootFrame;
+ }
+ }
///
/// Gets the root frame. (Implements .)
@@ -36,7 +47,15 @@ namespace SixLabors.ImageSharp
///
/// The index.
/// The at the specified index.
- public ImageFrame this[int index] => this.NonGenericGetFrame(index);
+ public ImageFrame this[int index]
+ {
+ get
+ {
+ this.EnsureNotDisposed();
+
+ return this.NonGenericGetFrame(index);
+ }
+ }
///
/// Determines the index of a specific in the .
@@ -52,14 +71,24 @@ namespace SixLabors.ImageSharp
/// The to clone and insert into the .
/// Frame must have the same dimensions as the image.
/// The cloned .
- 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);
+ }
///
/// Clones the frame and appends the clone to the end of the collection.
///
/// The raw pixel data to generate the from.
/// The cloned .
- public ImageFrame AddFrame(ImageFrame source) => this.NonGenericAddFrame(source);
+ public ImageFrame AddFrame(ImageFrame source)
+ {
+ this.EnsureNotDisposed();
+
+ return this.NonGenericAddFrame(source);
+ }
///
/// Removes the frame at the specified index and frees all freeable resources associated with it.
@@ -91,7 +120,12 @@ namespace SixLabors.ImageSharp
/// The zero-based index of the frame to export.
/// Cannot remove last frame.
/// The new with the specified frame.
- public Image ExportFrame(int index) => this.NonGenericExportFrame(index);
+ public Image ExportFrame(int index)
+ {
+ this.EnsureNotDisposed();
+
+ return this.NonGenericExportFrame(index);
+ }
///
/// Creates an with only the frame at the specified index
@@ -99,7 +133,12 @@ namespace SixLabors.ImageSharp
///
/// The zero-based index of the frame to clone.
/// The new with the specified frame.
- public Image CloneFrame(int index) => this.NonGenericCloneFrame(index);
+ public Image CloneFrame(int index)
+ {
+ this.EnsureNotDisposed();
+
+ return this.NonGenericCloneFrame(index);
+ }
///
/// Creates a new and appends it to the end of the collection.
@@ -107,7 +146,12 @@ namespace SixLabors.ImageSharp
///
/// The new .
///
- public ImageFrame CreateFrame() => this.NonGenericCreateFrame();
+ public ImageFrame CreateFrame()
+ {
+ this.EnsureNotDisposed();
+
+ return this.NonGenericCreateFrame();
+ }
///
/// Creates a new and appends it to the end of the collection.
@@ -116,14 +160,55 @@ namespace SixLabors.ImageSharp
///
/// The new .
///
- public ImageFrame CreateFrame(Color backgroundColor) => this.NonGenericCreateFrame(backgroundColor);
+ public ImageFrame CreateFrame(Color backgroundColor)
+ {
+ this.EnsureNotDisposed();
+
+ return this.NonGenericCreateFrame(backgroundColor);
+ }
///
- public IEnumerator GetEnumerator() => this.NonGenericGetEnumerator();
+ public void Dispose()
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+
+ this.isDisposed = true;
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ this.EnsureNotDisposed();
+
+ return this.NonGenericGetEnumerator();
+ }
///
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ ///
+ /// Throws if the image frame is disposed.
+ ///
+ protected void EnsureNotDisposed()
+ {
+ if (this.isDisposed)
+ {
+ ThrowObjectDisposedException(this.GetType());
+ }
+ }
+
+ ///
+ /// Disposes the object and frees resources for the Garbage Collector.
+ ///
+ /// Whether to dispose of managed and unmanaged objects.
+ protected abstract void Dispose(bool disposing);
+
///
/// Implements .
///
@@ -178,5 +263,8 @@ namespace SixLabors.ImageSharp
/// The background color.
/// The new frame.
protected abstract ImageFrame NonGenericCreateFrame(Color backgroundColor);
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name);
}
}
diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs
index 36c3ee481..da024c917 100644
--- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs
+++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs
@@ -67,7 +67,26 @@ namespace SixLabors.ImageSharp
///
/// Gets the root frame.
///
- public new ImageFrame RootFrame => this.frames.Count > 0 ? this.frames[0] : null;
+ public new ImageFrame 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];
+ }
+ }
+
+ ///
+ /// Gets root frame accessor in unsafe manner without any checks.
+ ///
+ ///
+ /// This property is most likely to be called from for indexing pixels.
+ /// already checks if it was disposed before querying for root frame.
+ ///
+ internal ImageFrame RootFrameUnsafe => this.frames[0];
///
protected override ImageFrame NonGenericRootFrame => this.RootFrame;
@@ -80,12 +99,22 @@ namespace SixLabors.ImageSharp
///
/// The index.
/// The at the specified index.
- public new ImageFrame this[int index] => this.frames[index];
+ public new ImageFrame this[int index]
+ {
+ get
+ {
+ this.EnsureNotDisposed();
+
+ return this.frames[index];
+ }
+ }
///
public override int IndexOf(ImageFrame frame)
{
- return frame is ImageFrame specific ? this.IndexOf(specific) : -1;
+ this.EnsureNotDisposed();
+
+ return frame is ImageFrame specific ? this.frames.IndexOf(specific) : -1;
}
///
@@ -93,7 +122,12 @@ namespace SixLabors.ImageSharp
///
/// The to locate in the .
/// The index of item if found in the list; otherwise, -1.
- public int IndexOf(ImageFrame frame) => this.frames.IndexOf(frame);
+ public int IndexOf(ImageFrame frame)
+ {
+ this.EnsureNotDisposed();
+
+ return this.frames.IndexOf(frame);
+ }
///
/// Clones and inserts the into the at the specified .
@@ -104,6 +138,8 @@ namespace SixLabors.ImageSharp
/// The cloned .
public ImageFrame InsertFrame(int index, ImageFrame source)
{
+ this.EnsureNotDisposed();
+
this.ValidateFrame(source);
ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration());
this.frames.Insert(index, clonedFrame);
@@ -117,6 +153,8 @@ namespace SixLabors.ImageSharp
/// The cloned .
public ImageFrame AddFrame(ImageFrame source)
{
+ this.EnsureNotDisposed();
+
this.ValidateFrame(source);
ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration());
this.frames.Add(clonedFrame);
@@ -131,6 +169,8 @@ namespace SixLabors.ImageSharp
/// The new .
public ImageFrame AddFrame(ReadOnlySpan source)
{
+ this.EnsureNotDisposed();
+
var frame = ImageFrame.LoadPixelData(
this.parent.GetConfiguration(),
source,
@@ -149,6 +189,7 @@ namespace SixLabors.ImageSharp
public ImageFrame AddFrame(TPixel[] source)
{
Guard.NotNull(source, nameof(source));
+
return this.AddFrame(source.AsSpan());
}
@@ -159,6 +200,8 @@ namespace SixLabors.ImageSharp
/// Cannot remove last frame.
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
}
///
- public override bool Contains(ImageFrame frame) =>
- frame is ImageFrame specific && this.Contains(specific);
+ public override bool Contains(ImageFrame frame)
+ {
+ this.EnsureNotDisposed();
+
+ return frame is ImageFrame specific && this.frames.Contains(specific);
+ }
///
/// Determines whether the contains the .
@@ -180,7 +227,12 @@ namespace SixLabors.ImageSharp
///
/// true if the contains the specified frame; otherwise, false.
///
- public bool Contains(ImageFrame frame) => this.frames.Contains(frame);
+ public bool Contains(ImageFrame frame)
+ {
+ this.EnsureNotDisposed();
+
+ return this.frames.Contains(frame);
+ }
///
/// Moves an from to .
@@ -189,6 +241,8 @@ namespace SixLabors.ImageSharp
/// The index to move the frame to.
public override void MoveFrame(int sourceIndex, int destinationIndex)
{
+ this.EnsureNotDisposed();
+
if (sourceIndex == destinationIndex)
{
return;
@@ -208,6 +262,8 @@ namespace SixLabors.ImageSharp
/// The new with the specified frame.
public new Image ExportFrame(int index)
{
+ this.EnsureNotDisposed();
+
ImageFrame frame = this[index];
if (this.Count == 1 && this.frames.Contains(frame))
@@ -228,6 +284,8 @@ namespace SixLabors.ImageSharp
/// The new with the specified frame.
public new Image CloneFrame(int index)
{
+ this.EnsureNotDisposed();
+
ImageFrame frame = this[index];
ImageFrame clonedFrame = frame.Clone();
return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { clonedFrame });
@@ -241,6 +299,8 @@ namespace SixLabors.ImageSharp
///
public new ImageFrame CreateFrame()
{
+ this.EnsureNotDisposed();
+
var frame = new ImageFrame(
this.parent.GetConfiguration(),
this.RootFrame.Width,
@@ -335,14 +395,18 @@ namespace SixLabors.ImageSharp
}
}
- internal void Dispose()
+ ///
+ protected override void Dispose(bool disposing)
{
- foreach (ImageFrame f in this.frames)
+ if (disposing)
{
- f.Dispose();
- }
+ foreach (ImageFrame f in this.frames)
+ {
+ f.Dispose();
+ }
- this.frames.Clear();
+ this.frames.Clear();
+ }
}
private ImageFrame CopyNonCompatibleFrame(ImageFrame source)
diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs
index 83ecc3753..b43ff0422 100644
--- a/src/ImageSharp/Image{TPixel}.cs
+++ b/src/ImageSharp/Image{TPixel}.cs
@@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp
public sealed class Image : Image
where TPixel : unmanaged, IPixel
{
- private bool isDisposed;
+ private readonly ImageFrameCollection frames;
///
/// Initializes a new instance of the class
@@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp
internal Image(Configuration configuration, int width, int height, ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create(), metadata, width, height)
{
- this.Frames = new ImageFrameCollection(this, width, height, default(TPixel));
+ this.frames = new ImageFrameCollection(this, width, height, default(TPixel));
}
///
@@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create(), metadata, width, height)
{
- this.Frames = new ImageFrameCollection(this, width, height, memoryGroup);
+ this.frames = new ImageFrameCollection(this, width, height, memoryGroup);
}
///
@@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create(), metadata, width, height)
{
- this.Frames = new ImageFrameCollection(this, width, height, backgroundColor);
+ this.frames = new ImageFrameCollection(this, width, height, backgroundColor);
}
///
@@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp
internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable> frames)
: base(configuration, PixelTypeInfo.Create(), metadata, ValidateFramesAndGetSize(frames))
{
- this.Frames = new ImageFrameCollection(this, frames);
+ this.frames = new ImageFrameCollection(this, frames);
}
///
@@ -146,12 +146,19 @@ namespace SixLabors.ImageSharp
///
/// Gets the collection of image frames.
///
- public new ImageFrameCollection Frames { get; }
+ public new ImageFrameCollection Frames
+ {
+ get
+ {
+ this.EnsureNotDisposed();
+ return this.frames;
+ }
+ }
///
/// Gets the root frame.
///
- private IPixelSource PixelSource => this.Frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image));
+ private IPixelSource PixelSourceUnsafe => this.frames.RootFrameUnsafe;
///
/// 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);
}
///
@@ -226,10 +239,10 @@ namespace SixLabors.ImageSharp
{
this.EnsureNotDisposed();
- var clonedFrames = new ImageFrame[this.Frames.Count];
+ var clonedFrames = new ImageFrame[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(configuration, this.Metadata.DeepClone(), clonedFrames);
@@ -245,10 +258,10 @@ namespace SixLabors.ImageSharp
{
this.EnsureNotDisposed();
- var clonedFrames = new ImageFrame[this.Frames.Count];
+ var clonedFrames = new ImageFrame[this.frames.Count];
for (int i = 0; i < clonedFrames.Length; i++)
{
- clonedFrames[i] = this.Frames[i].CloneAs(configuration);
+ clonedFrames[i] = this.frames[i].CloneAs(configuration);
}
return new Image(configuration, this.Metadata.DeepClone(), clonedFrames);
@@ -257,25 +270,9 @@ namespace SixLabors.ImageSharp
///
protected override void Dispose(bool disposing)
{
- if (this.isDisposed)
- {
- return;
- }
-
if (disposing)
{
- this.Frames.Dispose();
- }
-
- this.isDisposed = true;
- }
-
- ///
- 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 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());
diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
index a00f190db..dbc5af536 100644
--- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
+++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
@@ -339,6 +339,48 @@ namespace SixLabors.ImageSharp.Tests
using var frame = new ImageFrame(Configuration.Default, 10, 10);
Assert.False(this.Image.Frames.Contains(frame));
}
+
+ [Fact]
+ public void DisposeCall_NoThrowIfCalledMultiple()
+ {
+ var image = new Image(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(Configuration.Default, 10, 10);
+ var frameCollection = image.Frames as ImageFrameCollection;
+
+ image.Dispose(); // this should invalidate underlying collection as well
+
+ Assert.Throws(() => { var prop = frameCollection.RootFrame; });
+ }
+
+ [Fact]
+ public void PublicMethods_ThrowIfDisposed()
+ {
+ var image = new Image(Configuration.Default, 10, 10);
+ var frameCollection = image.Frames as ImageFrameCollection;
+
+ image.Dispose(); // this should invalidate underlying collection as well
+
+ Assert.Throws(() => { var res = frameCollection.AddFrame(default); });
+ Assert.Throws(() => { var res = frameCollection.CloneFrame(default); });
+ Assert.Throws(() => { var res = frameCollection.Contains(default); });
+ Assert.Throws(() => { var res = frameCollection.CreateFrame(); });
+ Assert.Throws(() => { var res = frameCollection.CreateFrame(default); });
+ Assert.Throws(() => { var res = frameCollection.ExportFrame(default); });
+ Assert.Throws(() => { var res = frameCollection.GetEnumerator(); });
+ Assert.Throws(() => { var prop = frameCollection.IndexOf(default); });
+ Assert.Throws(() => { var prop = frameCollection.InsertFrame(default, default); });
+ Assert.Throws(() => { frameCollection.RemoveFrame(default); });
+ Assert.Throws(() => { frameCollection.MoveFrame(default, default); });
+ }
}
}
}
diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
index 92109ed47..15838f690 100644
--- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
+++ b/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(Configuration.Default, 10, 10);
+ var frameCollection = image.Frames;
+
+ image.Dispose(); // this should invalidate underlying collection as well
+
+ Assert.Throws(() => { var prop = frameCollection.RootFrame; });
+ }
+
+ [Fact]
+ public void PublicMethods_ThrowIfDisposed()
+ {
+ var image = new Image(Configuration.Default, 10, 10);
+ var frameCollection = image.Frames;
+ var rgba32Array = new Rgba32[0];
+
+ image.Dispose(); // this should invalidate underlying collection as well
+
+ Assert.Throws(() => { var res = frameCollection.AddFrame((ImageFrame)null); });
+ Assert.Throws(() => { var res = frameCollection.AddFrame(rgba32Array); });
+ Assert.Throws(() => { var res = frameCollection.AddFrame((ImageFrame)null); });
+ Assert.Throws(() => { var res = frameCollection.AddFrame(rgba32Array.AsSpan()); });
+ Assert.Throws(() => { var res = frameCollection.CloneFrame(default); });
+ Assert.Throws(() => { var res = frameCollection.Contains(default); });
+ Assert.Throws(() => { var res = frameCollection.CreateFrame(); });
+ Assert.Throws(() => { var res = frameCollection.CreateFrame(default); });
+ Assert.Throws(() => { var res = frameCollection.ExportFrame(default); });
+ Assert.Throws(() => { var res = frameCollection.GetEnumerator(); });
+ Assert.Throws(() => { var prop = frameCollection.IndexOf(default); });
+ Assert.Throws(() => { var prop = frameCollection.InsertFrame(default, default); });
+ Assert.Throws(() => { frameCollection.RemoveFrame(default); });
+ Assert.Throws(() => { frameCollection.MoveFrame(default, default); });
+ }
+
///
/// Integration test for end-to end API validation.
///
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs
index b7c6b3835..1296f26c4 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.cs
+++ b/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(this.configuration, 10, 10);
+ image.Dispose();
+ image.Dispose();
+ }
+
+ [Fact]
+ public void NonPrivateProperties_ObjectDisposedException()
+ {
+ var image = new Image(this.configuration, 10, 10);
+ var genericImage = (Image)image;
+
+ image.Dispose();
+
+ // Image
+ Assert.Throws(() => { var prop = image.Frames; });
+
+ // Image
+ Assert.Throws(() => { var prop = genericImage.Frames; });
+ }
+
+ [Fact]
+ public void Save_ObjectDisposedException()
+ {
+ using var stream = new MemoryStream();
+ var image = new Image(this.configuration, 10, 10);
+ var encoder = new JpegEncoder();
+
+ image.Dispose();
+
+ // Image
+ Assert.Throws(() => 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(this.configuration, 10, 10);
+ var genericImage = (Image)image;
+
+ image.Dispose();
+
+ // Image
+ Assert.Throws(() => { var res = image.Clone(this.configuration); });
+ Assert.Throws(() => { var res = image.CloneAs(this.configuration); });
+ Assert.Throws(() => { var res = image.GetPixelRowSpan(default); });
+ Assert.Throws(() => { var res = image.TryGetSinglePixelSpan(out var _); });
+
+ // Image
+ Assert.Throws(() => { var res = genericImage.CloneAs(this.configuration); });
+ }
+ }
}
}