// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests; public abstract partial class ImageFrameCollectionTests { [GroupOutput("ImageFramesCollectionTests")] public class NonGeneric : ImageFrameCollectionTests { private new Image Image => base.Image; private new ImageFrameCollection Collection => base.Collection; [Fact] public void AddFrame_OfDifferentPixelType() { using (Image sourceImage = new( this.Image.Configuration, this.Image.Width, this.Image.Height, Color.Blue.ToPixel())) { this.Collection.AddFrame(sourceImage.Frames.RootFrame); } Rgba32[] expectedAllBlue = Enumerable.Repeat(Color.Blue.ToPixel(), this.Image.Width * this.Image.Height).ToArray(); Assert.Equal(2, this.Collection.Count); ImageFrame actualFrame = (ImageFrame)this.Collection[1]; actualFrame.ComparePixelBufferTo(expectedAllBlue); } [Fact] public void InsertFrame_OfDifferentPixelType() { using (Image sourceImage = new( this.Image.Configuration, this.Image.Width, this.Image.Height, Color.Blue.ToPixel())) { this.Collection.InsertFrame(0, sourceImage.Frames.RootFrame); } Rgba32[] expectedAllBlue = Enumerable.Repeat(Color.Blue.ToPixel(), this.Image.Width * this.Image.Height).ToArray(); Assert.Equal(2, this.Collection.Count); ImageFrame actualFrame = (ImageFrame)this.Collection[0]; actualFrame.ComparePixelBufferTo(expectedAllBlue); } [Fact] public void Constructor_ShouldCreateOneFrame() => Assert.Equal(1, this.Collection.Count); [Fact] public void AddNewFrame_FramesMustHaveSameSize() { ArgumentException ex = Assert.Throws( () => this.Collection.AddFrame(new ImageFrame(Configuration.Default, 1, 1))); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); } [Fact] public void AddNewFrame_Frame_FramesNotBeNull() { ArgumentNullException ex = Assert.Throws( () => this.Collection.AddFrame(null)); Assert.StartsWith("Value cannot be null. (Parameter 'source')", ex.Message); } [Fact] public void InsertNewFrame_FramesMustHaveSameSize() { ArgumentException ex = Assert.Throws( () => this.Collection.InsertFrame(1, new ImageFrame(Configuration.Default, 1, 1))); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); } [Fact] public void InsertNewFrame_FramesNotBeNull() { ArgumentNullException ex = Assert.Throws( () => this.Collection.InsertFrame(1, null)); Assert.StartsWith("Value cannot be null. (Parameter 'source')", ex.Message); } [Fact] public void RemoveAtFrame_ThrowIfRemovingLastFrame() { InvalidOperationException ex = Assert.Throws( () => this.Collection.RemoveFrame(0)); Assert.Equal("Cannot remove last frame.", ex.Message); } [Fact] public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() { this.Collection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); this.Collection.RemoveFrame(0); Assert.Equal(1, this.Collection.Count); } [Fact] public void RootFrameIsFrameAtIndexZero() => Assert.Equal(this.Collection.RootFrame, this.Collection[0]); [Theory] [WithTestPatternImages(10, 10, PixelTypes.Rgba32 | PixelTypes.Bgr24)] public void CloneFrame(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image img = provider.GetImage(); ImageFrameCollection nonGenericFrameCollection = img.Frames; nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); // add a frame anyway using Image cloned = nonGenericFrameCollection.CloneFrame(0); Assert.Equal(2, img.Frames.Count); Image expectedClone = (Image)cloned; Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); expectedClone.ComparePixelBufferTo(imgMem); } [Theory] [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] public void ExtractFrame(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image img = provider.GetImage(); Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); TPixel[] sourcePixelData = imgMem.ToArray(); ImageFrameCollection nonGenericFrameCollection = img.Frames; nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); using Image cloned = nonGenericFrameCollection.ExportFrame(0); Assert.Equal(1, img.Frames.Count); Image expectedClone = (Image)cloned; expectedClone.ComparePixelBufferTo(sourcePixelData.AsSpan()); } [Fact] public void CreateFrame_Default() { this.Image.Frames.CreateFrame(); Assert.Equal(2, this.Image.Frames.Count); ImageFrame frame = (ImageFrame)this.Image.Frames[1]; frame.ComparePixelBufferTo(default(Rgba32)); } [Fact] public void CreateFrame_CustomFillColor() { this.Image.Frames.CreateFrame(Color.HotPink); Assert.Equal(2, this.Image.Frames.Count); ImageFrame frame = (ImageFrame)this.Image.Frames[1]; frame.ComparePixelBufferTo(Color.HotPink.ToPixel()); } [Fact] public void MoveFrame_LeavesFrameInCorrectLocation() { for (int i = 0; i < 9; i++) { this.Image.Frames.CreateFrame(); } ImageFrame frame = this.Image.Frames[4]; this.Image.Frames.MoveFrame(4, 7); int newIndex = this.Image.Frames.IndexOf(frame); Assert.Equal(7, newIndex); } [Fact] public void IndexOf_ReturnsCorrectIndex() { for (int i = 0; i < 9; i++) { this.Image.Frames.CreateFrame(); } ImageFrame frame = this.Image.Frames[4]; int index = this.Image.Frames.IndexOf(frame); Assert.Equal(4, index); } [Fact] public void Contains_TrueIfMember() { for (int i = 0; i < 9; i++) { this.Image.Frames.CreateFrame(); } ImageFrame frame = this.Image.Frames[4]; Assert.True(this.Image.Frames.Contains(frame)); } [Fact] public void Contains_FalseIfNonMember() { for (int i = 0; i < 9; i++) { this.Image.Frames.CreateFrame(); } ImageFrame frame = new(Configuration.Default, 10, 10); Assert.False(this.Image.Frames.Contains(frame)); } [Fact] public void PublicProperties_ThrowIfDisposed() { Image image = new(Configuration.Default, 10, 10); ImageFrameCollection frameCollection = image.Frames; image.Dispose(); // this should invalidate underlying collection as well Assert.Throws(() => { ImageFrame prop = frameCollection.RootFrame; }); } [Fact] public void PublicMethods_ThrowIfDisposed() { Image image = new(Configuration.Default, 10, 10); ImageFrameCollection frameCollection = image.Frames; Rgba32[] rgba32Array = Array.Empty(); image.Dispose(); // this should invalidate underlying collection as well Assert.Throws(() => { ImageFrame res = frameCollection.AddFrame((ImageFrame)null); }); Assert.Throws(() => { ImageFrame res = frameCollection.AddFrame(rgba32Array); }); Assert.Throws(() => { ImageFrame res = frameCollection.AddFrame((ImageFrame)null); }); Assert.Throws(() => { ImageFrame res = frameCollection.AddFrame(rgba32Array.AsSpan()); }); Assert.Throws(() => { Image res = frameCollection.CloneFrame(default); }); Assert.Throws(() => { bool res = frameCollection.Contains(default); }); Assert.Throws(() => { ImageFrame res = frameCollection.CreateFrame(); }); Assert.Throws(() => { ImageFrame res = frameCollection.CreateFrame(default); }); Assert.Throws(() => { Image res = frameCollection.ExportFrame(default); }); Assert.Throws(() => { IEnumerator> res = frameCollection.GetEnumerator(); }); Assert.Throws(() => { int prop = frameCollection.IndexOf(default); }); Assert.Throws(() => { ImageFrame prop = frameCollection.InsertFrame(default, default); }); Assert.Throws(() => frameCollection.RemoveFrame(default)); Assert.Throws(() => frameCollection.MoveFrame(default, default)); } /// /// Integration test for end-to end API validation. /// /// The pixel type of the image. /// The test image provider [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void ConstructGif_FromDifferentPixelTypes(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image source = provider.GetImage(); using Image dest = new(source.Configuration, source.Width, source.Height); // Giphy.gif has 5 frames ImportFrameAs(source.Frames, dest.Frames, 0); ImportFrameAs(source.Frames, dest.Frames, 1); ImportFrameAs(source.Frames, dest.Frames, 2); ImportFrameAs(source.Frames, dest.Frames, 3); ImportFrameAs(source.Frames, dest.Frames, 4); // Drop the original empty root frame: dest.Frames.RemoveFrame(0); dest.DebugSave(provider, extension: "gif", appendSourceFileOrDescription: false); dest.CompareToOriginal(provider); for (int i = 0; i < 5; i++) { CompareGifMetadata(source.Frames[i], dest.Frames[i]); } } private static void ImportFrameAs(ImageFrameCollection source, ImageFrameCollection destination, int index) where TPixel : unmanaged, IPixel { using Image temp = source.CloneFrame(index); using Image temp2 = temp.CloneAs(); destination.AddFrame(temp2.Frames.RootFrame); } private static void CompareGifMetadata(ImageFrame a, ImageFrame b) { // TODO: all metadata classes should be equatable! GifFrameMetadata aData = a.Metadata.GetGifMetadata(); GifFrameMetadata bData = b.Metadata.GetGifMetadata(); Assert.Equal(aData.DisposalMode, bData.DisposalMode); Assert.Equal(aData.FrameDelay, bData.FrameDelay); if (aData.ColorTableMode == FrameColorTableMode.Local && bData.ColorTableMode == FrameColorTableMode.Local) { Assert.Equal(aData.LocalColorTable.Value.Length, bData.LocalColorTable.Value.Length); } } } }