mirror of https://github.com/SixLabors/ImageSharp
Browse Source
* temporarily disable target frameworks * drop DelegateProcessor * drop IImageProcessingContext<TPixel> * drop NamedColors<T> * drop ColorBuilder<T> * drop the *Base postfix for clean class hierarchies * adding basic skeletons * non-generic ImageFrameCollection API definition * non-generic ImageFrameCollection tests * cleanup + docs + more tests * implement ImageFrameCollection methods * tests for generic PixelOperations.To<TDest>() * experimental implementation * fix .ttinclude * generate generic From<TSourcePixel>(...) * fix RgbaVector <--> BT709 Gray pixel conversion * Gray8 and Gray16 using ConvertFromRgbaScaledVector4() by default * fixed all conversion tests * ConstructGif_FromDifferentPixelTypes * fix xmldoc and other StyelCop findings * re-enable all target frameworks * fix NonGenericAddFrame() and NonGenericInsertFrame() * fix remaining bugspull/952/head
committed by
James Jackson-South
50 changed files with 1824 additions and 876 deletions
@ -0,0 +1,91 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a pixel-agnostic image frame containing all pixel data and <see cref="ImageFrameMetadata"/>.
|
|||
/// In case of animated formats like gif, it contains the single frame in a animation.
|
|||
/// In all other cases it is the only frame of the image.
|
|||
/// </summary>
|
|||
public abstract partial class ImageFrame : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ImageFrame"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The <see cref="Configuration"/>.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <param name="height">The height.</param>
|
|||
/// <param name="metadata">The <see cref="ImageFrameMetadata"/>.</param>
|
|||
protected ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) |
|||
{ |
|||
Guard.NotNull(configuration, nameof(configuration)); |
|||
Guard.NotNull(metadata, nameof(metadata)); |
|||
|
|||
this.Configuration = configuration; |
|||
this.MemoryAllocator = configuration.MemoryAllocator; |
|||
this.Width = width; |
|||
this.Height = height; |
|||
this.Metadata = metadata; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="MemoryAllocator" /> to use for buffer allocations.
|
|||
/// </summary>
|
|||
public MemoryAllocator MemoryAllocator { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="Configuration"/> instance associated with this <see cref="ImageFrame{TPixel}"/>.
|
|||
/// </summary>
|
|||
internal Configuration Configuration { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the width.
|
|||
/// </summary>
|
|||
public int Width { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the height.
|
|||
/// </summary>
|
|||
public int Height { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the metadata of the frame.
|
|||
/// </summary>
|
|||
public ImageFrameMetadata Metadata { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of the frame.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="Size"/></returns>
|
|||
public Size Size() => new Size(this.Width, this.Height); |
|||
|
|||
/// <summary>
|
|||
/// Gets the bounds of the frame.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="Rectangle"/></returns>
|
|||
public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); |
|||
|
|||
/// <inheritdoc />
|
|||
public abstract void Dispose(); |
|||
|
|||
internal abstract void CopyPixelsTo<TDestinationPixel>(Span<TDestinationPixel> destination) |
|||
where TDestinationPixel : struct, IPixel<TDestinationPixel>; |
|||
|
|||
/// <summary>
|
|||
/// Updates the size of the image frame.
|
|||
/// </summary>
|
|||
internal void UpdateSize(Size size) |
|||
{ |
|||
this.Width = size.Width; |
|||
this.Height = size.Height; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,358 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates a pixel-specific collection of <see cref="ImageFrame{T}"/> instances
|
|||
/// that make up an <see cref="Image{T}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|||
public sealed class ImageFrameCollection<TPixel> : ImageFrameCollection, IEnumerable<ImageFrame<TPixel>> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly IList<ImageFrame<TPixel>> frames = new List<ImageFrame<TPixel>>(); |
|||
private readonly Image<TPixel> parent; |
|||
|
|||
internal ImageFrameCollection(Image<TPixel> parent, int width, int height, TPixel backgroundColor) |
|||
{ |
|||
this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); |
|||
|
|||
// Frames are already cloned within the caller
|
|||
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, backgroundColor)); |
|||
} |
|||
|
|||
internal ImageFrameCollection(Image<TPixel> parent, int width, int height, MemorySource<TPixel> memorySource) |
|||
{ |
|||
this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); |
|||
|
|||
// Frames are already cloned within the caller
|
|||
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, memorySource)); |
|||
} |
|||
|
|||
internal ImageFrameCollection(Image<TPixel> parent, IEnumerable<ImageFrame<TPixel>> frames) |
|||
{ |
|||
Guard.NotNull(parent, nameof(parent)); |
|||
Guard.NotNull(frames, nameof(frames)); |
|||
|
|||
this.parent = parent; |
|||
|
|||
// Frames are already cloned by the caller
|
|||
foreach (ImageFrame<TPixel> f in frames) |
|||
{ |
|||
this.ValidateFrame(f); |
|||
this.frames.Add(f); |
|||
} |
|||
|
|||
// Ensure at least 1 frame was added to the frames collection
|
|||
if (this.frames.Count == 0) |
|||
{ |
|||
throw new ArgumentException("Must not be empty.", nameof(frames)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of frames.
|
|||
/// </summary>
|
|||
public override int Count => this.frames.Count; |
|||
|
|||
/// <summary>
|
|||
/// Gets the root frame.
|
|||
/// </summary>
|
|||
public new ImageFrame<TPixel> RootFrame => this.frames.Count > 0 ? this.frames[0] : null; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override ImageFrame NonGenericRootFrame => this.RootFrame; |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ImageFrame{TPixel}"/> at the specified index.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The <see cref="ImageFrame{TPixel}"/>.
|
|||
/// </value>
|
|||
/// <param name="index">The index.</param>
|
|||
/// <returns>The <see cref="ImageFrame{TPixel}"/> at the specified index.</returns>
|
|||
public new ImageFrame<TPixel> this[int index] => this.frames[index]; |
|||
|
|||
/// <inheritdoc />
|
|||
public override int IndexOf(ImageFrame frame) |
|||
{ |
|||
return frame is ImageFrame<TPixel> specific ? this.IndexOf(specific) : -1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines the index of a specific <paramref name="frame"/> in the <seealso cref="ImageFrameCollection{TPixel}"/>.
|
|||
/// </summary>
|
|||
/// <param name="frame">The <seealso cref="ImageFrame{TPixel}"/> to locate in the <seealso cref="ImageFrameCollection{TPixel}"/>.</param>
|
|||
/// <returns>The index of item if found in the list; otherwise, -1.</returns>
|
|||
public int IndexOf(ImageFrame<TPixel> frame) => this.frames.IndexOf(frame); |
|||
|
|||
/// <summary>
|
|||
/// Clones and inserts the <paramref name="source"/> into the <seealso cref="ImageFrameCollection{TPixel}"/> at the specified <paramref name="index"/>.
|
|||
/// </summary>
|
|||
/// <param name="index">The zero-based index to insert the frame at.</param>
|
|||
/// <param name="source">The <seealso cref="ImageFrame{TPixel}"/> to clone and insert into the <seealso cref="ImageFrameCollection{TPixel}"/>.</param>
|
|||
/// <exception cref="ArgumentException">Frame must have the same dimensions as the image.</exception>
|
|||
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
|
|||
public ImageFrame<TPixel> InsertFrame(int index, ImageFrame<TPixel> source) |
|||
{ |
|||
this.ValidateFrame(source); |
|||
ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration()); |
|||
this.frames.Insert(index, clonedFrame); |
|||
return clonedFrame; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clones the <paramref name="source"/> frame and appends the clone to the end of the collection.
|
|||
/// </summary>
|
|||
/// <param name="source">The raw pixel data to generate the <seealso cref="ImageFrame{TPixel}"/> from.</param>
|
|||
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
|
|||
public ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> source) |
|||
{ |
|||
this.ValidateFrame(source); |
|||
ImageFrame<TPixel> clonedFrame = source.Clone(this.parent.GetConfiguration()); |
|||
this.frames.Add(clonedFrame); |
|||
return clonedFrame; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the
|
|||
/// new frame at the end of the collection.
|
|||
/// </summary>
|
|||
/// <param name="source">The raw pixel data to generate the <seealso cref="ImageFrame{TPixel}"/> from.</param>
|
|||
/// <returns>The new <see cref="ImageFrame{TPixel}"/>.</returns>
|
|||
public ImageFrame<TPixel> AddFrame(ReadOnlySpan<TPixel> source) |
|||
{ |
|||
var frame = ImageFrame.LoadPixelData( |
|||
this.parent.GetConfiguration(), |
|||
source, |
|||
this.RootFrame.Width, |
|||
this.RootFrame.Height); |
|||
this.frames.Add(frame); |
|||
return frame; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the
|
|||
/// new frame at the end of the collection.
|
|||
/// </summary>
|
|||
/// <param name="source">The raw pixel data to generate the <seealso cref="ImageFrame{TPixel}"/> from.</param>
|
|||
/// <returns>The new <see cref="ImageFrame{TPixel}"/>.</returns>
|
|||
public ImageFrame<TPixel> AddFrame(TPixel[] source) |
|||
{ |
|||
Guard.NotNull(source, nameof(source)); |
|||
return this.AddFrame(source.AsSpan()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the frame at the specified index and frees all freeable resources associated with it.
|
|||
/// </summary>
|
|||
/// <param name="index">The zero-based index of the frame to remove.</param>
|
|||
/// <exception cref="InvalidOperationException">Cannot remove last frame.</exception>
|
|||
public override void RemoveFrame(int index) |
|||
{ |
|||
if (index == 0 && this.Count == 1) |
|||
{ |
|||
throw new InvalidOperationException("Cannot remove last frame."); |
|||
} |
|||
|
|||
ImageFrame<TPixel> frame = this.frames[index]; |
|||
this.frames.RemoveAt(index); |
|||
frame.Dispose(); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override bool Contains(ImageFrame frame) => |
|||
frame is ImageFrame<TPixel> specific && this.Contains(specific); |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the <seealso cref="ImageFrameCollection{TPixel}"/> contains the <paramref name="frame"/>.
|
|||
/// </summary>
|
|||
/// <param name="frame">The frame.</param>
|
|||
/// <returns>
|
|||
/// <c>true</c> if the <seealso cref="ImageFrameCollection{TPixel}"/> contains the specified frame; otherwise, <c>false</c>.
|
|||
/// </returns>
|
|||
public bool Contains(ImageFrame<TPixel> frame) => this.frames.Contains(frame); |
|||
|
|||
/// <summary>
|
|||
/// Moves an <seealso cref="ImageFrame{TPixel}"/> from <paramref name="sourceIndex"/> to <paramref name="destinationIndex"/>.
|
|||
/// </summary>
|
|||
/// <param name="sourceIndex">The zero-based index of the frame to move.</param>
|
|||
/// <param name="destinationIndex">The index to move the frame to.</param>
|
|||
public override void MoveFrame(int sourceIndex, int destinationIndex) |
|||
{ |
|||
if (sourceIndex == destinationIndex) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
ImageFrame<TPixel> frameAtIndex = this.frames[sourceIndex]; |
|||
this.frames.RemoveAt(sourceIndex); |
|||
this.frames.Insert(destinationIndex, frameAtIndex); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the frame at the specified index and creates a new image with only the removed frame
|
|||
/// with the same metadata as the original image.
|
|||
/// </summary>
|
|||
/// <param name="index">The zero-based index of the frame to export.</param>
|
|||
/// <exception cref="InvalidOperationException">Cannot remove last frame.</exception>
|
|||
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
|
|||
public new Image<TPixel> ExportFrame(int index) |
|||
{ |
|||
ImageFrame<TPixel> frame = this[index]; |
|||
|
|||
if (this.Count == 1 && this.frames.Contains(frame)) |
|||
{ |
|||
throw new InvalidOperationException("Cannot remove last frame."); |
|||
} |
|||
|
|||
this.frames.Remove(frame); |
|||
|
|||
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { frame }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an <see cref="Image{T}"/> with only the frame at the specified index
|
|||
/// with the same metadata as the original image.
|
|||
/// </summary>
|
|||
/// <param name="index">The zero-based index of the frame to clone.</param>
|
|||
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
|
|||
public new Image<TPixel> CloneFrame(int index) |
|||
{ |
|||
ImageFrame<TPixel> frame = this[index]; |
|||
ImageFrame<TPixel> clonedFrame = frame.Clone(); |
|||
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { clonedFrame }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// The new <see cref="ImageFrame{TPixel}" />.
|
|||
/// </returns>
|
|||
public new ImageFrame<TPixel> CreateFrame() |
|||
{ |
|||
var frame = new ImageFrame<TPixel>( |
|||
this.parent.GetConfiguration(), |
|||
this.RootFrame.Width, |
|||
this.RootFrame.Height); |
|||
this.frames.Add(frame); |
|||
return frame; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override IEnumerator<ImageFrame> NonGenericGetEnumerator() => this.frames.GetEnumerator(); |
|||
|
|||
/// <inheritdoc />
|
|||
protected override ImageFrame NonGenericGetFrame(int index) => this[index]; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override ImageFrame NonGenericInsertFrame(int index, ImageFrame source) |
|||
{ |
|||
Guard.NotNull(source, nameof(source)); |
|||
|
|||
if (source is ImageFrame<TPixel> compatibleSource) |
|||
{ |
|||
return this.InsertFrame(index, compatibleSource); |
|||
} |
|||
|
|||
ImageFrame<TPixel> result = this.CopyNonCompatibleFrame(source); |
|||
this.frames.Insert(index, result); |
|||
return result; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override ImageFrame NonGenericAddFrame(ImageFrame source) |
|||
{ |
|||
Guard.NotNull(source, nameof(source)); |
|||
|
|||
if (source is ImageFrame<TPixel> compatibleSource) |
|||
{ |
|||
return this.AddFrame(compatibleSource); |
|||
} |
|||
|
|||
ImageFrame<TPixel> result = this.CopyNonCompatibleFrame(source); |
|||
this.frames.Add(result); |
|||
return result; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override Image NonGenericExportFrame(int index) => this.ExportFrame(index); |
|||
|
|||
/// <inheritdoc />
|
|||
protected override Image NonGenericCloneFrame(int index) => this.CloneFrame(index); |
|||
|
|||
/// <inheritdoc />
|
|||
protected override ImageFrame NonGenericCreateFrame(Color backgroundColor) => |
|||
this.CreateFrame(backgroundColor.ToPixel<TPixel>()); |
|||
|
|||
/// <inheritdoc />
|
|||
protected override ImageFrame NonGenericCreateFrame() => this.CreateFrame(); |
|||
|
|||
/// <summary>
|
|||
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection.
|
|||
/// </summary>
|
|||
/// <param name="backgroundColor">The background color to initialize the pixels with.</param>
|
|||
/// <returns>
|
|||
/// The new <see cref="ImageFrame{TPixel}" />.
|
|||
/// </returns>
|
|||
public ImageFrame<TPixel> CreateFrame(TPixel backgroundColor) |
|||
{ |
|||
var frame = new ImageFrame<TPixel>( |
|||
this.parent.GetConfiguration(), |
|||
this.RootFrame.Width, |
|||
this.RootFrame.Height, |
|||
backgroundColor); |
|||
this.frames.Add(frame); |
|||
return frame; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
IEnumerator<ImageFrame<TPixel>> IEnumerable<ImageFrame<TPixel>>.GetEnumerator() => this.frames.GetEnumerator(); |
|||
|
|||
/// <inheritdoc/>
|
|||
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).GetEnumerator(); |
|||
|
|||
private void ValidateFrame(ImageFrame<TPixel> frame) |
|||
{ |
|||
Guard.NotNull(frame, nameof(frame)); |
|||
|
|||
if (this.Count != 0) |
|||
{ |
|||
if (this.RootFrame.Width != frame.Width || this.RootFrame.Height != frame.Height) |
|||
{ |
|||
throw new ArgumentException("Frame must have the same dimensions as the image.", nameof(frame)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal void Dispose() |
|||
{ |
|||
foreach (ImageFrame<TPixel> f in this.frames) |
|||
{ |
|||
f.Dispose(); |
|||
} |
|||
|
|||
this.frames.Clear(); |
|||
} |
|||
|
|||
private ImageFrame<TPixel> CopyNonCompatibleFrame(ImageFrame source) |
|||
{ |
|||
ImageFrame<TPixel> result = new ImageFrame<TPixel>( |
|||
this.parent.GetConfiguration(), |
|||
source.Size(), |
|||
source.Metadata.DeepClone()); |
|||
source.CopyPixelsTo(result.PixelBuffer.Span); |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,346 @@ |
|||
// // Copyright (c) Six Labors and contributors.
|
|||
// // Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
public abstract partial class ImageFrameCollectionTests |
|||
{ |
|||
[GroupOutput("ImageFramesCollectionTests")] |
|||
public class Generic : ImageFrameCollectionTests |
|||
{ |
|||
[Fact] |
|||
public void Constructor_ShouldCreateOneFrame() |
|||
{ |
|||
Assert.Equal(1, this.Collection.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddNewFrame_FramesMustHaveSameSize() |
|||
{ |
|||
ArgumentException ex = Assert.Throws<ArgumentException>( |
|||
() => |
|||
{ |
|||
this.Collection.AddFrame(new ImageFrame<Rgba32>(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<ArgumentNullException>( |
|||
() => |
|||
{ |
|||
this.Collection.AddFrame((ImageFrame<Rgba32>)null); |
|||
}); |
|||
|
|||
Assert.StartsWith("Value cannot be null.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddNewFrame_PixelBuffer_DataMustNotBeNull() |
|||
{ |
|||
Rgba32[] data = null; |
|||
|
|||
ArgumentNullException ex = Assert.Throws<ArgumentNullException>( |
|||
() => |
|||
{ |
|||
this.Collection.AddFrame(data); |
|||
}); |
|||
|
|||
Assert.StartsWith("Value cannot be null.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddNewFrame_PixelBuffer_BufferIncorrectSize() |
|||
{ |
|||
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>( |
|||
() => |
|||
{ |
|||
this.Collection.AddFrame(new Rgba32[0]); |
|||
}); |
|||
|
|||
Assert.StartsWith("Value 0 must be greater than or equal to 100.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void InsertNewFrame_FramesMustHaveSameSize() |
|||
{ |
|||
ArgumentException ex = Assert.Throws<ArgumentException>( |
|||
() => |
|||
{ |
|||
this.Collection.InsertFrame(1, new ImageFrame<Rgba32>(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<ArgumentNullException>( |
|||
() => |
|||
{ |
|||
this.Collection.InsertFrame(1, null); |
|||
}); |
|||
|
|||
Assert.StartsWith("Value cannot be null.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Constructor_FramesMustHaveSameSize() |
|||
{ |
|||
ArgumentException ex = Assert.Throws<ArgumentException>( |
|||
() => |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>( |
|||
this.Image, |
|||
new[] |
|||
{ |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10), |
|||
new ImageFrame<Rgba32>(Configuration.Default, 1, 1) |
|||
}); |
|||
}); |
|||
|
|||
Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RemoveAtFrame_ThrowIfRemovingLastFrame() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>( |
|||
this.Image, |
|||
new[] { new ImageFrame<Rgba32>(Configuration.Default, 10, 10) }); |
|||
|
|||
InvalidOperationException ex = Assert.Throws<InvalidOperationException>( |
|||
() => |
|||
{ |
|||
collection.RemoveFrame(0); |
|||
}); |
|||
Assert.Equal("Cannot remove last frame.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>( |
|||
this.Image, |
|||
new[] |
|||
{ |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10), |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10) |
|||
}); |
|||
|
|||
collection.RemoveFrame(0); |
|||
Assert.Equal(1, collection.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RootFrameIsFrameAtIndexZero() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>( |
|||
this.Image, |
|||
new[] |
|||
{ |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10), |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10) |
|||
}); |
|||
|
|||
Assert.Equal(collection.RootFrame, collection[0]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ConstructorPopulatesFrames() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>( |
|||
this.Image, |
|||
new[] |
|||
{ |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10), |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10) |
|||
}); |
|||
|
|||
Assert.Equal(2, collection.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DisposeClearsCollection() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>( |
|||
this.Image, |
|||
new[] |
|||
{ |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10), |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10) |
|||
}); |
|||
|
|||
collection.Dispose(); |
|||
|
|||
Assert.Equal(0, collection.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Dispose_DisposesAllInnerFrames() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>( |
|||
this.Image, |
|||
new[] |
|||
{ |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10), |
|||
new ImageFrame<Rgba32>(Configuration.Default, 10, 10) |
|||
}); |
|||
|
|||
IPixelSource<Rgba32>[] framesSnapShot = collection.OfType<IPixelSource<Rgba32>>().ToArray(); |
|||
collection.Dispose(); |
|||
|
|||
Assert.All( |
|||
framesSnapShot, |
|||
f => |
|||
{ |
|||
// the pixel source of the frame is null after its been disposed.
|
|||
Assert.Null(f.PixelBuffer); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithTestPatternImages(10, 10, PixelTypes.Rgba32)] |
|||
public void CloneFrame<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
img.Frames.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10)); // add a frame anyway
|
|||
using (Image<TPixel> cloned = img.Frames.CloneFrame(0)) |
|||
{ |
|||
Assert.Equal(2, img.Frames.Count); |
|||
cloned.ComparePixelBufferTo(img.GetPixelSpan()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithTestPatternImages(10, 10, PixelTypes.Rgba32)] |
|||
public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
var sourcePixelData = img.GetPixelSpan().ToArray(); |
|||
|
|||
img.Frames.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10)); |
|||
using (Image<TPixel> cloned = img.Frames.ExportFrame(0)) |
|||
{ |
|||
Assert.Equal(1, img.Frames.Count); |
|||
cloned.ComparePixelBufferTo(sourcePixelData); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CreateFrame_Default() |
|||
{ |
|||
this.Image.Frames.CreateFrame(); |
|||
|
|||
Assert.Equal(2, this.Image.Frames.Count); |
|||
this.Image.Frames[1].ComparePixelBufferTo(default(Rgba32)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CreateFrame_CustomFillColor() |
|||
{ |
|||
this.Image.Frames.CreateFrame(Rgba32.HotPink); |
|||
|
|||
Assert.Equal(2, this.Image.Frames.Count); |
|||
this.Image.Frames[1].ComparePixelBufferTo(Rgba32.HotPink); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddFrameFromPixelData() |
|||
{ |
|||
var pixelData = this.Image.Frames.RootFrame.GetPixelSpan().ToArray(); |
|||
this.Image.Frames.AddFrame(pixelData); |
|||
Assert.Equal(2, this.Image.Frames.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddFrame_clones_sourceFrame() |
|||
{ |
|||
var pixelData = this.Image.Frames.RootFrame.GetPixelSpan().ToArray(); |
|||
var otherFRame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); |
|||
var addedFrame = this.Image.Frames.AddFrame(otherFRame); |
|||
addedFrame.ComparePixelBufferTo(otherFRame.GetPixelSpan()); |
|||
Assert.NotEqual(otherFRame, addedFrame); |
|||
} |
|||
|
|||
[Fact] |
|||
public void InsertFrame_clones_sourceFrame() |
|||
{ |
|||
var pixelData = this.Image.Frames.RootFrame.GetPixelSpan().ToArray(); |
|||
var otherFRame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); |
|||
var addedFrame = this.Image.Frames.InsertFrame(0, otherFRame); |
|||
addedFrame.ComparePixelBufferTo(otherFRame.GetPixelSpan()); |
|||
Assert.NotEqual(otherFRame, addedFrame); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MoveFrame_LeavesFrmaeInCorrectLocation() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.Image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = this.Image.Frames[4]; |
|||
this.Image.Frames.MoveFrame(4, 7); |
|||
var newIndex = this.Image.Frames.IndexOf(frame); |
|||
Assert.Equal(7, newIndex); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IndexOf_ReturnsCorrectIndex() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.Image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = this.Image.Frames[4]; |
|||
var index = this.Image.Frames.IndexOf(frame); |
|||
Assert.Equal(4, index); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Contains_TrueIfMember() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.Image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = this.Image.Frames[4]; |
|||
Assert.True(this.Image.Frames.Contains(frame)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Contains_FalseIfNonMember() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.Image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); |
|||
Assert.False(this.Image.Frames.Contains(frame)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,325 @@ |
|||
// // Copyright (c) Six Labors and contributors.
|
|||
// // Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Drawing.Imaging; |
|||
using System.Linq; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Formats.Gif; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
|
|||
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<Bgra32> sourceImage = new Image<Bgra32>( |
|||
this.Image.GetConfiguration(), |
|||
this.Image.Width, |
|||
this.Image.Height, |
|||
(Bgra32)Color.Blue)) |
|||
{ |
|||
this.Collection.AddFrame(sourceImage.Frames.RootFrame); |
|||
} |
|||
|
|||
Rgba32[] expectedAllBlue = |
|||
Enumerable.Repeat(Rgba32.Blue, this.Image.Width * this.Image.Height).ToArray(); |
|||
|
|||
Assert.Equal(2, this.Collection.Count); |
|||
ImageFrame<Rgba32> actualFrame = (ImageFrame<Rgba32>)this.Collection[1]; |
|||
|
|||
actualFrame.ComparePixelBufferTo(expectedAllBlue); |
|||
} |
|||
|
|||
[Fact] |
|||
public void InsertFrame_OfDifferentPixelType() |
|||
{ |
|||
using (Image<Bgra32> sourceImage = new Image<Bgra32>( |
|||
this.Image.GetConfiguration(), |
|||
this.Image.Width, |
|||
this.Image.Height, |
|||
(Bgra32)Color.Blue)) |
|||
{ |
|||
this.Collection.InsertFrame(0, sourceImage.Frames.RootFrame); |
|||
} |
|||
|
|||
Rgba32[] expectedAllBlue = |
|||
Enumerable.Repeat(Rgba32.Blue, this.Image.Width * this.Image.Height).ToArray(); |
|||
|
|||
Assert.Equal(2, this.Collection.Count); |
|||
ImageFrame<Rgba32> actualFrame = (ImageFrame<Rgba32>)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<ArgumentException>( |
|||
() => |
|||
{ |
|||
this.Collection.AddFrame(new ImageFrame<Rgba32>(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<ArgumentNullException>( |
|||
() => |
|||
{ |
|||
this.Collection.AddFrame((ImageFrame<Rgba32>)null); |
|||
}); |
|||
|
|||
Assert.StartsWith("Value cannot be null.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void InsertNewFrame_FramesMustHaveSameSize() |
|||
{ |
|||
ArgumentException ex = Assert.Throws<ArgumentException>( |
|||
() => |
|||
{ |
|||
this.Collection.InsertFrame(1, new ImageFrame<Rgba32>(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<ArgumentNullException>( |
|||
() => |
|||
{ |
|||
this.Collection.InsertFrame(1, null); |
|||
}); |
|||
|
|||
Assert.StartsWith("Value cannot be null.", ex.Message); |
|||
} |
|||
|
|||
|
|||
[Fact] |
|||
public void RemoveAtFrame_ThrowIfRemovingLastFrame() |
|||
{ |
|||
|
|||
InvalidOperationException ex = Assert.Throws<InvalidOperationException>( |
|||
() => |
|||
{ |
|||
this.Collection.RemoveFrame(0); |
|||
}); |
|||
Assert.Equal("Cannot remove last frame.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() |
|||
{ |
|||
this.Collection.AddFrame(new ImageFrame<Rgba32>(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<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
ImageFrameCollection nonGenericFrameCollection = img.Frames; |
|||
|
|||
nonGenericFrameCollection.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10)); // add a frame anyway
|
|||
using (Image cloned = nonGenericFrameCollection.CloneFrame(0)) |
|||
{ |
|||
Assert.Equal(2, img.Frames.Count); |
|||
|
|||
Image<TPixel> expectedClone = (Image<TPixel>)cloned; |
|||
|
|||
expectedClone.ComparePixelBufferTo(img.GetPixelSpan()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithTestPatternImages(10, 10, PixelTypes.Rgba32)] |
|||
public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
var sourcePixelData = img.GetPixelSpan().ToArray(); |
|||
|
|||
ImageFrameCollection nonGenericFrameCollection = img.Frames; |
|||
|
|||
nonGenericFrameCollection.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10)); |
|||
using (Image cloned = nonGenericFrameCollection.ExportFrame(0)) |
|||
{ |
|||
Assert.Equal(1, img.Frames.Count); |
|||
|
|||
Image<TPixel> expectedClone = (Image<TPixel>)cloned; |
|||
expectedClone.ComparePixelBufferTo(sourcePixelData); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CreateFrame_Default() |
|||
{ |
|||
this.Image.Frames.CreateFrame(); |
|||
|
|||
Assert.Equal(2, this.Image.Frames.Count); |
|||
|
|||
ImageFrame<Rgba32> frame = (ImageFrame<Rgba32>)this.Image.Frames[1]; |
|||
|
|||
frame.ComparePixelBufferTo(default(Rgba32)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CreateFrame_CustomFillColor() |
|||
{ |
|||
this.Image.Frames.CreateFrame(Rgba32.HotPink); |
|||
|
|||
Assert.Equal(2, this.Image.Frames.Count); |
|||
|
|||
ImageFrame<Rgba32> frame = (ImageFrame<Rgba32>)this.Image.Frames[1]; |
|||
|
|||
frame.ComparePixelBufferTo(Rgba32.HotPink); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MoveFrame_LeavesFrmaeInCorrectLocation() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.Image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = this.Image.Frames[4]; |
|||
this.Image.Frames.MoveFrame(4, 7); |
|||
var newIndex = this.Image.Frames.IndexOf(frame); |
|||
Assert.Equal(7, newIndex); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IndexOf_ReturnsCorrectIndex() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.Image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = this.Image.Frames[4]; |
|||
var index = this.Image.Frames.IndexOf(frame); |
|||
Assert.Equal(4, index); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Contains_TrueIfMember() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.Image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = this.Image.Frames[4]; |
|||
Assert.True(this.Image.Frames.Contains(frame)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Contains_FalseIfNonMember() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.Image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); |
|||
Assert.False(this.Image.Frames.Contains(frame)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Integration test for end-to end API validation.
|
|||
/// </summary>
|
|||
[Theory] |
|||
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] |
|||
public void ConstructGif_FromDifferentPixelTypes<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image source = provider.GetImage()) |
|||
using (Image<TPixel> dest = new Image<TPixel>(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++) |
|||
{ |
|||
CompareGifMetadata(source.Frames[i], dest.Frames[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void ImportFrameAs<TPixel>(ImageFrameCollection source, ImageFrameCollection destination, int index) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image temp = source.CloneFrame(index)) |
|||
{ |
|||
using (Image<TPixel> temp2 = temp.CloneAs<TPixel>()) |
|||
{ |
|||
destination.AddFrame(temp2.Frames.RootFrame); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void CompareGifMetadata(ImageFrame a, ImageFrame b) |
|||
{ |
|||
// TODO: all metadata classes should be equatable!
|
|||
|
|||
GifFrameMetadata aData = a.Metadata.GetFormatMetadata(GifFormat.Instance); |
|||
GifFrameMetadata bData = b.Metadata.GetFormatMetadata(GifFormat.Instance); |
|||
|
|||
Assert.Equal(aData.DisposalMethod, bData.DisposalMethod); |
|||
Assert.Equal(aData.FrameDelay, bData.FrameDelay); |
|||
Assert.Equal(aData.ColorTableLength, bData.ColorTableLength); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
public abstract partial class ImageFrameCollectionTests : IDisposable |
|||
{ |
|||
protected Image<Rgba32> Image { get; } |
|||
protected ImageFrameCollection<Rgba32> Collection { get; } |
|||
|
|||
public ImageFrameCollectionTests() |
|||
{ |
|||
// Needed to get English exception messages, which are checked in several tests.
|
|||
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); |
|||
|
|||
this.Image = new Image<Rgba32>(10, 10); |
|||
this.Collection = new ImageFrameCollection<Rgba32>(this.Image, 10, 10, default(Rgba32)); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
this.Image.Dispose(); |
|||
this.Collection.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,331 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests |
|||
{ |
|||
public class ImageFramesCollectionTests : IDisposable |
|||
{ |
|||
private Image<Rgba32> image; |
|||
private ImageFrameCollection<Rgba32> collection; |
|||
|
|||
public ImageFramesCollectionTests() |
|||
{ |
|||
// Needed to get English exception messages, which are checked in several tests.
|
|||
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); |
|||
|
|||
this.image = new Image<Rgba32>(10, 10); |
|||
this.collection = new ImageFrameCollection<Rgba32>(this.image, 10, 10, default(Rgba32)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageFramesaLwaysHaveOneFrame() |
|||
{ |
|||
Assert.Equal(1, this.collection.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddNewFrame_FramesMustHaveSameSize() |
|||
{ |
|||
ArgumentException ex = Assert.Throws<ArgumentException>(() => |
|||
{ |
|||
this.collection.AddFrame(new ImageFrame<Rgba32>(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<ArgumentNullException>(() => |
|||
{ |
|||
this.collection.AddFrame((ImageFrame<Rgba32>)null); |
|||
}); |
|||
|
|||
Assert.StartsWith("Value cannot be null.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddNewFrame_PixelBuffer_DataMustNotBeNull() |
|||
{ |
|||
Rgba32[] data = null; |
|||
|
|||
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(() => |
|||
{ |
|||
this.collection.AddFrame(data); |
|||
}); |
|||
|
|||
Assert.StartsWith("Value cannot be null.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddNewFrame_PixelBuffer_BufferIncorrectSize() |
|||
{ |
|||
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => |
|||
{ |
|||
this.collection.AddFrame(new Rgba32[0]); |
|||
}); |
|||
|
|||
Assert.StartsWith("Value 0 must be greater than or equal to 100.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void InsertNewFrame_FramesMustHaveSameSize() |
|||
{ |
|||
ArgumentException ex = Assert.Throws<ArgumentException>(() => |
|||
{ |
|||
this.collection.InsertFrame(1, new ImageFrame<Rgba32>(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<ArgumentNullException>(() => |
|||
{ |
|||
this.collection.InsertFrame(1, null); |
|||
}); |
|||
|
|||
Assert.StartsWith("Value cannot be null.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Constructor_FramesMustHaveSameSize() |
|||
{ |
|||
ArgumentException ex = Assert.Throws<ArgumentException>(() => |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>(this.image, new[] { |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10), |
|||
new ImageFrame<Rgba32>(Configuration.Default,1,1) |
|||
}); |
|||
}); |
|||
|
|||
Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RemoveAtFrame_ThrowIfRemovingLastFrame() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>(this.image, new[] { |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10) |
|||
}); |
|||
|
|||
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => |
|||
{ |
|||
collection.RemoveFrame(0); |
|||
}); |
|||
Assert.Equal("Cannot remove last frame.", ex.Message); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() |
|||
{ |
|||
|
|||
var collection = new ImageFrameCollection<Rgba32>(this.image, new[] { |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10), |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10) |
|||
}); |
|||
|
|||
collection.RemoveFrame(0); |
|||
Assert.Equal(1, collection.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RootFrameIsFrameAtIndexZero() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>(this.image, new[] { |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10), |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10) |
|||
}); |
|||
|
|||
Assert.Equal(collection.RootFrame, collection[0]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ConstructorPopulatesFrames() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>(this.image, new[] { |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10), |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10) |
|||
}); |
|||
|
|||
Assert.Equal(2, collection.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DisposeClearsCollection() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>(this.image, new[] { |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10), |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10) |
|||
}); |
|||
|
|||
collection.Dispose(); |
|||
|
|||
Assert.Equal(0, collection.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Dispose_DisposesAllInnerFrames() |
|||
{ |
|||
var collection = new ImageFrameCollection<Rgba32>(this.image, new[] { |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10), |
|||
new ImageFrame<Rgba32>(Configuration.Default,10,10) |
|||
}); |
|||
|
|||
IPixelSource<Rgba32>[] framesSnapShot = collection.OfType<IPixelSource<Rgba32>>().ToArray(); |
|||
collection.Dispose(); |
|||
|
|||
Assert.All(framesSnapShot, f => |
|||
{ |
|||
// the pixel source of the frame is null after its been disposed.
|
|||
Assert.Null(f.PixelBuffer); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithTestPatternImages(10, 10, PixelTypes.Rgba32)] |
|||
public void CloneFrame<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
img.Frames.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10));// add a frame anyway
|
|||
using (Image<TPixel> cloned = img.Frames.CloneFrame(0)) |
|||
{ |
|||
Assert.Equal(2, img.Frames.Count); |
|||
cloned.ComparePixelBufferTo(img.GetPixelSpan()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithTestPatternImages(10, 10, PixelTypes.Rgba32)] |
|||
public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
var sourcePixelData = img.GetPixelSpan().ToArray(); |
|||
|
|||
img.Frames.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10)); |
|||
using (Image<TPixel> cloned = img.Frames.ExportFrame(0)) |
|||
{ |
|||
Assert.Equal(1, img.Frames.Count); |
|||
cloned.ComparePixelBufferTo(sourcePixelData); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CreateFrame_Default() |
|||
{ |
|||
this.image.Frames.CreateFrame(); |
|||
|
|||
Assert.Equal(2, this.image.Frames.Count); |
|||
this.image.Frames[1].ComparePixelBufferTo(default(Rgba32)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CreateFrame_CustomFillColor() |
|||
{ |
|||
this.image.Frames.CreateFrame(Rgba32.HotPink); |
|||
|
|||
Assert.Equal(2, this.image.Frames.Count); |
|||
this.image.Frames[1].ComparePixelBufferTo(Rgba32.HotPink); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddFrameFromPixelData() |
|||
{ |
|||
var pixelData = this.image.Frames.RootFrame.GetPixelSpan().ToArray(); |
|||
this.image.Frames.AddFrame(pixelData); |
|||
Assert.Equal(2, this.image.Frames.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddFrame_clones_sourceFrame() |
|||
{ |
|||
var pixelData = this.image.Frames.RootFrame.GetPixelSpan().ToArray(); |
|||
var otherFRame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); |
|||
var addedFrame = this.image.Frames.AddFrame(otherFRame); |
|||
addedFrame.ComparePixelBufferTo(otherFRame.GetPixelSpan()); |
|||
Assert.NotEqual(otherFRame, addedFrame); |
|||
} |
|||
|
|||
[Fact] |
|||
public void InsertFrame_clones_sourceFrame() |
|||
{ |
|||
var pixelData = this.image.Frames.RootFrame.GetPixelSpan().ToArray(); |
|||
var otherFRame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); |
|||
var addedFrame = this.image.Frames.InsertFrame(0, otherFRame); |
|||
addedFrame.ComparePixelBufferTo(otherFRame.GetPixelSpan()); |
|||
Assert.NotEqual(otherFRame, addedFrame); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MoveFrame_LeavesFrmaeInCorrectLocation() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = this.image.Frames[4]; |
|||
this.image.Frames.MoveFrame(4, 7); |
|||
var newIndex = this.image.Frames.IndexOf(frame); |
|||
Assert.Equal(7, newIndex); |
|||
} |
|||
|
|||
|
|||
[Fact] |
|||
public void IndexOf_ReturnsCorrectIndex() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = this.image.Frames[4]; |
|||
var index = this.image.Frames.IndexOf(frame); |
|||
Assert.Equal(4, index); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Contains_TrueIfMember() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = this.image.Frames[4]; |
|||
Assert.True(this.image.Frames.Contains(frame)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Contains_TFalseIfNoneMember() |
|||
{ |
|||
for (var i = 0; i < 9; i++) |
|||
{ |
|||
this.image.Frames.CreateFrame(); |
|||
} |
|||
|
|||
var frame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10); |
|||
Assert.False(this.image.Frames.Contains(frame)); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
this.image.Dispose(); |
|||
this.collection.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
// // Copyright (c) Six Labors and contributors.
|
|||
// // Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// // Copyright (c) Six Labors and contributors.
|
|||
// // Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// // Copyright (c) Six Labors and contributors.
|
|||
// // Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats |
|||
{ |
|||
public abstract partial class PixelConverterTests |
|||
{ |
|||
public static class ReferenceImplementations |
|||
{ |
|||
public static Rgba32 MakeRgba32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Rgba32 d = default; |
|||
d.R = r; |
|||
d.G = g; |
|||
d.B = b; |
|||
d.A = a; |
|||
return d; |
|||
} |
|||
|
|||
public static Argb32 MakeArgb32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Argb32 d = default; |
|||
d.R = r; |
|||
d.G = g; |
|||
d.B = b; |
|||
d.A = a; |
|||
return d; |
|||
} |
|||
|
|||
public static Bgra32 MakeBgra32(byte r, byte g, byte b, byte a) |
|||
{ |
|||
Bgra32 d = default; |
|||
d.R = r; |
|||
d.G = g; |
|||
d.B = b; |
|||
d.A = a; |
|||
return d; |
|||
} |
|||
|
|||
internal static void To<TSourcePixel, TDestinationPixel>( |
|||
Configuration configuration, |
|||
ReadOnlySpan<TSourcePixel> sourcePixels, |
|||
Span<TDestinationPixel> destinationPixels) |
|||
where TSourcePixel : struct, IPixel<TSourcePixel> where TDestinationPixel : struct, IPixel<TDestinationPixel> |
|||
{ |
|||
Guard.NotNull(configuration, nameof(configuration)); |
|||
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); |
|||
|
|||
int count = sourcePixels.Length; |
|||
ref TSourcePixel sourceRef = ref MemoryMarshal.GetReference(sourcePixels); |
|||
|
|||
if (typeof(TSourcePixel) == typeof(TDestinationPixel)) |
|||
{ |
|||
Span<TSourcePixel> uniformDest = |
|||
MemoryMarshal.Cast<TDestinationPixel, TSourcePixel>(destinationPixels); |
|||
sourcePixels.CopyTo(uniformDest); |
|||
return; |
|||
} |
|||
|
|||
// Gray8 and Gray16 are special implementations of IPixel in that they do not conform to the
|
|||
// standard RGBA colorspace format and must be converted from RGBA using the special ITU BT709 algorithm.
|
|||
// One of the requirements of FromScaledVector4/ToScaledVector4 is that it unaware of this and
|
|||
// packs/unpacks the pixel without and conversion so we employ custom methods do do this.
|
|||
if (typeof(TDestinationPixel) == typeof(Gray16)) |
|||
{ |
|||
ref Gray16 gray16Ref = ref MemoryMarshal.GetReference( |
|||
MemoryMarshal.Cast<TDestinationPixel, Gray16>(destinationPixels)); |
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); |
|||
ref Gray16 dp = ref Unsafe.Add(ref gray16Ref, i); |
|||
dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (typeof(TDestinationPixel) == typeof(Gray8)) |
|||
{ |
|||
ref Gray8 gray8Ref = ref MemoryMarshal.GetReference( |
|||
MemoryMarshal.Cast<TDestinationPixel, Gray8>(destinationPixels)); |
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); |
|||
ref Gray8 dp = ref Unsafe.Add(ref gray8Ref, i); |
|||
dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
// Normal conversion
|
|||
ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(destinationPixels); |
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); |
|||
ref TDestinationPixel dp = ref Unsafe.Add(ref destRef, i); |
|||
dp.FromScaledVector4(sp.ToScaledVector4()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit.Abstractions; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations |
|||
{ |
|||
public partial class PixelOperationsTests |
|||
{ |
|||
public class Bgra5551OperationsTests : PixelOperationsTests<Bgra5551> |
|||
{ |
|||
public Bgra5551OperationsTests(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue