Browse Source

Introduce non-generic ImageFrameCollection (#941)

* 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 bugs
pull/952/head
Anton Firsov 7 years ago
committed by James Jackson-South
parent
commit
a48de6301b
  1. 2
      .gitattributes
  2. 13
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  3. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
  4. 12
      src/ImageSharp/Image.cs
  5. 10
      src/ImageSharp/ImageFrame.LoadPixelData.cs
  6. 91
      src/ImageSharp/ImageFrame.cs
  7. 252
      src/ImageSharp/ImageFrameCollection.cs
  8. 358
      src/ImageSharp/ImageFrameCollection{TPixel}.cs
  9. 84
      src/ImageSharp/ImageFrame{TPixel}.cs
  10. 10
      src/ImageSharp/ImageSharp.csproj
  11. 27
      src/ImageSharp/Image{TPixel}.cs
  12. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs
  13. 2
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt
  14. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs
  15. 2
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt
  16. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs
  17. 2
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt
  18. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs
  19. 2
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt
  20. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs
  21. 2
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt
  22. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs
  23. 2
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt
  24. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs
  25. 2
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt
  26. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs
  27. 2
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt
  28. 12
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs
  29. 2
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt
  30. 17
      src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude
  31. 16
      src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs
  32. 14
      src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs
  33. 35
      src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs
  34. 91
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  35. 2
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  36. 7
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
  37. 4
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
  38. 7
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  39. 346
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
  40. 325
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
  41. 29
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs
  42. 331
      tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs
  43. 7
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  44. 115
      tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs
  45. 38
      tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs
  46. 20
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra5551OperationsTests.cs
  47. 87
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs
  48. 87
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs
  49. 2
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs
  50. 131
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs

2
.gitattributes

@ -50,7 +50,7 @@
*.svg text eol=lf
*.targets text eol=lf
*.tt text eol=lf
*.ttinclude text eol=lf
*.ttinclude text eol=crlf
*.txt text eol=lf
*.vb text eol=lf
*.yml text eol=lf

13
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -36,6 +36,17 @@ namespace SixLabors.ImageSharp
public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) =>
(ushort)((r * .2126F) + (g * .7152F) + (b * .0722F));
/// <summary>
/// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <returns>The <see cref="ushort"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static ushort Get16BitBT709Luminance(float r, float g, float b) =>
(ushort)((r * .2126F) + (g * .7152F) + (b * .0722F));
/// <summary>
/// Scales a value from a 16 bit <see cref="ushort"/> to it's 8 bit <see cref="byte"/> equivalent.
/// </summary>
@ -379,4 +390,4 @@ namespace SixLabors.ImageSharp
return GetBoundingRectangle(topLeft, bottomRight);
}
}
}
}

4
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// (4) Packing <see cref="Image{TPixel}"/> pixels from the <see cref="Vector4"/> buffer. <br/>
/// These operations are executed in <see cref="NumberOfPostProcessorSteps"/> steps.
/// <see cref="PixelRowsPerStep"/> image rows are converted in one step,
/// which means that size of the allocated memory is limited (does not depend on <see cref="ImageFrame{TPixel}.Height"/>).
/// which means that size of the allocated memory is limited (does not depend on <see cref="ImageFrame.Height"/>).
/// </summary>
internal class JpegImagePostProcessor : IDisposable
{
@ -177,4 +177,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
}
}
}

12
src/ImageSharp/Image.cs

@ -53,6 +53,11 @@ namespace SixLabors.ImageSharp
/// </summary>
protected Configuration Configuration { get; }
/// <summary>
/// Gets the <see cref="ImageFrameCollection"/> implementing the public <see cref="Frames"/> property.
/// </summary>
protected abstract ImageFrameCollection NonGenericFrameCollection { get; }
/// <inheritdoc/>
public PixelTypeInfo PixelType { get; }
@ -65,6 +70,11 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/>
public ImageMetadata Metadata { get; }
/// <summary>
/// Gets the frames of the image as (non-generic) <see cref="ImageFrameCollection"/>.
/// </summary>
public ImageFrameCollection Frames => this.NonGenericFrameCollection;
/// <summary>
/// Gets the pixel buffer.
/// </summary>
@ -137,4 +147,4 @@ namespace SixLabors.ImageSharp
}
}
}
}
}

10
src/ImageSharp/ImageFrame.LoadPixelData.cs

@ -9,9 +9,9 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <content>
/// Adds static methods allowing the creation of new image from raw pixel data.
/// Contains methods for loading raw pixel data.
/// </content>
internal static class ImageFrame
public partial class ImageFrame
{
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static ImageFrame<TPixel> LoadPixelData<TPixel>(Configuration configuration, ReadOnlySpan<byte> data, int width, int height)
internal static ImageFrame<TPixel> LoadPixelData<TPixel>(Configuration configuration, ReadOnlySpan<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(configuration, MemoryMarshal.Cast<byte, TPixel>(data), width, height);
@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static ImageFrame<TPixel> LoadPixelData<TPixel>(Configuration configuration, ReadOnlySpan<TPixel> data, int width, int height)
internal static ImageFrame<TPixel> LoadPixelData<TPixel>(Configuration configuration, ReadOnlySpan<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
int count = width * height;
@ -48,4 +48,4 @@ namespace SixLabors.ImageSharp
return image;
}
}
}
}

91
src/ImageSharp/ImageFrame.cs

@ -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;
}
}
}

252
src/ImageSharp/ImageFrameCollection.cs

@ -4,149 +4,69 @@
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 collection of <see cref="ImageFrame{T}"/> instances that make up an <see cref="Image{T}"/>.
/// Encapsulates a pixel-agnostic collection of <see cref="ImageFrame"/> instances
/// that make up an <see cref="Image"/>.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
public sealed class ImageFrameCollection<TPixel> : IEnumerable<ImageFrame<TPixel>>
where TPixel : struct, IPixel<TPixel>
public abstract class ImageFrameCollection : IEnumerable<ImageFrame>
{
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 int Count => this.frames.Count;
public abstract int Count { get; }
/// <summary>
/// Gets the root frame.
/// </summary>
public ImageFrame<TPixel> RootFrame => this.frames.Count > 0 ? this.frames[0] : null;
public ImageFrame RootFrame => this.NonGenericRootFrame;
/// <summary>
/// Gets the root frame. (Implements <see cref="RootFrame"/>.)
/// </summary>
protected abstract ImageFrame NonGenericRootFrame { get; }
/// <summary>
/// Gets the <see cref="ImageFrame{TPixel}"/> at the specified index.
/// Gets the <see cref="ImageFrame"/> at the specified index.
/// </summary>
/// <value>
/// The <see cref="ImageFrame{TPixel}"/>.
/// The <see cref="ImageFrame"/>.
/// </value>
/// <param name="index">The index.</param>
/// <returns>The <see cref="ImageFrame{TPixel}"/> at the specified index.</returns>
public ImageFrame<TPixel> this[int index] => this.frames[index];
/// <returns>The <see cref="ImageFrame"/> at the specified index.</returns>
public ImageFrame this[int index] => this.NonGenericGetFrame(index);
/// <summary>
/// Determines the index of a specific <paramref name="frame"/> in the <seealso cref="ImageFrameCollection{TPixel}"/>.
/// Determines the index of a specific <paramref name="frame"/> in the <seealso cref="ImageFrameCollection"/>.
/// </summary>
/// <param name="frame">The <seealso cref="ImageFrame{TPixel}"/> to locate in the <seealso cref="ImageFrameCollection{TPixel}"/>.</param>
/// <param name="frame">The <seealso cref="ImageFrame"/> to locate in the <seealso cref="ImageFrameCollection"/>.</param>
/// <returns>The index of item if found in the list; otherwise, -1.</returns>
public int IndexOf(ImageFrame<TPixel> frame) => this.frames.IndexOf(frame);
public abstract int IndexOf(ImageFrame frame);
/// <summary>
/// Clones and inserts the <paramref name="source"/> into the <seealso cref="ImageFrameCollection{TPixel}"/> at the specified <paramref name="index"/>.
/// Clones and inserts the <paramref name="source"/> into the <seealso cref="ImageFrameCollection"/> 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>
/// <param name="source">The <seealso cref="ImageFrame"/> to clone and insert into the <seealso cref="ImageFrameCollection"/>.</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;
}
/// <returns>The cloned <see cref="ImageFrame"/>.</returns>
public ImageFrame InsertFrame(int index, ImageFrame source) => this.NonGenericInsertFrame(index, source);
/// <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(TPixel[] source)
{
Guard.NotNull(source, nameof(source));
var frame = ImageFrame.LoadPixelData(
this.parent.GetConfiguration(),
new ReadOnlySpan<TPixel>(source),
this.RootFrame.Width,
this.RootFrame.Height);
this.frames.Add(frame);
return frame;
}
public ImageFrame AddFrame(ImageFrame source) => this.NonGenericAddFrame(source);
/// <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 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();
}
public abstract void RemoveFrame(int index);
/// <summary>
/// Determines whether the <seealso cref="ImageFrameCollection{TPixel}"/> contains the <paramref name="frame"/>.
@ -155,24 +75,14 @@ namespace SixLabors.ImageSharp
/// <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);
public abstract bool Contains(ImageFrame 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 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);
}
public abstract void MoveFrame(int sourceIndex, int destinationIndex);
/// <summary>
/// Removes the frame at the specified index and creates a new image with only the removed frame
@ -181,19 +91,7 @@ namespace SixLabors.ImageSharp
/// <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 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 });
}
public Image ExportFrame(int index) => this.NonGenericExportFrame(index);
/// <summary>
/// Creates an <see cref="Image{T}"/> with only the frame at the specified index
@ -201,12 +99,7 @@ namespace SixLabors.ImageSharp
/// </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 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 });
}
public Image CloneFrame(int index) => this.NonGenericCloneFrame(index);
/// <summary>
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection.
@ -214,7 +107,7 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The new <see cref="ImageFrame{TPixel}" />.
/// </returns>
public ImageFrame<TPixel> CreateFrame() => this.CreateFrame(default);
public ImageFrame CreateFrame() => this.NonGenericCreateFrame();
/// <summary>
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection.
@ -223,44 +116,67 @@ namespace SixLabors.ImageSharp
/// <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;
}
public ImageFrame CreateFrame(Color backgroundColor) => this.NonGenericCreateFrame(backgroundColor);
/// <inheritdoc/>
IEnumerator<ImageFrame<TPixel>> IEnumerable<ImageFrame<TPixel>>.GetEnumerator() => this.frames.GetEnumerator();
/// <inheritdoc />
public IEnumerator<ImageFrame> GetEnumerator() => this.NonGenericGetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.NonGenericGetEnumerator();
private void ValidateFrame(ImageFrame<TPixel> frame)
{
Guard.NotNull(frame, nameof(frame));
/// <summary>
/// Implements <see cref="GetEnumerator"/>.
/// </summary>
/// <returns>The enumerator.</returns>
protected abstract IEnumerator<ImageFrame> NonGenericGetEnumerator();
/// <summary>
/// Implements the getter of the indexer.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The frame.</returns>
protected abstract ImageFrame NonGenericGetFrame(int index);
/// <summary>
/// Implements <see cref="InsertFrame"/>.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="source">The frame.</param>
/// <returns>The new frame.</returns>
protected abstract ImageFrame NonGenericInsertFrame(int index, ImageFrame source);
/// <summary>
/// Implements <see cref="AddFrame"/>.
/// </summary>
/// <param name="source">The frame.</param>
/// <returns>The new frame.</returns>
protected abstract ImageFrame NonGenericAddFrame(ImageFrame source);
/// <summary>
/// Implements <see cref="ExportFrame"/>.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The new image.</returns>
protected abstract Image NonGenericExportFrame(int index);
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));
}
}
}
/// <summary>
/// Implements <see cref="CloneFrame"/>.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The new image.</returns>
protected abstract Image NonGenericCloneFrame(int index);
internal void Dispose()
{
foreach (ImageFrame<TPixel> f in this.frames)
{
f.Dispose();
}
/// <summary>
/// Implements <see cref="CreateFrame()"/>.
/// </summary>
/// <returns>The new frame.</returns>
protected abstract ImageFrame NonGenericCreateFrame();
this.frames.Clear();
}
/// <summary>
/// Implements <see cref="CreateFrame()"/>.
/// </summary>
/// <param name="backgroundColor">The background color.</param>
/// <returns>The new frame.</returns>
protected abstract ImageFrame NonGenericCreateFrame(Color backgroundColor);
}
}
}

