Browse Source

Use sampling strategy in BmpEncoder

pull/2269/head
James Jackson-South 4 years ago
parent
commit
715b0ba2d9
  1. 53
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  2. 10
      src/ImageSharp/ImageExtensions.Internal.cs
  3. 9
      src/ImageSharp/ImageFrameCollection{TPixel}.cs
  4. 142
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
  5. 194
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs

53
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -9,7 +9,6 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Bmp; namespace SixLabors.ImageSharp.Formats.Bmp;
@ -92,6 +91,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// </summary> /// </summary>
private readonly IQuantizer quantizer; private readonly IQuantizer quantizer;
/// <summary>
/// The pixel sampling strategy for quantization.
/// </summary>
private readonly IPixelSamplingStrategy pixelSamplingStrategy;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class. /// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
/// </summary> /// </summary>
@ -101,7 +105,8 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
{ {
this.memoryAllocator = memoryAllocator; this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = encoder.BitsPerPixel; this.bitsPerPixel = encoder.BitsPerPixel;
this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree; this.quantizer = encoder.Quantizer;
this.pixelSamplingStrategy = encoder.GlobalPixelSamplingStrategy;
this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3; this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;
} }
@ -159,7 +164,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer); WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer);
this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize); this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize);
this.WriteImage(stream, image.Frames.RootFrame); this.WriteImage(stream, image);
WriteColorProfile(stream, iccProfileData, buffer); WriteColorProfile(stream, iccProfileData, buffer);
stream.Flush(); stream.Flush();
@ -311,10 +316,10 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// <param name="image"> /// <param name="image">
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data. /// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param> /// </param>
private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image) private void WriteImage<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Buffer2D<TPixel> pixels = image.PixelBuffer; Buffer2D<TPixel> pixels = image.Frames.RootFrame.PixelBuffer;
switch (this.bitsPerPixel) switch (this.bitsPerPixel)
{ {
case BmpBitsPerPixel.Pixel32: case BmpBitsPerPixel.Pixel32:
@ -433,8 +438,8 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="image"> The <see cref="Image{TPixel}"/> containing pixel data.</param>
private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image) private void Write8BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
bool isL8 = typeof(TPixel) == typeof(L8); bool isL8 = typeof(TPixel) == typeof(L8);
@ -456,14 +461,15 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="image"> The <see cref="Image{TPixel}"/> containing pixel data.</param>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param> /// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette) private void Write8BitColor<TPixel>(Stream stream, Image<TPixel> image, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// TODO: Should we use the pixel sampling strategy here?
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration); using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span; ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
@ -487,7 +493,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param> /// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette) private void Write8BitPixelData<TPixel>(Stream stream, Image<TPixel> image, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// Create a color palette with 256 different gray values. // Create a color palette with 256 different gray values.
@ -504,7 +510,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
} }
stream.Write(colorPalette); stream.Write(colorPalette);
Buffer2D<TPixel> imageBuffer = image.PixelBuffer; Buffer2D<TPixel> imageBuffer = image.GetRootFramePixelBuffer();
for (int y = image.Height - 1; y >= 0; y--) for (int y = image.Height - 1; y >= 0; y--)
{ {
ReadOnlySpan<TPixel> inputPixelRow = imageBuffer.DangerousGetRowSpan(y); ReadOnlySpan<TPixel> inputPixelRow = imageBuffer.DangerousGetRowSpan(y);
@ -524,14 +530,17 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write4BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image) private void Write4BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions() using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{ {
MaxColors = 16 MaxColors = 16
}); });
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize4Bit, AllocationOptions.Clean); using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize4Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan(); Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
@ -568,14 +577,17 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write2BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image) private void Write2BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions() using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{ {
MaxColors = 4 MaxColors = 4
}); });
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize2Bit, AllocationOptions.Clean); using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize2Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan(); Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
@ -621,14 +633,17 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param> /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write1BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image) private void Write1BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions() using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{ {
MaxColors = 2 MaxColors = 2
}); });
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize1Bit, AllocationOptions.Clean); using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize1Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan(); Span<byte> colorPalette = colorPaletteBuffer.GetSpan();

10
src/ImageSharp/ImageExtensions.Internal.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp;
public static partial class ImageExtensions public static partial class ImageExtensions
{ {
/// <summary> /// <summary>
/// Locks the image providing access to the pixels. /// Provides access to the image pixels.
/// <remarks> /// <remarks>
/// It is imperative that the accessor is correctly disposed off after use. /// It is imperative that the accessor is correctly disposed of after use.
/// </remarks> /// </remarks>
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
@ -24,7 +24,5 @@ public static partial class ImageExtensions
/// </returns> /// </returns>
internal static Buffer2D<TPixel> GetRootFramePixelBuffer<TPixel>(this Image<TPixel> image) internal static Buffer2D<TPixel> GetRootFramePixelBuffer<TPixel>(this Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => image.Frames.RootFrame.PixelBuffer;
return image.Frames.RootFrame.PixelBuffer;
}
} }

9
src/ImageSharp/ImageFrameCollection{TPixel}.cs

@ -374,10 +374,15 @@ public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumer
} }
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<ImageFrame<TPixel>> GetEnumerator() => this.frames.GetEnumerator(); public IEnumerator<ImageFrame<TPixel>> GetEnumerator()
{
this.EnsureNotDisposed();
return this.frames.GetEnumerator();
}
/// <inheritdoc/> /// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private void ValidateFrame(ImageFrame<TPixel> frame) private void ValidateFrame(ImageFrame<TPixel> frame)
{ {

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

@ -14,9 +14,7 @@ public abstract partial class ImageFrameCollectionTests
{ {
[Fact] [Fact]
public void Constructor_ShouldCreateOneFrame() public void Constructor_ShouldCreateOneFrame()
{ => Assert.Equal(1, this.Collection.Count);
Assert.Equal(1, this.Collection.Count);
}
[Fact] [Fact]
public void AddNewFrame_FramesMustHaveSameSize() public void AddNewFrame_FramesMustHaveSameSize()
@ -24,7 +22,7 @@ public abstract partial class ImageFrameCollectionTests
ArgumentException ex = Assert.Throws<ArgumentException>( ArgumentException ex = Assert.Throws<ArgumentException>(
() => () =>
{ {
using var frame = new ImageFrame<Rgba32>(Configuration.Default, 1, 1); using ImageFrame<Rgba32> frame = new(Configuration.Default, 1, 1);
using ImageFrame<Rgba32> addedFrame = this.Collection.AddFrame(frame); using ImageFrame<Rgba32> addedFrame = this.Collection.AddFrame(frame);
}); });
@ -75,7 +73,7 @@ public abstract partial class ImageFrameCollectionTests
ArgumentException ex = Assert.Throws<ArgumentException>( ArgumentException ex = Assert.Throws<ArgumentException>(
() => () =>
{ {
using var frame = new ImageFrame<Rgba32>(Configuration.Default, 1, 1); using ImageFrame<Rgba32> frame = new(Configuration.Default, 1, 1);
using ImageFrame<Rgba32> insertedFrame = this.Collection.InsertFrame(1, frame); using ImageFrame<Rgba32> insertedFrame = this.Collection.InsertFrame(1, frame);
}); });
@ -100,8 +98,8 @@ public abstract partial class ImageFrameCollectionTests
ArgumentException ex = Assert.Throws<ArgumentException>( ArgumentException ex = Assert.Throws<ArgumentException>(
() => () =>
{ {
using var imageFrame1 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame1 = new(Configuration.Default, 10, 10);
using var imageFrame2 = new ImageFrame<Rgba32>(Configuration.Default, 1, 1); using ImageFrame<Rgba32> imageFrame2 = new(Configuration.Default, 1, 1);
new ImageFrameCollection<Rgba32>( new ImageFrameCollection<Rgba32>(
this.Image, this.Image,
new[] { imageFrame1, imageFrame2 }); new[] { imageFrame1, imageFrame2 });
@ -113,8 +111,8 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void RemoveAtFrame_ThrowIfRemovingLastFrame() public void RemoveAtFrame_ThrowIfRemovingLastFrame()
{ {
using var imageFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame = new(Configuration.Default, 10, 10);
var collection = new ImageFrameCollection<Rgba32>( ImageFrameCollection<Rgba32> collection = new(
this.Image, this.Image,
new[] { imageFrame }); new[] { imageFrame });
@ -126,9 +124,9 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist()
{ {
using var imageFrame1 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame1 = new(Configuration.Default, 10, 10);
using var imageFrame2 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame2 = new(Configuration.Default, 10, 10);
var collection = new ImageFrameCollection<Rgba32>( ImageFrameCollection<Rgba32> collection = new(
this.Image, this.Image,
new[] { imageFrame1, imageFrame2 }); new[] { imageFrame1, imageFrame2 });
@ -139,9 +137,9 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void RootFrameIsFrameAtIndexZero() public void RootFrameIsFrameAtIndexZero()
{ {
using var imageFrame1 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame1 = new(Configuration.Default, 10, 10);
using var imageFrame2 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame2 = new(Configuration.Default, 10, 10);
var collection = new ImageFrameCollection<Rgba32>( ImageFrameCollection<Rgba32> collection = new(
this.Image, this.Image,
new[] { imageFrame1, imageFrame2 }); new[] { imageFrame1, imageFrame2 });
@ -151,9 +149,9 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void ConstructorPopulatesFrames() public void ConstructorPopulatesFrames()
{ {
using var imageFrame1 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame1 = new(Configuration.Default, 10, 10);
using var imageFrame2 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame2 = new(Configuration.Default, 10, 10);
var collection = new ImageFrameCollection<Rgba32>( ImageFrameCollection<Rgba32> collection = new(
this.Image, this.Image,
new[] { imageFrame1, imageFrame2 }); new[] { imageFrame1, imageFrame2 });
@ -163,9 +161,9 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void DisposeClearsCollection() public void DisposeClearsCollection()
{ {
using var imageFrame1 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame1 = new(Configuration.Default, 10, 10);
using var imageFrame2 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame2 = new(Configuration.Default, 10, 10);
var collection = new ImageFrameCollection<Rgba32>( ImageFrameCollection<Rgba32> collection = new(
this.Image, this.Image,
new[] { imageFrame1, imageFrame2 }); new[] { imageFrame1, imageFrame2 });
@ -177,9 +175,9 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void Dispose_DisposesAllInnerFrames() public void Dispose_DisposesAllInnerFrames()
{ {
using var imageFrame1 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame1 = new(Configuration.Default, 10, 10);
using var imageFrame2 = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> imageFrame2 = new(Configuration.Default, 10, 10);
var collection = new ImageFrameCollection<Rgba32>( ImageFrameCollection<Rgba32> collection = new(
this.Image, this.Image,
new[] { imageFrame1, imageFrame2 }); new[] { imageFrame1, imageFrame2 });
@ -188,11 +186,8 @@ public abstract partial class ImageFrameCollectionTests
Assert.All( Assert.All(
framesSnapShot, framesSnapShot,
f => f => // The pixel source of the frame is null after its been disposed.
{ Assert.Null(f.PixelBuffer));
// The pixel source of the frame is null after its been disposed.
Assert.Null(f.PixelBuffer);
});
} }
[Theory] [Theory]
@ -200,18 +195,14 @@ public abstract partial class ImageFrameCollectionTests
public void CloneFrame<TPixel>(TestImageProvider<TPixel> provider) public void CloneFrame<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> img = provider.GetImage()) using Image<TPixel> img = provider.GetImage();
{ using ImageFrame<Rgba32> imageFrame = new(Configuration.Default, 10, 10);
using var imageFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); // add a frame anyway
using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); // add a frame anyway using Image<TPixel> cloned = img.Frames.CloneFrame(0);
using (Image<TPixel> cloned = img.Frames.CloneFrame(0)) Assert.Equal(2, img.Frames.Count);
{ Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory<TPixel> imgMem));
Assert.Equal(2, img.Frames.Count);
Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory<TPixel> imgMem)); cloned.ComparePixelBufferTo(imgMem);
cloned.ComparePixelBufferTo(imgMem);
}
}
} }
[Theory] [Theory]
@ -219,19 +210,15 @@ public abstract partial class ImageFrameCollectionTests
public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider) public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> img = provider.GetImage()) using Image<TPixel> img = provider.GetImage();
{ Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory<TPixel> imgMemory));
Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory<TPixel> imgMemory)); TPixel[] sourcePixelData = imgMemory.ToArray();
TPixel[] sourcePixelData = imgMemory.ToArray();
using ImageFrame<Rgba32> imageFrame = new(Configuration.Default, 10, 10);
using var imageFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame);
using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); using Image<TPixel> cloned = img.Frames.ExportFrame(0);
using (Image<TPixel> cloned = img.Frames.ExportFrame(0)) Assert.Equal(1, img.Frames.Count);
{ cloned.ComparePixelBufferTo(sourcePixelData.AsSpan());
Assert.Equal(1, img.Frames.Count);
cloned.ComparePixelBufferTo(sourcePixelData.AsSpan());
}
}
} }
[Fact] [Fact]
@ -266,7 +253,7 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void AddFrame_clones_sourceFrame() public void AddFrame_clones_sourceFrame()
{ {
using var otherFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> otherFrame = new(Configuration.Default, 10, 10);
using ImageFrame<Rgba32> addedFrame = this.Image.Frames.AddFrame(otherFrame); using ImageFrame<Rgba32> addedFrame = this.Image.Frames.AddFrame(otherFrame);
Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> otherFrameMem)); Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> otherFrameMem));
@ -277,7 +264,7 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void InsertFrame_clones_sourceFrame() public void InsertFrame_clones_sourceFrame()
{ {
using var otherFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> otherFrame = new(Configuration.Default, 10, 10);
using ImageFrame<Rgba32> addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); using ImageFrame<Rgba32> addedFrame = this.Image.Frames.InsertFrame(0, otherFrame);
Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> otherFrameMem)); Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> otherFrameMem));
@ -332,7 +319,7 @@ public abstract partial class ImageFrameCollectionTests
this.Image.Frames.CreateFrame(); this.Image.Frames.CreateFrame();
} }
using var frame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); using ImageFrame<Rgba32> frame = new(Configuration.Default, 10, 10);
Assert.False(this.Image.Frames.Contains(frame)); Assert.False(this.Image.Frames.Contains(frame));
} }
@ -343,14 +330,13 @@ public abstract partial class ImageFrameCollectionTests
configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = 1000 }; configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = 1000 };
configuration.PreferContiguousImageBuffers = true; configuration.PreferContiguousImageBuffers = true;
using var image = new Image<Rgba32>(configuration, 100, 100); using Image<Rgba32> image = new(configuration, 100, 100);
image.Frames.CreateFrame(); image.Frames.CreateFrame();
image.Frames.InsertFrame(0, image.Frames[0]); image.Frames.InsertFrame(0, image.Frames[0]);
image.Frames.CreateFrame(Color.Red); image.Frames.CreateFrame(Color.Red);
Assert.Equal(4, image.Frames.Count); Assert.Equal(4, image.Frames.Count);
IEnumerable<ImageFrame<Rgba32>> frames = image.Frames; foreach (ImageFrame<Rgba32> frame in image.Frames)
foreach (ImageFrame<Rgba32> frame in frames)
{ {
Assert.True(frame.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> _)); Assert.True(frame.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> _));
} }
@ -359,8 +345,8 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void DisposeCall_NoThrowIfCalledMultiple() public void DisposeCall_NoThrowIfCalledMultiple()
{ {
var image = new Image<Rgba32>(Configuration.Default, 10, 10); Image<Rgba32> image = new(Configuration.Default, 10, 10);
var frameCollection = image.Frames as ImageFrameCollection; ImageFrameCollection<Rgba32> frameCollection = image.Frames;
image.Dispose(); // this should invalidate underlying collection as well image.Dispose(); // this should invalidate underlying collection as well
frameCollection.Dispose(); frameCollection.Dispose();
@ -369,33 +355,33 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void PublicProperties_ThrowIfDisposed() public void PublicProperties_ThrowIfDisposed()
{ {
var image = new Image<Rgba32>(Configuration.Default, 10, 10); Image<Rgba32> image = new(Configuration.Default, 10, 10);
var frameCollection = image.Frames as ImageFrameCollection; ImageFrameCollection<Rgba32> frameCollection = image.Frames;
image.Dispose(); // this should invalidate underlying collection as well image.Dispose(); // this should invalidate underlying collection as well
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.RootFrame; }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame prop = frameCollection.RootFrame; });
} }
[Fact] [Fact]
public void PublicMethods_ThrowIfDisposed() public void PublicMethods_ThrowIfDisposed()
{ {
var image = new Image<Rgba32>(Configuration.Default, 10, 10); Image<Rgba32> image = new(Configuration.Default, 10, 10);
var frameCollection = image.Frames as ImageFrameCollection; ImageFrameCollection<Rgba32> frameCollection = image.Frames;
image.Dispose(); // this should invalidate underlying collection as well image.Dispose(); // this should invalidate underlying collection as well
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.AddFrame(default); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> res = frameCollection.AddFrame(default(ImageFrame<Rgba32>)); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CloneFrame(default); }); Assert.Throws<ObjectDisposedException>(() => { Image<Rgba32> res = frameCollection.CloneFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.Contains(default); }); Assert.Throws<ObjectDisposedException>(() => { bool res = frameCollection.Contains(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CreateFrame(); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> res = frameCollection.CreateFrame(); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CreateFrame(default); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> res = frameCollection.CreateFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.ExportFrame(default); }); Assert.Throws<ObjectDisposedException>(() => { Image<Rgba32> res = frameCollection.ExportFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.GetEnumerator(); }); Assert.Throws<ObjectDisposedException>(() => { IEnumerator<ImageFrame<Rgba32>> res = frameCollection.GetEnumerator(); });
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.IndexOf(default); }); Assert.Throws<ObjectDisposedException>(() => { int prop = frameCollection.IndexOf(default); });
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.InsertFrame(default, default); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> prop = frameCollection.InsertFrame(default, default); });
Assert.Throws<ObjectDisposedException>(() => { frameCollection.RemoveFrame(default); }); Assert.Throws<ObjectDisposedException>(() => frameCollection.RemoveFrame(default));
Assert.Throws<ObjectDisposedException>(() => { frameCollection.MoveFrame(default, default); }); Assert.Throws<ObjectDisposedException>(() => frameCollection.MoveFrame(default, default));
} }
} }
} }

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

