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); }); + } + } } }