358
src/ImageSharp/ImageFrameCollection{TPixel}.cs

@ -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;
}
}
}

84
src/ImageSharp/ImageFrame{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
@ -15,10 +16,12 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Represents a single frame in a animation.
/// Represents a pixel-specific 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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class ImageFrame<TPixel> : IPixelSource<TPixel>, IDisposable
public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>, IDisposable
where TPixel : struct, IPixel<TPixel>
{
private bool isDisposed;
@ -53,8 +56,12 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The metadata.</param>
internal ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata)
: this(configuration, width, height, default(TPixel), metadata)
: base(configuration, width, height, metadata)
{
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(width, height, AllocationOptions.Clean);
}
/// <summary>
@ -78,15 +85,12 @@ namespace SixLabors.ImageSharp
/// <param name="backgroundColor">The color to clear the image with.</param>
/// <param name="metadata">The metadata.</param>
internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor, ImageFrameMetadata metadata)
: base(configuration, width, height, metadata)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.Configuration = configuration;
this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(width, height);
this.Metadata = metadata ?? new ImageFrameMetadata();
this.Clear(configuration.GetParallelOptions(), backgroundColor);
}
@ -111,16 +115,12 @@ namespace SixLabors.ImageSharp
/// <param name="memorySource">The memory source.</param>
/// <param name="metadata">The metadata.</param>
internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource, ImageFrameMetadata metadata)
: base(configuration, width, height, metadata)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
Guard.NotNull(metadata, nameof(metadata));
this.Configuration = configuration;
this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = new Buffer2D<TPixel>(memorySource, width, height);
this.Metadata = metadata;
}
/// <summary>
@ -129,27 +129,15 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="source">The source.</param>
internal ImageFrame(Configuration configuration, ImageFrame<TPixel> source)
: base(configuration, source.Width, source.Height, source.Metadata.DeepClone())
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(source, nameof(source));
this.Configuration = configuration;
this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(source.PixelBuffer.Width, source.PixelBuffer.Height);
source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan());
this.Metadata = source.Metadata.DeepClone();
}
/// <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 image pixels. Not private as Buffer2D requires an array in its constructor.
/// </summary>
@ -158,21 +146,6 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/>
Buffer2D<TPixel> IPixelSource<TPixel>.PixelBuffer => this.PixelBuffer;
/// <summary>
/// Gets the width.
/// </summary>
public int Width => this.PixelBuffer.Width;
/// <summary>
/// Gets the height.
/// </summary>
public int Height => this.PixelBuffer.Height;
/// <summary>
/// Gets the metadata of the frame.
/// </summary>
public ImageFrameMetadata Metadata { get; }
/// <summary>
/// Gets or sets the pixel at the specified position.
/// </summary>
@ -188,18 +161,6 @@ namespace SixLabors.ImageSharp
set => this.PixelBuffer[x, y] = value;
}
/// <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);
/// <summary>
/// Gets a reference to the pixel at the specified position.
/// </summary>
@ -232,12 +193,13 @@ namespace SixLabors.ImageSharp
Guard.NotNull(pixelSource, nameof(pixelSource));
Buffer2D<TPixel>.SwapOrCopyContent(this.PixelBuffer, pixelSource.PixelBuffer);
this.UpdateSize(this.PixelBuffer.Size());
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
internal void Dispose()
public override void Dispose()
{
if (this.isDisposed)
{
@ -251,6 +213,17 @@ namespace SixLabors.ImageSharp
this.isDisposed = true;
}
internal override void CopyPixelsTo<TDestinationPixel>(Span<TDestinationPixel> destination)
{
if (typeof(TPixel) == typeof(TDestinationPixel))
{
Span<TPixel> dest1 = MemoryMarshal.Cast<TDestinationPixel, TPixel>(destination);
this.PixelBuffer.Span.CopyTo(dest1);
}
PixelOperations<TPixel>.Instance.To(this.Configuration, this.PixelBuffer.Span, destination);
}
/// <inheritdoc/>
public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>({this.Width}x{this.Height})";
@ -325,8 +298,5 @@ namespace SixLabors.ImageSharp
span.Fill(value);
}
}
/// <inheritdoc/>
void IDisposable.Dispose() => this.Dispose();
}
}
}