@ -19,7 +19,7 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void AddFrame_OfDifferentPixelType() public void AddFrame_OfDifferentPixelType()
{ {
using (var sourceImage = new Image<Bgra32>( using (Image<Bgra32> sourceImage = new(
this.Image.GetConfiguration(), this.Image.GetConfiguration(),
this.Image.Width, this.Image.Width,
this.Image.Height, this.Image.Height,
@ -32,7 +32,7 @@ public abstract partial class ImageFrameCollectionTests
Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray();
Assert.Equal(2, this.Collection.Count); Assert.Equal(2, this.Collection.Count);
var actualFrame = (ImageFrame<Rgba32>)this.Collection[1]; ImageFrame<Rgba32> actualFrame = (ImageFrame<Rgba32>)this.Collection[1];
actualFrame.ComparePixelBufferTo(expectedAllBlue); actualFrame.ComparePixelBufferTo(expectedAllBlue);
} }
@ -40,7 +40,7 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void InsertFrame_OfDifferentPixelType() public void InsertFrame_OfDifferentPixelType()
{ {
using (var sourceImage = new Image<Bgra32>( using (Image<Bgra32> sourceImage = new(
this.Image.GetConfiguration(), this.Image.GetConfiguration(),
this.Image.Width, this.Image.Width,
this.Image.Height, this.Image.Height,
@ -53,25 +53,20 @@ public abstract partial class ImageFrameCollectionTests
Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray();
Assert.Equal(2, this.Collection.Count); Assert.Equal(2, this.Collection.Count);
var actualFrame = (ImageFrame<Rgba32>)this.Collection[0]; ImageFrame<Rgba32> actualFrame = (ImageFrame<Rgba32>)this.Collection[0];
actualFrame.ComparePixelBufferTo(expectedAllBlue); actualFrame.ComparePixelBufferTo(expectedAllBlue);
} }
[Fact] [Fact]
public void Constructor_ShouldCreateOneFrame() public void Constructor_ShouldCreateOneFrame()
{ => Assert.Equal(1, this.Collection.Count);
Assert.Equal(1, this.Collection.Count);
}
[Fact] [Fact]
public void AddNewFrame_FramesMustHaveSameSize() public void AddNewFrame_FramesMustHaveSameSize()
{ {
ArgumentException ex = Assert.Throws<ArgumentException>( ArgumentException ex = Assert.Throws<ArgumentException>(
() => () => this.Collection.AddFrame(new ImageFrame<Rgba32>(Configuration.Default, 1, 1)));
{
this.Collection.AddFrame(new ImageFrame<Rgba32>(Configuration.Default, 1, 1));
});
Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message);
} }
@ -80,10 +75,7 @@ public abstract partial class ImageFrameCollectionTests
public void AddNewFrame_Frame_FramesNotBeNull() public void AddNewFrame_Frame_FramesNotBeNull()
{ {
ArgumentNullException ex = Assert.Throws<ArgumentNullException>( ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
() => () => this.Collection.AddFrame(null));
{
this.Collection.AddFrame(null);
});
Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message);
} }
@ -92,10 +84,7 @@ public abstract partial class ImageFrameCollectionTests
public void InsertNewFrame_FramesMustHaveSameSize() public void InsertNewFrame_FramesMustHaveSameSize()
{ {
ArgumentException ex = Assert.Throws<ArgumentException>( ArgumentException ex = Assert.Throws<ArgumentException>(
() => () => this.Collection.InsertFrame(1, new ImageFrame<Rgba32>(Configuration.Default, 1, 1)));
{
this.Collection.InsertFrame(1, new ImageFrame<Rgba32>(Configuration.Default, 1, 1));
});
Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message);
} }
@ -104,10 +93,7 @@ public abstract partial class ImageFrameCollectionTests
public void InsertNewFrame_FramesNotBeNull() public void InsertNewFrame_FramesNotBeNull()
{ {
ArgumentNullException ex = Assert.Throws<ArgumentNullException>( ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
() => () => this.Collection.InsertFrame(1, null));
{
this.Collection.InsertFrame(1, null);
});
Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message);
} }
@ -116,10 +102,7 @@ public abstract partial class ImageFrameCollectionTests
public void RemoveAtFrame_ThrowIfRemovingLastFrame() public void RemoveAtFrame_ThrowIfRemovingLastFrame()
{ {
InvalidOperationException ex = Assert.Throws<InvalidOperationException>( InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
() => () => this.Collection.RemoveFrame(0));
{
this.Collection.RemoveFrame(0);
});
Assert.Equal("Cannot remove last frame.", ex.Message); Assert.Equal("Cannot remove last frame.", ex.Message);
} }
@ -134,30 +117,24 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void RootFrameIsFrameAtIndexZero() public void RootFrameIsFrameAtIndexZero()
{ => Assert.Equal(this.Collection.RootFrame, this.Collection[0]);
Assert.Equal(this.Collection.RootFrame, this.Collection[0]);
}
[Theory] [Theory]
[WithTestPatternImages(10, 10, PixelTypes.Rgba32 | PixelTypes.Bgr24)] [WithTestPatternImages(10, 10, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public void CloneFrame<TPixel>(TestImageProvider<TPixel> provider) public void CloneFrame<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> img = provider.GetImage()) using Image<TPixel> img = provider.GetImage();
{ ImageFrameCollection nonGenericFrameCollection = img.Frames;
ImageFrameCollection nonGenericFrameCollection = img.Frames;
nonGenericFrameCollection.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10)); // add a frame anyway nonGenericFrameCollection.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10)); // add a frame anyway
using (Image cloned = nonGenericFrameCollection.CloneFrame(0)) using Image cloned = nonGenericFrameCollection.CloneFrame(0);
{ Assert.Equal(2, img.Frames.Count);
Assert.Equal(2, img.Frames.Count);
var expectedClone = (Image<TPixel>)cloned; Image<TPixel> expectedClone = (Image<TPixel>)cloned;
Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory<TPixel> imgMem)); Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory<TPixel> imgMem));
expectedClone.ComparePixelBufferTo(imgMem); expectedClone.ComparePixelBufferTo(imgMem);
}
}
} }
[Theory] [Theory]
@ -165,22 +142,18 @@ public abstract partial class ImageFrameCollectionTests
public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider) public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> img = provider.GetImage()) using Image<TPixel> img = provider.GetImage();
{ Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory<TPixel> imgMem));
Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory<TPixel> imgMem)); TPixel[] sourcePixelData = imgMem.ToArray();
TPixel[] sourcePixelData = imgMem.ToArray();
ImageFrameCollection nonGenericFrameCollection = img.Frames; ImageFrameCollection nonGenericFrameCollection = img.Frames;
nonGenericFrameCollection.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10)); nonGenericFrameCollection.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10));
using (Image cloned = nonGenericFrameCollection.ExportFrame(0)) using Image cloned = nonGenericFrameCollection.ExportFrame(0);
{ Assert.Equal(1, img.Frames.Count);
Assert.Equal(1, img.Frames.Count);
var expectedClone = (Image<TPixel>)cloned; Image<TPixel> expectedClone = (Image<TPixel>)cloned;
expectedClone.ComparePixelBufferTo(sourcePixelData.AsSpan()); expectedClone.ComparePixelBufferTo(sourcePixelData.AsSpan());
}
}
} }
[Fact] [Fact]
@ -190,7 +163,7 @@ public abstract partial class ImageFrameCollectionTests
Assert.Equal(2, this.Image.Frames.Count); Assert.Equal(2, this.Image.Frames.Count);
var frame = (ImageFrame<Rgba32>)this.Image.Frames[1]; ImageFrame<Rgba32> frame = (ImageFrame<Rgba32>)this.Image.Frames[1];
frame.ComparePixelBufferTo(default(Rgba32)); frame.ComparePixelBufferTo(default(Rgba32));
} }
@ -202,7 +175,7 @@ public abstract partial class ImageFrameCollectionTests
Assert.Equal(2, this.Image.Frames.Count); Assert.Equal(2, this.Image.Frames.Count);
var frame = (ImageFrame<Rgba32>)this.Image.Frames[1]; ImageFrame<Rgba32> frame = (ImageFrame<Rgba32>)this.Image.Frames[1];
frame.ComparePixelBufferTo(Color.HotPink); frame.ComparePixelBufferTo(Color.HotPink);
} }
@ -210,132 +183,127 @@ public abstract partial class ImageFrameCollectionTests
[Fact] [Fact]
public void MoveFrame_LeavesFrameInCorrectLocation() public void MoveFrame_LeavesFrameInCorrectLocation()
{ {
for (var i = 0; i < 9; i++) for (int i = 0; i < 9; i++)
{ {
this.Image.Frames.CreateFrame(); this.Image.Frames.CreateFrame();
} }
var frame = this.Image.Frames[4]; ImageFrame frame = this.Image.Frames[4];
this.Image.Frames.MoveFrame(4, 7); this.Image.Frames.MoveFrame(4, 7);
var newIndex = this.Image.Frames.IndexOf(frame); int newIndex = this.Image.Frames.IndexOf(frame);
Assert.Equal(7, newIndex); Assert.Equal(7, newIndex);
} }
[Fact] [Fact]
public void IndexOf_ReturnsCorrectIndex() public void IndexOf_ReturnsCorrectIndex()
{ {
for (var i = 0; i < 9; i++) for (int i = 0; i < 9; i++)
{ {
this.Image.Frames.CreateFrame(); this.Image.Frames.CreateFrame();
} }
var frame = this.Image.Frames[4]; ImageFrame frame = this.Image.Frames[4];
var index = this.Image.Frames.IndexOf(frame); int index = this.Image.Frames.IndexOf(frame);
Assert.Equal(4, index); Assert.Equal(4, index);
} }
[Fact] [Fact]
public void Contains_TrueIfMember() public void Contains_TrueIfMember()
{ {
for (var i = 0; i < 9; i++) for (int i = 0; i < 9; i++)
{ {
this.Image.Frames.CreateFrame(); this.Image.Frames.CreateFrame();
} }
var frame = this.Image.Frames[4]; ImageFrame frame = this.Image.Frames[4];
Assert.True(this.Image.Frames.Contains(frame)); Assert.True(this.Image.Frames.Contains(frame));
} }
[Fact] [Fact]
public void Contains_FalseIfNonMember() public void Contains_FalseIfNonMember()
{ {
for (var i = 0; i < 9; i++) for (int i = 0; i < 9; i++)
{ {
this.Image.Frames.CreateFrame(); this.Image.Frames.CreateFrame();
} }
var frame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); ImageFrame<Rgba32> frame = new(Configuration.Default, 10, 10);
Assert.False(this.Image.Frames.Contains(frame)); Assert.False(this.Image.Frames.Contains(frame));
} }
[Fact] [Fact]
public void PublicProperties_ThrowIfDisposed() public void PublicProperties_ThrowIfDisposed()
{ {
var image = new Image<Rgba32>(Configuration.Default, 10, 10); Image<Rgba32> image = new(Configuration.Default, 10, 10);
var frameCollection = image.Frames; ImageFrameCollection<Rgba32> frameCollection = image.Frames;
image.Dispose(); // this should invalidate underlying collection as well image.Dispose(); // this should invalidate underlying collection as well
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.RootFrame; }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> prop = frameCollection.RootFrame; });
} }
[Fact] [Fact]
public void PublicMethods_ThrowIfDisposed() public void PublicMethods_ThrowIfDisposed()
{ {
var image = new Image<Rgba32>(Configuration.Default, 10, 10); Image<Rgba32> image = new(Configuration.Default, 10, 10);
var frameCollection = image.Frames; ImageFrameCollection<Rgba32> frameCollection = image.Frames;
var rgba32Array = new Rgba32[0]; Rgba32[] rgba32Array = Array.Empty<Rgba32>();
image.Dispose(); // this should invalidate underlying collection as well image.Dispose(); // this should invalidate underlying collection as well
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.AddFrame((ImageFrame)null); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame res = frameCollection.AddFrame((ImageFrame)null); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.AddFrame(rgba32Array); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> res = frameCollection.AddFrame(rgba32Array); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.AddFrame((ImageFrame<Rgba32>)null); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> res = frameCollection.AddFrame((ImageFrame<Rgba32>)null); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.AddFrame(rgba32Array.AsSpan()); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> res = frameCollection.AddFrame(rgba32Array.AsSpan()); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CloneFrame(default); }); Assert.Throws<ObjectDisposedException>(() => { Image<Rgba32> res = frameCollection.CloneFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.Contains(default); }); Assert.Throws<ObjectDisposedException>(() => { bool res = frameCollection.Contains(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CreateFrame(); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> res = frameCollection.CreateFrame(); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.CreateFrame(default); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> res = frameCollection.CreateFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.ExportFrame(default); }); Assert.Throws<ObjectDisposedException>(() => { Image<Rgba32> res = frameCollection.ExportFrame(default); });
Assert.Throws<ObjectDisposedException>(() => { var res = frameCollection.GetEnumerator(); }); Assert.Throws<ObjectDisposedException>(() => { IEnumerator<ImageFrame<Rgba32>> res = frameCollection.GetEnumerator(); });
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.IndexOf(default); }); Assert.Throws<ObjectDisposedException>(() => { int prop = frameCollection.IndexOf(default); });
Assert.Throws<ObjectDisposedException>(() => { var prop = frameCollection.InsertFrame(default, default); }); Assert.Throws<ObjectDisposedException>(() => { ImageFrame<Rgba32> prop = frameCollection.InsertFrame(default, default); });
Assert.Throws<ObjectDisposedException>(() => { frameCollection.RemoveFrame(default); }); Assert.Throws<ObjectDisposedException>(() => frameCollection.RemoveFrame(default));
Assert.Throws<ObjectDisposedException>(() => { frameCollection.MoveFrame(default, default); }); Assert.Throws<ObjectDisposedException>(() => frameCollection.MoveFrame(default, default));
} }
/// <summary> /// <summary>
/// Integration test for end-to end API validation. /// Integration test for end-to end API validation.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type of the image.</typeparam> /// <typeparam name="TPixel">The pixel type of the image.</typeparam>
/// <param name="provider">The test image provider</param>
[Theory] [Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
public void ConstructGif_FromDifferentPixelTypes<TPixel>(TestImageProvider<TPixel> provider) public void ConstructGif_FromDifferentPixelTypes<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image source = provider.GetImage()) using Image source = provider.GetImage();
using (var dest = new Image<TPixel>(source.GetConfiguration(), source.Width, source.Height)) using Image<TPixel> dest = new(source.GetConfiguration(), source.Width, source.Height);
// Giphy.gif has 5 frames
ImportFrameAs<Bgra32>(source.Frames, dest.Frames, 0);
ImportFrameAs<Argb32>(source.Frames, dest.Frames, 1);
ImportFrameAs<Rgba64>(source.Frames, dest.Frames, 2);
ImportFrameAs<Rgba32>(source.Frames, dest.Frames, 3);
ImportFrameAs<Bgra32>(source.Frames, dest.Frames, 4);
// Drop the original empty root frame:
dest.Frames.RemoveFrame(0);
dest.DebugSave(provider, appendSourceFileOrDescription: false, extension: "gif");
dest.CompareToOriginal(provider);
for (int i = 0; i < 5; i++)
{ {
// Giphy.gif has 5 frames CompareGifMetadata(source.Frames[i], dest.Frames[i]);
ImportFrameAs<Bgra32>(source.Frames, dest.Frames, 0);
ImportFrameAs<Argb32>(source.Frames, dest.Frames, 1);
ImportFrameAs<Rgba64>(source.Frames, dest.Frames, 2);
ImportFrameAs<Rgba32>(source.Frames, dest.Frames, 3);
ImportFrameAs<Bgra32>(source.Frames, dest.Frames, 4);
// Drop the original empty root frame:
dest.Frames.RemoveFrame(0);
dest.DebugSave(provider, appendSourceFileOrDescription: false, extension: "gif");
dest.CompareToOriginal(provider);
for (int i = 0; i < 5; i++)
{
CompareGifMetadata(source.Frames[i], dest.Frames[i]);
}
} }
} }
private static void ImportFrameAs<TPixel>(ImageFrameCollection source, ImageFrameCollection destination, int index) private static void ImportFrameAs<TPixel>(ImageFrameCollection source, ImageFrameCollection destination, int index)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image temp = source.CloneFrame(index)) using Image temp = source.CloneFrame(index);
{ using Image<TPixel> temp2 = temp.CloneAs<TPixel>();
using (Image<TPixel> temp2 = temp.CloneAs<TPixel>()) destination.AddFrame(temp2.Frames.RootFrame);
{
destination.AddFrame(temp2.Frames.RootFrame);
}
}
} }
private static void CompareGifMetadata(ImageFrame a, ImageFrame b) private static void CompareGifMetadata(ImageFrame a, ImageFrame b)

Loading…
Cancel
Save