10
src/ImageSharp/ImageSharp.csproj

@ -5,6 +5,16 @@
<AssemblyName>SixLabors.ImageSharp</AssemblyName>
<AssemblyTitle>SixLabors.ImageSharp</AssemblyTitle>
<Description>A cross-platform library for the processing of image files; written in C#</Description>
<NeutralLanguage>en</NeutralLanguage>
<VersionPrefix Condition="$(packageversion) != ''">$(packageversion)</VersionPrefix>
<VersionPrefix Condition="$(packageversion) == ''">0.0.1</VersionPrefix>
<TargetFrameworks>netcoreapp2.1;netstandard1.3;netstandard2.0;net472</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>SixLabors.ImageSharp</AssemblyName>
<PackageId>SixLabors.ImageSharp</PackageId>
<PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Png Core</PackageTags>
<RootNamespace>SixLabors.ImageSharp</RootNamespace>

27
src/ImageSharp/Image{TPixel}.cs

@ -80,7 +80,12 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The images metadata.</param>
internal Image(Configuration configuration, MemorySource<TPixel> memorySource, int width, int height, ImageMetadata metadata)
internal Image(
Configuration configuration,
MemorySource<TPixel> memorySource,
int width,
int height,
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
{
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource);
@ -95,7 +100,12 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to initialize the pixels with.</param>
/// <param name="metadata">The images metadata.</param>
internal Image(Configuration configuration, int width, int height, TPixel backgroundColor, ImageMetadata metadata)
internal Image(
Configuration configuration,
int width,
int height,
TPixel backgroundColor,
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
{
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);
@ -114,10 +124,13 @@ namespace SixLabors.ImageSharp
this.Frames = new ImageFrameCollection<TPixel>(this, frames);
}
/// <inheritdoc />
protected override ImageFrameCollection NonGenericFrameCollection => this.Frames;
/// <summary>
/// Gets the frames.
/// </summary>
public ImageFrameCollection<TPixel> Frames { get; }
public new ImageFrameCollection<TPixel> Frames { get; }
/// <summary>
/// Gets the root frame.
@ -149,7 +162,8 @@ namespace SixLabors.ImageSharp
/// <returns>Returns a new <see cref="Image{TPixel}"/> with all the same pixel data as the original.</returns>
public Image<TPixel> Clone(Configuration configuration)
{
IEnumerable<ImageFrame<TPixel>> clonedFrames = this.Frames.Select(x => x.Clone(configuration));
IEnumerable<ImageFrame<TPixel>> clonedFrames =
this.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(x => x.Clone(configuration));
return new Image<TPixel>(configuration, this.Metadata.DeepClone(), clonedFrames);
}
@ -161,7 +175,8 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel2}"/>.</returns>
public override Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
{
IEnumerable<ImageFrame<TPixel2>> clonedFrames = this.Frames.Select(x => x.CloneAs<TPixel2>(configuration));
IEnumerable<ImageFrame<TPixel2>> clonedFrames =
this.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel2>>(x => x.CloneAs<TPixel2>(configuration));
return new Image<TPixel2>(configuration, this.Metadata.DeepClone(), clonedFrames);
}
@ -214,4 +229,4 @@ namespace SixLabors.ImageSharp
return rootSize;
}
}
}
}

12
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs

@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <content>
@ -222,6 +223,15 @@ namespace SixLabors.ImageSharp.PixelFormats
dp.FromArgb32(sp);
}
}
/// <inheritdoc />
internal override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<Argb32> destinationPixels)
{
PixelOperations<TSourcePixel>.Instance.ToArgb32(configuration, sourcePixels, destinationPixels);
}
}
}
}
}

2
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt

@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats
<# GenerateAllDefaultConversionMethods("Argb32"); #>
}
}
}
}

12
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs

@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <content>
@ -196,6 +197,15 @@ namespace SixLabors.ImageSharp.PixelFormats
dp.FromBgr24(sp);
}
}
/// <inheritdoc />
internal override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<Bgr24> destinationPixels)
{
PixelOperations<TSourcePixel>.Instance.ToBgr24(configuration, sourcePixels, destinationPixels);
}
}
}
}
}

2
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt

@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats
<# GenerateAllDefaultConversionMethods("Bgr24"); #>
}
}
}
}

12
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs

@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <content>
@ -222,6 +223,15 @@ namespace SixLabors.ImageSharp.PixelFormats
dp.FromBgra32(sp);
}
}
/// <inheritdoc />
internal override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<Bgra32> destinationPixels)
{
PixelOperations<TSourcePixel>.Instance.ToBgra32(configuration, sourcePixels, destinationPixels);
}
}
}
}
}

2
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt

@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats
<# GenerateAllDefaultConversionMethods("Bgra32"); #>
}
}
}
}

12
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs

@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <content>
@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats
dp.FromGray16(sp);
}
}
/// <inheritdoc />
internal override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<Gray16> destinationPixels)
{
PixelOperations<TSourcePixel>.Instance.ToGray16(configuration, sourcePixels, destinationPixels);
}
}
}
}
}

2
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt

@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats
<# GenerateAllDefaultConversionMethods("Gray16"); #>
}
}
}
}

12
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs

@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <content>
@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats
dp.FromGray8(sp);
}
}
/// <inheritdoc />
internal override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<Gray8> destinationPixels)
{
PixelOperations<TSourcePixel>.Instance.ToGray8(configuration, sourcePixels, destinationPixels);
}
}
}
}
}

2
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt

@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats
<# GenerateAllDefaultConversionMethods("Gray8"); #>
}
}
}
}

12
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs

@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <content>
@ -196,6 +197,15 @@ namespace SixLabors.ImageSharp.PixelFormats
dp.FromRgb24(sp);
}
}
/// <inheritdoc />
internal override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<Rgb24> destinationPixels)
{
PixelOperations<TSourcePixel>.Instance.ToRgb24(configuration, sourcePixels, destinationPixels);
}
}
}
}
}

2
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt

@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats
<# GenerateAllDefaultConversionMethods("Rgb24"); #>
}
}
}
}

12
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs

@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <content>
@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats
dp.FromRgb48(sp);
}
}
/// <inheritdoc />
internal override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<Rgb48> destinationPixels)
{
PixelOperations<TSourcePixel>.Instance.ToRgb48(configuration, sourcePixels, destinationPixels);
}
}
}
}
}

2
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt

@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats
<# GenerateAllDefaultConversionMethods("Rgb48"); #>
}
}
}
}

12
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs

@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <content>
@ -211,6 +212,15 @@ namespace SixLabors.ImageSharp.PixelFormats
dp.FromRgba32(sp);
}
}
/// <inheritdoc />
internal override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<Rgba32> destinationPixels)
{
PixelOperations<TSourcePixel>.Instance.ToRgba32(configuration, sourcePixels, destinationPixels);
}
}
}
}
}

2
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt

@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats
<# GenerateAllDefaultConversionMethods("Rgba32"); #>
}
}
}
}

12
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs

@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <content>
@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats
dp.FromRgba64(sp);
}
}
/// <inheritdoc />
internal override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<Rgba64> destinationPixels)
{
PixelOperations<TSourcePixel>.Instance.ToRgba64(configuration, sourcePixels, destinationPixels);
}
}
}
}
}

2
src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt

@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats
<# GenerateAllDefaultConversionMethods("Rgba64"); #>
}
}
}
}

17
src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude

@ -22,6 +22,19 @@ using System.Runtime.InteropServices;
// Types with Rgba32-combatible to/from Vector4 conversion
static readonly string[] Rgba32CompatibleTypes = { "Argb32", "Bgra32", "Rgb24", "Bgr24" };
void GenerateGenericConverterMethods(string pixelType)
{
#>
/// <inheritdoc />
internal override void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<<#=pixelType#>> destinationPixels)
{
PixelOperations<TSourcePixel>.Instance.To<#=pixelType#>(configuration, sourcePixels, destinationPixels);
}
<#+
}
void GenerateDefaultSelfConversionMethods(string pixelType)
{
@ -117,7 +130,7 @@ using System.Runtime.InteropServices;
}
#>
/// <inheritdoc />
internal override void FromVector4(Configuration configuration, Span<Vector4> sourceVectors, Span<<#=pixelType#>> destPixels, PixelConversionModifiers modifiers)
internal override void FromVector4Destructive(Configuration configuration, Span<Vector4> sourceVectors, Span<<#=pixelType#>> destPixels, PixelConversionModifiers modifiers)
{
Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destPixels, modifiers.Remove(<#=removeTheseModifiers#>));
}
@ -156,5 +169,7 @@ using System.Runtime.InteropServices;
{
GenerateDefaultConvertToMethod(pixelType, destPixelType);
}
GenerateGenericConverterMethods(pixelType);
}
#>

16
src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs

@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector);
public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
@ -62,11 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]
public void FromVector4(Vector4 vector)
{
vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max * Average;
this.PackedValue = (ushort)MathF.Round(vector.X + vector.Y + vector.Z);
}
public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector);
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]
@ -176,9 +172,9 @@ namespace SixLabors.ImageSharp.PixelFormats
{
vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max;
this.PackedValue = ImageMaths.Get16BitBT709Luminance(
(ushort)MathF.Round(vector.X),
(ushort)MathF.Round(vector.Y),
(ushort)MathF.Round(vector.Z));
vector.X,
vector.Y,
vector.Z);
}
}
}
}

14
src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs

@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector);
public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
@ -67,15 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]
public void FromVector4(Vector4 vector)
{
vector = Vector4.Max(Min, vector);
vector = Vector4.Min(Max, vector);
float roundedSum = Vector4.Dot(vector, Accumulator);
this.PackedValue = (byte)roundedSum;
}
public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector);
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]
@ -166,4 +158,4 @@ namespace SixLabors.ImageSharp.PixelFormats
this.PackedValue = ImageMaths.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z);
}
}
}
}

35
src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs

@ -3,6 +3,7 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats.Utils;
@ -44,6 +45,38 @@ namespace SixLabors.ImageSharp.PixelFormats
MemoryMarshal.Cast<RgbaVector, Vector4>(sourcePixels).CopyTo(destVectors);
Vector4Converters.ApplyForwardConversionModifiers(destVectors, modifiers);
}
internal override void ToGray8(Configuration configuration, ReadOnlySpan<RgbaVector> sourcePixels, Span<Gray8> destPixels)
{
Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels));
ref Vector4 sourceBaseRef = ref Unsafe.As<RgbaVector, Vector4>(ref MemoryMarshal.GetReference(sourcePixels));
ref Gray8 destBaseRef = ref MemoryMarshal.GetReference(destPixels);
for (int i = 0; i < sourcePixels.Length; i++)
{
ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i);
ref Gray8 dp = ref Unsafe.Add(ref destBaseRef, i);
dp.ConvertFromRgbaScaledVector4(sp);
}
}
internal override void ToGray16(Configuration configuration, ReadOnlySpan<RgbaVector> sourcePixels, Span<Gray16> destPixels)
{
Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels));
ref Vector4 sourceBaseRef = ref Unsafe.As<RgbaVector, Vector4>(ref MemoryMarshal.GetReference(sourcePixels));
ref Gray16 destBaseRef = ref MemoryMarshal.GetReference(destPixels);
for (int i = 0; i < sourcePixels.Length; i++)
{
ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i);
ref Gray16 dp = ref Unsafe.Add(ref destBaseRef, i);
dp.ConvertFromRgbaScaledVector4(sp);
}
}
}
}
}
}

91
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs

@ -7,6 +7,8 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <summary>
@ -89,63 +91,56 @@ namespace SixLabors.ImageSharp.PixelFormats
Span<Vector4> destVectors) =>
this.ToVector4(configuration, sourcePixels, destVectors, PixelConversionModifiers.None);
/// <summary>
/// Converts 'sourceColors.Length' pixels from 'sourceColors' into 'destinationColors'.
/// </summary>
/// <typeparam name="TDestinationPixel">The destination pixel type.</typeparam>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations</param>
/// <param name="sourceColors">The <see cref="Span{T}"/> to the source colors.</param>
/// <param name="destinationColors">The <see cref="Span{T}"/> to the destination colors.</param>
internal virtual void To<TDestinationPixel>(
internal virtual void From<TSourcePixel>(
Configuration configuration,
ReadOnlySpan<TPixel> sourceColors,
Span<TDestinationPixel> destinationColors)
where TDestinationPixel : struct, IPixel<TDestinationPixel>
ReadOnlySpan<TSourcePixel> sourcePixels,
Span<TPixel> destinationPixels)
where TSourcePixel : struct, IPixel<TSourcePixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourceColors, destinationColors, nameof(destinationColors));
int count = sourceColors.Length;
ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourceColors);
// 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))
const int SliceLength = 1024;
int numberOfSlices = sourcePixels.Length / SliceLength;
using (IMemoryOwner<Vector4> tempVectors = configuration.MemoryAllocator.Allocate<Vector4>(SliceLength))
{
ref Gray16 gray16Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<TDestinationPixel, Gray16>(destinationColors));
for (int i = 0; i < count; i++)
Span<Vector4> vectorSpan = tempVectors.GetSpan();
for (int i = 0; i < numberOfSlices; i++)
{
ref TPixel sp = ref Unsafe.Add(ref sourceRef, i);
ref Gray16 dp = ref Unsafe.Add(ref gray16Ref, i);
dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4());
int start = i * SliceLength;
ReadOnlySpan<TSourcePixel> s = sourcePixels.Slice(start, SliceLength);
Span<TPixel> d = destinationPixels.Slice(start, SliceLength);
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, s, vectorSpan);
this.FromVector4Destructive(configuration, vectorSpan, d);
}
return;
}
if (typeof(TDestinationPixel) == typeof(Gray8))
{
ref Gray8 gray8Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<TDestinationPixel, Gray8>(destinationColors));
for (int i = 0; i < count; i++)
int endOfCompleteSlices = numberOfSlices * SliceLength;
int remainder = sourcePixels.Length - endOfCompleteSlices;
if (remainder > 0)
{
ref TPixel sp = ref Unsafe.Add(ref sourceRef, i);
ref Gray8 dp = ref Unsafe.Add(ref gray8Ref, i);
dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4());
ReadOnlySpan<TSourcePixel> s = sourcePixels.Slice(endOfCompleteSlices);
Span<TPixel> d = destinationPixels.Slice(endOfCompleteSlices);
vectorSpan = vectorSpan.Slice(0, remainder);
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, s, vectorSpan);
this.FromVector4Destructive(configuration, vectorSpan, d);
}
return;
}
}
// Normal conversion
ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(destinationColors);
for (int i = 0; i < count; i++)
{
ref TPixel sp = ref Unsafe.Add(ref sourceRef, i);
ref TDestinationPixel dp = ref Unsafe.Add(ref destRef, i);
dp.FromScaledVector4(sp.ToScaledVector4());
}
/// <summary>
/// Converts 'sourcePixels.Length' pixels from 'sourcePixels' into 'destinationPixels'.
/// </summary>
/// <typeparam name="TDestinationPixel">The destination pixel type.</typeparam>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="sourcePixels">The <see cref="Span{T}"/> to the source pixels.</param>
/// <param name="destinationPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
internal virtual void To<TDestinationPixel>(
Configuration configuration,
ReadOnlySpan<TPixel> sourcePixels,
Span<TDestinationPixel> destinationPixels)
where TDestinationPixel : struct, IPixel<TDestinationPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
PixelOperations<TDestinationPixel>.Instance.From(configuration, sourcePixels, destinationPixels);
}
}
}
}

2
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(source.GetConfiguration(), this.TargetDimensions, x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added

7
src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs

@ -36,7 +36,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.Metadata.DeepClone()));
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
source.GetConfiguration(),
this.CropRectangle.Width,
this.CropRectangle.Height,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.Metadata.DeepClone(), frames);

4
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs

@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
source.GetConfiguration(),
this.TargetDimensions.Width,
@ -140,4 +140,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
}
}
}
}

7
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -68,7 +68,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.Width, this.Height, x.Metadata.DeepClone()));
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
source.GetConfiguration(),
this.Width,
this.Height,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(source.GetConfiguration(), source.Metadata.DeepClone(), frames);

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

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

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

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

29
tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs

@ -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();
}
}
}

331
tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs

@ -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();
}
}
}

7
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -2,6 +2,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;net462;net472</TargetFrameworks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<DebugType Condition="$(codecov) != ''">full</DebugType>
<DebugType Condition="$(codecov) == ''">portable</DebugType>
<DebugSymbols>True</DebugSymbols>
<RootNamespace>SixLabors.ImageSharp.Tests</RootNamespace>
<AssemblyName>SixLabors.ImageSharp.Tests</AssemblyName>
<Platforms>AnyCPU;x64;x86</Platforms>
<RootNamespace>SixLabors.ImageSharp.Tests</RootNamespace>

115
tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs

@ -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());
}
}
}
}
}

38
tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs

@ -5,7 +5,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.PixelFormats
{
public abstract class PixelConverterTests
public abstract partial class PixelConverterTests
{
public static readonly TheoryData<byte, byte, byte, byte> RgbaData =
new TheoryData<byte, byte, byte, byte>
@ -122,39 +122,5 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
Assert.Equal(expectedPacked, actualPacked);
}
}
private 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;
}
}
}
}
}

20
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra5551OperationsTests.cs

@ -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)
{
}
}
}
}

87
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -25,90 +26,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
[Fact]
public void IsSpecialImplementation() => Assert.IsType<Gray16.PixelOperations>(PixelOperations<Gray16>.Instance);
[Theory]
[MemberData(nameof(ArraySizesData))]
public void FromGray8Bytes(int count)
{
byte[] source = CreateByteTestData(count);
var expected = new Gray16[count];
for (int i = 0; i < count; i++)
{
expected[i].FromGray8(new Gray8(source[i]));
}
TestOperation(
source,
expected,
(s, d) => Operations.FromGray8Bytes(this.Configuration, s, d.GetSpan(), count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void ToGray8Bytes(int count)
{
Gray16[] source = CreatePixelTestData(count);
byte[] expected = new byte[count];
var gray = default(Gray8);
for (int i = 0; i < count; i++)
{
gray.FromScaledVector4(source[i].ToScaledVector4());
expected[i] = gray.PackedValue;
}
TestOperation(
source,
expected,
(s, d) => Operations.ToGray8Bytes(this.Configuration, s, d.GetSpan(), count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void FromGray16Bytes(int count)
{
byte[] source = CreateByteTestData(count * 2);
Span<byte> sourceSpan = source.AsSpan();
var expected = new Gray16[count];
for (int i = 0; i < count; i++)
{
int i2 = i * 2;
expected[i].FromGray16(MemoryMarshal.Cast<byte, Gray16>(sourceSpan.Slice(i2, 2))[0]);
}
TestOperation(
source,
expected,
(s, d) => Operations.FromGray16Bytes(this.Configuration, s, d.GetSpan(), count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void ToGray16Bytes(int count)
{
Gray16[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 2];
Gray16 gray = default;
for (int i = 0; i < count; i++)
{
int i2 = i * 2;
gray.FromScaledVector4(source[i].ToScaledVector4());
OctetBytes bytes = Unsafe.As<Gray16, OctetBytes>(ref gray);
expected[i2] = bytes[0];
expected[i2 + 1] = bytes[1];
}
TestOperation(
source,
expected,
(s, d) => Operations.ToGray16Bytes(this.Configuration, s, d.GetSpan(), count)
);
}
}
}
}
}

87
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs

@ -24,91 +24,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
[Fact]
public void IsSpecialImplementation() => Assert.IsType<Gray8.PixelOperations>(PixelOperations<Gray8>.Instance);
[Theory]
[MemberData(nameof(ArraySizesData))]
public void FromGray8Bytes(int count)
{
byte[] source = CreateByteTestData(count);
var expected = new Gray8[count];
for (int i = 0; i < count; i++)
{
expected[i].FromGray8(new Gray8(source[i]));
}
TestOperation(
source,
expected,
(s, d) => Operations.FromGray8Bytes(this.Configuration, s, d.GetSpan(), count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void ToGray8Bytes(int count)
{
Gray8[] source = CreatePixelTestData(count);
byte[] expected = new byte[count];
var gray = default(Gray8);
for (int i = 0; i < count; i++)
{
gray.FromScaledVector4(source[i].ToScaledVector4());
expected[i] = gray.PackedValue;
}
TestOperation(
source,
expected,
(s, d) => Operations.ToGray8Bytes(this.Configuration, s, d.GetSpan(), count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void FromGray16Bytes(int count)
{
byte[] source = CreateByteTestData(count * 2);
Span<byte> sourceSpan = source.AsSpan();
var expected = new Gray8[count];
for (int i = 0; i < count; i++)
{
int i2 = i * 2;
expected[i].FromGray16(MemoryMarshal.Cast<byte, Gray16>(sourceSpan.Slice(i2, 2))[0]);
}
TestOperation(
source,
expected,
(s, d) => Operations.FromGray16Bytes(this.Configuration, s, d.GetSpan(), count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void ToGray16Bytes(int count)
{
Gray8[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 2];
Gray16 gray = default;
for (int i = 0; i < count; i++)
{
int i2 = i * 2;
gray.FromScaledVector4(source[i].ToScaledVector4());
OctetBytes bytes = Unsafe.As<Gray16, OctetBytes>(ref gray);
expected[i2] = bytes[0];
expected[i2 + 1] = bytes[1];
}
TestOperation(
source,
expected,
(s, d) => Operations.ToGray16Bytes(this.Configuration, s, d.GetSpan(), count)
);
}
}
}
}
}

2
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs

@ -21,4 +21,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
public void IsSpecialImplementation() => Assert.IsType<Rgb24.PixelOperations>(PixelOperations<Rgb24>.Instance);
}
}
}
}

131
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs

@ -3,12 +3,14 @@
using System;
using System.Buffers;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorSpaces.Companding;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using Xunit;
using Xunit.Abstractions;
@ -245,7 +247,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
Vector4Utils.UnPremultiply(ref v);
}
SRgbCompanding.Compress(ref v);
}
@ -277,6 +279,33 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
);
}
public static readonly TheoryData<IPixel> Generic_To_Data = new TheoryData<IPixel>
{
default(Rgba32),
default(Bgra32),
default(Rgb24),
default(Gray8),
default(Gray16),
default(Rgb48),
default(Rgba64)
};
[Theory]
[MemberData(nameof(Generic_To_Data))]
public void Generic_To<TDestPixel>(TDestPixel dummy)
where TDestPixel : struct, IPixel<TDestPixel>
{
const int Count = 2134;
TPixel[] source = CreatePixelTestData(Count);
TDestPixel[] expected = new TDestPixel[Count];
PixelConverterTests.ReferenceImplementations.To<TPixel, TDestPixel>(this.Configuration, source, expected);
TestOperation(source, expected, (s, d) => Operations.To(this.Configuration, (ReadOnlySpan<TPixel>)s, d.GetSpan()));
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void ToScaledVector4(int count)
@ -732,6 +761,92 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void FromGray8(int count)
{
byte[] sourceBytes = CreateByteTestData(count);
Gray8[] source = sourceBytes.Select(b => new Gray8(b)).ToArray();
var expected = new TPixel[count];
for (int i = 0; i < count; i++)
{
expected[i].FromGray8(source[i]);
}
TestOperation(
source,
expected,
(s, d) => Operations.FromGray8(this.Configuration, s, d.GetSpan())
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void ToGray8(int count)
{
TPixel[] source = CreatePixelTestData(count);
Gray8[] expected = new Gray8[count];
for (int i = 0; i < count; i++)
{
expected[i].FromScaledVector4(source[i].ToScaledVector4());
}
TestOperation(
source,
expected,
(s, d) => Operations.ToGray8(this.Configuration, s, d.GetSpan())
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void FromGray16(int count)
{
Gray16[] source = CreateVector4TestData(count).Select(v =>
{
Gray16 g = default;
g.FromVector4(v);
return g;
}).ToArray();
var expected = new TPixel[count];
for (int i = 0; i < count; i++)
{
int i2 = i * 2;
expected[i].FromGray16(source[i]);
}
TestOperation(
source,
expected,
(s, d) => Operations.FromGray16(this.Configuration, s, d.GetSpan())
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void ToGray16(int count)
{
TPixel[] source = CreatePixelTestData(count);
Gray16[] expected = new Gray16[count];
for (int i = 0; i < count; i++)
{
expected[i].FromScaledVector4(source[i].ToScaledVector4());
}
TestOperation(
source,
expected,
(s, d) => Operations.ToGray16(this.Configuration, s, d.GetSpan())
);
}
public delegate void RefAction<T1>(ref T1 arg1);
internal static Vector4[] CreateExpectedVector4Data(TPixel[] source, RefAction<Vector4> vectorModifier = null)
@ -903,6 +1018,18 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
// ReSharper restore PossibleNullReferenceException
}
}
else if (typeof(TDest) == typeof(Gray16))
{
// Minor difference is tolerated for 16 bit pixel values
Span<Gray16> expected = MemoryMarshal.Cast<TDest, Gray16>(this.ExpectedDestBuffer.AsSpan());
Span<Gray16> actual = MemoryMarshal.Cast<TDest, Gray16>(this.ActualDestBuffer.GetSpan());
for (int i = 0; i < count; i++)
{
int difference = expected[i].PackedValue - actual[i].PackedValue;
Assert.True(Math.Abs(difference) < 2);
}
}
else
{
Span<TDest> expected = this.ExpectedDestBuffer.AsSpan();
@ -915,4 +1042,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
}
}
}
}
}

Loading…
Cancel
Save