From a48de6301b4e5a410e7b6fbd3512fe3c38ee2fc1 Mon Sep 17 00:00:00 2001 From: Anton Firsov Date: Mon, 1 Jul 2019 08:31:52 +0200 Subject: [PATCH] Introduce non-generic ImageFrameCollection (#941) * temporarily disable target frameworks * drop DelegateProcessor * drop IImageProcessingContext * drop NamedColors * drop ColorBuilder * 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() * experimental implementation * fix .ttinclude * generate generic From(...) * 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 --- .gitattributes | 2 +- src/ImageSharp/Common/Helpers/ImageMaths.cs | 13 +- .../Decoder/JpegImagePostProcessor.cs | 4 +- src/ImageSharp/Image.cs | 12 +- src/ImageSharp/ImageFrame.LoadPixelData.cs | 10 +- src/ImageSharp/ImageFrame.cs | 91 +++++ src/ImageSharp/ImageFrameCollection.cs | 252 ++++-------- .../ImageFrameCollection{TPixel}.cs | 358 ++++++++++++++++++ src/ImageSharp/ImageFrame{TPixel}.cs | 84 ++-- src/ImageSharp/ImageSharp.csproj | 10 + src/ImageSharp/Image{TPixel}.cs | 27 +- .../Argb32.PixelOperations.Generated.cs | 12 +- .../Argb32.PixelOperations.Generated.tt | 2 +- .../Bgr24.PixelOperations.Generated.cs | 12 +- .../Bgr24.PixelOperations.Generated.tt | 2 +- .../Bgra32.PixelOperations.Generated.cs | 12 +- .../Bgra32.PixelOperations.Generated.tt | 2 +- .../Gray16.PixelOperations.Generated.cs | 12 +- .../Gray16.PixelOperations.Generated.tt | 2 +- .../Gray8.PixelOperations.Generated.cs | 12 +- .../Gray8.PixelOperations.Generated.tt | 2 +- .../Rgb24.PixelOperations.Generated.cs | 12 +- .../Rgb24.PixelOperations.Generated.tt | 2 +- .../Rgb48.PixelOperations.Generated.cs | 12 +- .../Rgb48.PixelOperations.Generated.tt | 2 +- .../Rgba32.PixelOperations.Generated.cs | 12 +- .../Rgba32.PixelOperations.Generated.tt | 2 +- .../Rgba64.PixelOperations.Generated.cs | 12 +- .../Rgba64.PixelOperations.Generated.tt | 2 +- .../Generated/_Common.ttinclude | 17 +- .../PixelImplementations/Gray16.cs | 16 +- .../PixelImplementations/Gray8.cs | 14 +- .../RgbaVector.PixelOperations.cs | 35 +- .../PixelFormats/PixelOperations{TPixel}.cs | 91 +++-- .../AffineTransformProcessor{TPixel}.cs | 2 +- .../Transforms/CropProcessor{TPixel}.cs | 7 +- .../ProjectiveTransformProcessor{TPixel}.cs | 4 +- .../Resize/ResizeProcessor{TPixel}.cs | 7 +- .../ImageFrameCollectionTests.Generic.cs | 346 +++++++++++++++++ .../ImageFrameCollectionTests.NonGeneric.cs | 325 ++++++++++++++++ .../Image/ImageFrameCollectionTests.cs | 29 ++ .../Image/ImageFramesCollectionTests.cs | 331 ---------------- .../ImageSharp.Tests/ImageSharp.Tests.csproj | 7 + ...ConverterTests.ReferenceImplementations.cs | 115 ++++++ .../PixelFormats/PixelConverterTests.cs | 38 +- ...OperationsTests.Bgra5551OperationsTests.cs | 20 + ...elOperationsTests.Gray16OperationsTests.cs | 87 +---- ...xelOperationsTests.Gray8OperationsTests.cs | 87 +---- ...xelOperationsTests.Rgb24OperationsTests.cs | 2 +- .../PixelOperations/PixelOperationsTests.cs | 131 ++++++- 50 files changed, 1824 insertions(+), 876 deletions(-) create mode 100644 src/ImageSharp/ImageFrame.cs create mode 100644 src/ImageSharp/ImageFrameCollection{TPixel}.cs create mode 100644 tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs create mode 100644 tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs create mode 100644 tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs delete mode 100644 tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs create mode 100644 tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs create mode 100644 tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra5551OperationsTests.cs diff --git a/.gitattributes b/.gitattributes index adef3cc9e..f39b679c5 100644 --- a/.gitattributes +++ b/.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 diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 64bcb11c9..7460c9cac 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/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)); + /// + /// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static ushort Get16BitBT709Luminance(float r, float g, float b) => + (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F)); + /// /// Scales a value from a 16 bit to it's 8 bit equivalent. /// @@ -379,4 +390,4 @@ namespace SixLabors.ImageSharp return GetBoundingRectangle(topLeft, bottomRight); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index 2c8190884..f3f2952b1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// (4) Packing pixels from the buffer.
/// These operations are executed in steps. /// image rows are converted in one step, - /// which means that size of the allocated memory is limited (does not depend on ). + /// which means that size of the allocated memory is limited (does not depend on ). /// internal class JpegImagePostProcessor : IDisposable { @@ -177,4 +177,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 3f3d6a62f..86fef715d 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -53,6 +53,11 @@ namespace SixLabors.ImageSharp /// protected Configuration Configuration { get; } + /// + /// Gets the implementing the public property. + /// + protected abstract ImageFrameCollection NonGenericFrameCollection { get; } + /// public PixelTypeInfo PixelType { get; } @@ -65,6 +70,11 @@ namespace SixLabors.ImageSharp /// public ImageMetadata Metadata { get; } + /// + /// Gets the frames of the image as (non-generic) . + /// + public ImageFrameCollection Frames => this.NonGenericFrameCollection; + /// /// Gets the pixel buffer. /// @@ -137,4 +147,4 @@ namespace SixLabors.ImageSharp } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs index 33dbe31df..9e90aeaf5 100644 --- a/src/ImageSharp/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs @@ -9,9 +9,9 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { /// - /// Adds static methods allowing the creation of new image from raw pixel data. + /// Contains methods for loading raw pixel data. /// - internal static class ImageFrame + public partial class ImageFrame { /// /// Create a new instance of the class from the given byte array in format. @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp /// The height of the final image. /// The pixel format. /// A new . - public static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) where TPixel : struct, IPixel => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp /// The height of the final image. /// The pixel format. /// A new . - public static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) where TPixel : struct, IPixel { int count = width * height; @@ -48,4 +48,4 @@ namespace SixLabors.ImageSharp return image; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs new file mode 100644 index 000000000..f3fe1ed8d --- /dev/null +++ b/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 +{ + /// + /// Represents a pixel-agnostic image frame containing all pixel data and . + /// 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. + /// + public abstract partial class ImageFrame : IDisposable + { + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The width. + /// The height. + /// The . + 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; + } + + /// + /// Gets the to use for buffer allocations. + /// + public MemoryAllocator MemoryAllocator { get; } + + /// + /// Gets the instance associated with this . + /// + internal Configuration Configuration { get; } + + /// + /// Gets the width. + /// + public int Width { get; private set; } + + /// + /// Gets the height. + /// + public int Height { get; private set; } + + /// + /// Gets the metadata of the frame. + /// + public ImageFrameMetadata Metadata { get; } + + /// + /// Gets the size of the frame. + /// + /// The + public Size Size() => new Size(this.Width, this.Height); + + /// + /// Gets the bounds of the frame. + /// + /// The + public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); + + /// + public abstract void Dispose(); + + internal abstract void CopyPixelsTo(Span destination) + where TDestinationPixel : struct, IPixel; + + /// + /// Updates the size of the image frame. + /// + internal void UpdateSize(Size size) + { + this.Width = size.Width; + this.Height = size.Height; + } + } +} diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index 7e516434c..c5bd02c79 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/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 { /// - /// Encapsulates a collection of instances that make up an . + /// Encapsulates a pixel-agnostic collection of instances + /// that make up an . /// - /// The type of the pixel. - public sealed class ImageFrameCollection : IEnumerable> - where TPixel : struct, IPixel + public abstract class ImageFrameCollection : IEnumerable { - private readonly IList> frames = new List>(); - private readonly Image parent; - - internal ImageFrameCollection(Image 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(parent.GetConfiguration(), width, height, backgroundColor)); - } - - internal ImageFrameCollection(Image parent, int width, int height, MemorySource memorySource) - { - this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); - - // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, memorySource)); - } - - internal ImageFrameCollection(Image parent, IEnumerable> frames) - { - Guard.NotNull(parent, nameof(parent)); - Guard.NotNull(frames, nameof(frames)); - - this.parent = parent; - - // Frames are already cloned by the caller - foreach (ImageFrame 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)); - } - } - /// /// Gets the number of frames. /// - public int Count => this.frames.Count; + public abstract int Count { get; } /// /// Gets the root frame. /// - public ImageFrame RootFrame => this.frames.Count > 0 ? this.frames[0] : null; + public ImageFrame RootFrame => this.NonGenericRootFrame; + + /// + /// Gets the root frame. (Implements .) + /// + protected abstract ImageFrame NonGenericRootFrame { get; } /// - /// Gets the at the specified index. + /// Gets the at the specified index. /// /// - /// The . + /// The . /// /// The index. - /// The at the specified index. - public ImageFrame this[int index] => this.frames[index]; + /// The at the specified index. + public ImageFrame this[int index] => this.NonGenericGetFrame(index); /// - /// Determines the index of a specific in the . + /// Determines the index of a specific in the . /// - /// The to locate in the . + /// The to locate in the . /// The index of item if found in the list; otherwise, -1. - public int IndexOf(ImageFrame frame) => this.frames.IndexOf(frame); + public abstract int IndexOf(ImageFrame frame); /// - /// Clones and inserts the into the at the specified . + /// Clones and inserts the into the at the specified . /// /// The zero-based index to insert the frame at. - /// The to clone and insert into the . + /// The to clone and insert into the . /// Frame must have the same dimensions as the image. - /// The cloned . - public ImageFrame InsertFrame(int index, ImageFrame source) - { - this.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); - this.frames.Insert(index, clonedFrame); - return clonedFrame; - } + /// The cloned . + public ImageFrame InsertFrame(int index, ImageFrame source) => this.NonGenericInsertFrame(index, source); /// /// Clones the frame and appends the clone to the end of the collection. /// /// The raw pixel data to generate the from. /// The cloned . - public ImageFrame AddFrame(ImageFrame source) - { - this.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); - this.frames.Add(clonedFrame); - return clonedFrame; - } - - /// - /// 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. - /// - /// The raw pixel data to generate the from. - /// The new . - public ImageFrame AddFrame(TPixel[] source) - { - Guard.NotNull(source, nameof(source)); - - var frame = ImageFrame.LoadPixelData( - this.parent.GetConfiguration(), - new ReadOnlySpan(source), - this.RootFrame.Width, - this.RootFrame.Height); - this.frames.Add(frame); - return frame; - } + public ImageFrame AddFrame(ImageFrame source) => this.NonGenericAddFrame(source); /// /// Removes the frame at the specified index and frees all freeable resources associated with it. /// /// The zero-based index of the frame to remove. /// Cannot remove last frame. - public void RemoveFrame(int index) - { - if (index == 0 && this.Count == 1) - { - throw new InvalidOperationException("Cannot remove last frame."); - } - - ImageFrame frame = this.frames[index]; - this.frames.RemoveAt(index); - frame.Dispose(); - } + public abstract void RemoveFrame(int index); /// /// Determines whether the contains the . @@ -155,24 +75,14 @@ namespace SixLabors.ImageSharp /// /// true if the contains the specified frame; otherwise, false. /// - public bool Contains(ImageFrame frame) => this.frames.Contains(frame); + public abstract bool Contains(ImageFrame frame); /// /// Moves an from to . /// /// The zero-based index of the frame to move. /// The index to move the frame to. - public void MoveFrame(int sourceIndex, int destinationIndex) - { - if (sourceIndex == destinationIndex) - { - return; - } - - ImageFrame frameAtIndex = this.frames[sourceIndex]; - this.frames.RemoveAt(sourceIndex); - this.frames.Insert(destinationIndex, frameAtIndex); - } + public abstract void MoveFrame(int sourceIndex, int destinationIndex); /// /// Removes the frame at the specified index and creates a new image with only the removed frame @@ -181,19 +91,7 @@ namespace SixLabors.ImageSharp /// The zero-based index of the frame to export. /// Cannot remove last frame. /// The new with the specified frame. - public Image ExportFrame(int index) - { - ImageFrame 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(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { frame }); - } + public Image ExportFrame(int index) => this.NonGenericExportFrame(index); /// /// Creates an with only the frame at the specified index @@ -201,12 +99,7 @@ namespace SixLabors.ImageSharp /// /// The zero-based index of the frame to clone. /// The new with the specified frame. - public Image CloneFrame(int index) - { - ImageFrame frame = this[index]; - ImageFrame clonedFrame = frame.Clone(); - return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { clonedFrame }); - } + public Image CloneFrame(int index) => this.NonGenericCloneFrame(index); /// /// Creates a new and appends it to the end of the collection. @@ -214,7 +107,7 @@ namespace SixLabors.ImageSharp /// /// The new . /// - public ImageFrame CreateFrame() => this.CreateFrame(default); + public ImageFrame CreateFrame() => this.NonGenericCreateFrame(); /// /// Creates a new and appends it to the end of the collection. @@ -223,44 +116,67 @@ namespace SixLabors.ImageSharp /// /// The new . /// - public ImageFrame CreateFrame(TPixel backgroundColor) - { - var frame = new ImageFrame( - this.parent.GetConfiguration(), - this.RootFrame.Width, - this.RootFrame.Height, - backgroundColor); - this.frames.Add(frame); - return frame; - } + public ImageFrame CreateFrame(Color backgroundColor) => this.NonGenericCreateFrame(backgroundColor); - /// - IEnumerator> IEnumerable>.GetEnumerator() => this.frames.GetEnumerator(); + /// + public IEnumerator GetEnumerator() => this.NonGenericGetEnumerator(); /// - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.NonGenericGetEnumerator(); - private void ValidateFrame(ImageFrame frame) - { - Guard.NotNull(frame, nameof(frame)); + /// + /// Implements . + /// + /// The enumerator. + protected abstract IEnumerator NonGenericGetEnumerator(); + + /// + /// Implements the getter of the indexer. + /// + /// The index. + /// The frame. + protected abstract ImageFrame NonGenericGetFrame(int index); + + /// + /// Implements . + /// + /// The index. + /// The frame. + /// The new frame. + protected abstract ImageFrame NonGenericInsertFrame(int index, ImageFrame source); + + /// + /// Implements . + /// + /// The frame. + /// The new frame. + protected abstract ImageFrame NonGenericAddFrame(ImageFrame source); + + /// + /// Implements . + /// + /// The index. + /// The new image. + 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)); - } - } - } + /// + /// Implements . + /// + /// The index. + /// The new image. + protected abstract Image NonGenericCloneFrame(int index); - internal void Dispose() - { - foreach (ImageFrame f in this.frames) - { - f.Dispose(); - } + /// + /// Implements . + /// + /// The new frame. + protected abstract ImageFrame NonGenericCreateFrame(); - this.frames.Clear(); - } + /// + /// Implements . + /// + /// The background color. + /// The new frame. + protected abstract ImageFrame NonGenericCreateFrame(Color backgroundColor); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs new file mode 100644 index 000000000..ffd7225bd --- /dev/null +++ b/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 +{ + /// + /// Encapsulates a pixel-specific collection of instances + /// that make up an . + /// + /// The type of the pixel. + public sealed class ImageFrameCollection : ImageFrameCollection, IEnumerable> + where TPixel : struct, IPixel + { + private readonly IList> frames = new List>(); + private readonly Image parent; + + internal ImageFrameCollection(Image 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(parent.GetConfiguration(), width, height, backgroundColor)); + } + + internal ImageFrameCollection(Image parent, int width, int height, MemorySource memorySource) + { + this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); + + // Frames are already cloned within the caller + this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, memorySource)); + } + + internal ImageFrameCollection(Image parent, IEnumerable> frames) + { + Guard.NotNull(parent, nameof(parent)); + Guard.NotNull(frames, nameof(frames)); + + this.parent = parent; + + // Frames are already cloned by the caller + foreach (ImageFrame 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)); + } + } + + /// + /// Gets the number of frames. + /// + public override int Count => this.frames.Count; + + /// + /// Gets the root frame. + /// + public new ImageFrame RootFrame => this.frames.Count > 0 ? this.frames[0] : null; + + /// + protected override ImageFrame NonGenericRootFrame => this.RootFrame; + + /// + /// Gets the at the specified index. + /// + /// + /// The . + /// + /// The index. + /// The at the specified index. + public new ImageFrame this[int index] => this.frames[index]; + + /// + public override int IndexOf(ImageFrame frame) + { + return frame is ImageFrame specific ? this.IndexOf(specific) : -1; + } + + /// + /// Determines the index of a specific in the . + /// + /// The to locate in the . + /// The index of item if found in the list; otherwise, -1. + public int IndexOf(ImageFrame frame) => this.frames.IndexOf(frame); + + /// + /// Clones and inserts the into the at the specified . + /// + /// The zero-based index to insert the frame at. + /// The to clone and insert into the . + /// Frame must have the same dimensions as the image. + /// The cloned . + public ImageFrame InsertFrame(int index, ImageFrame source) + { + this.ValidateFrame(source); + ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); + this.frames.Insert(index, clonedFrame); + return clonedFrame; + } + + /// + /// Clones the frame and appends the clone to the end of the collection. + /// + /// The raw pixel data to generate the from. + /// The cloned . + public ImageFrame AddFrame(ImageFrame source) + { + this.ValidateFrame(source); + ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); + this.frames.Add(clonedFrame); + return clonedFrame; + } + + /// + /// 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. + /// + /// The raw pixel data to generate the from. + /// The new . + public ImageFrame AddFrame(ReadOnlySpan source) + { + var frame = ImageFrame.LoadPixelData( + this.parent.GetConfiguration(), + source, + this.RootFrame.Width, + this.RootFrame.Height); + this.frames.Add(frame); + return frame; + } + + /// + /// 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. + /// + /// The raw pixel data to generate the from. + /// The new . + public ImageFrame AddFrame(TPixel[] source) + { + Guard.NotNull(source, nameof(source)); + return this.AddFrame(source.AsSpan()); + } + + /// + /// Removes the frame at the specified index and frees all freeable resources associated with it. + /// + /// The zero-based index of the frame to remove. + /// Cannot remove last frame. + public override void RemoveFrame(int index) + { + if (index == 0 && this.Count == 1) + { + throw new InvalidOperationException("Cannot remove last frame."); + } + + ImageFrame frame = this.frames[index]; + this.frames.RemoveAt(index); + frame.Dispose(); + } + + /// + public override bool Contains(ImageFrame frame) => + frame is ImageFrame specific && this.Contains(specific); + + /// + /// Determines whether the contains the . + /// + /// The frame. + /// + /// true if the contains the specified frame; otherwise, false. + /// + public bool Contains(ImageFrame frame) => this.frames.Contains(frame); + + /// + /// Moves an from to . + /// + /// The zero-based index of the frame to move. + /// The index to move the frame to. + public override void MoveFrame(int sourceIndex, int destinationIndex) + { + if (sourceIndex == destinationIndex) + { + return; + } + + ImageFrame frameAtIndex = this.frames[sourceIndex]; + this.frames.RemoveAt(sourceIndex); + this.frames.Insert(destinationIndex, frameAtIndex); + } + + /// + /// 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. + /// + /// The zero-based index of the frame to export. + /// Cannot remove last frame. + /// The new with the specified frame. + public new Image ExportFrame(int index) + { + ImageFrame 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(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { frame }); + } + + /// + /// Creates an with only the frame at the specified index + /// with the same metadata as the original image. + /// + /// The zero-based index of the frame to clone. + /// The new with the specified frame. + public new Image CloneFrame(int index) + { + ImageFrame frame = this[index]; + ImageFrame clonedFrame = frame.Clone(); + return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { clonedFrame }); + } + + /// + /// Creates a new and appends it to the end of the collection. + /// + /// + /// The new . + /// + public new ImageFrame CreateFrame() + { + var frame = new ImageFrame( + this.parent.GetConfiguration(), + this.RootFrame.Width, + this.RootFrame.Height); + this.frames.Add(frame); + return frame; + } + + /// + protected override IEnumerator NonGenericGetEnumerator() => this.frames.GetEnumerator(); + + /// + protected override ImageFrame NonGenericGetFrame(int index) => this[index]; + + /// + protected override ImageFrame NonGenericInsertFrame(int index, ImageFrame source) + { + Guard.NotNull(source, nameof(source)); + + if (source is ImageFrame compatibleSource) + { + return this.InsertFrame(index, compatibleSource); + } + + ImageFrame result = this.CopyNonCompatibleFrame(source); + this.frames.Insert(index, result); + return result; + } + + /// + protected override ImageFrame NonGenericAddFrame(ImageFrame source) + { + Guard.NotNull(source, nameof(source)); + + if (source is ImageFrame compatibleSource) + { + return this.AddFrame(compatibleSource); + } + + ImageFrame result = this.CopyNonCompatibleFrame(source); + this.frames.Add(result); + return result; + } + + /// + protected override Image NonGenericExportFrame(int index) => this.ExportFrame(index); + + /// + protected override Image NonGenericCloneFrame(int index) => this.CloneFrame(index); + + /// + protected override ImageFrame NonGenericCreateFrame(Color backgroundColor) => + this.CreateFrame(backgroundColor.ToPixel()); + + /// + protected override ImageFrame NonGenericCreateFrame() => this.CreateFrame(); + + /// + /// Creates a new and appends it to the end of the collection. + /// + /// The background color to initialize the pixels with. + /// + /// The new . + /// + public ImageFrame CreateFrame(TPixel backgroundColor) + { + var frame = new ImageFrame( + this.parent.GetConfiguration(), + this.RootFrame.Width, + this.RootFrame.Height, + backgroundColor); + this.frames.Add(frame); + return frame; + } + + /// + IEnumerator> IEnumerable>.GetEnumerator() => this.frames.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).GetEnumerator(); + + private void ValidateFrame(ImageFrame 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 f in this.frames) + { + f.Dispose(); + } + + this.frames.Clear(); + } + + private ImageFrame CopyNonCompatibleFrame(ImageFrame source) + { + ImageFrame result = new ImageFrame( + this.parent.GetConfiguration(), + source.Size(), + source.Metadata.DeepClone()); + source.CopyPixelsTo(result.PixelBuffer.Span); + return result; + } + } +} diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 4f0be5971..982f8d8f2 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/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 { /// - /// Represents a single frame in a animation. + /// Represents a pixel-specific image frame containing all pixel data and . + /// 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. /// /// The pixel format. - public sealed class ImageFrame : IPixelSource, IDisposable + public sealed class ImageFrame : ImageFrame, IPixelSource, IDisposable where TPixel : struct, IPixel { private bool isDisposed; @@ -53,8 +56,12 @@ namespace SixLabors.ImageSharp /// The height of the image in pixels. /// The metadata. 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(width, height, AllocationOptions.Clean); } /// @@ -78,15 +85,12 @@ namespace SixLabors.ImageSharp /// The color to clear the image with. /// The metadata. 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(width, height); - this.Metadata = metadata ?? new ImageFrameMetadata(); this.Clear(configuration.GetParallelOptions(), backgroundColor); } @@ -111,16 +115,12 @@ namespace SixLabors.ImageSharp /// The memory source. /// The metadata. internal ImageFrame(Configuration configuration, int width, int height, MemorySource 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(memorySource, width, height); - this.Metadata = metadata; } /// @@ -129,27 +129,15 @@ namespace SixLabors.ImageSharp /// The configuration which allows altering default behaviour or extending the library. /// The source. internal ImageFrame(Configuration configuration, ImageFrame 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(source.PixelBuffer.Width, source.PixelBuffer.Height); source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan()); - this.Metadata = source.Metadata.DeepClone(); } - /// - /// Gets the to use for buffer allocations. - /// - public MemoryAllocator MemoryAllocator { get; } - - /// - /// Gets the instance associated with this . - /// - internal Configuration Configuration { get; } - /// /// Gets the image pixels. Not private as Buffer2D requires an array in its constructor. /// @@ -158,21 +146,6 @@ namespace SixLabors.ImageSharp /// Buffer2D IPixelSource.PixelBuffer => this.PixelBuffer; - /// - /// Gets the width. - /// - public int Width => this.PixelBuffer.Width; - - /// - /// Gets the height. - /// - public int Height => this.PixelBuffer.Height; - - /// - /// Gets the metadata of the frame. - /// - public ImageFrameMetadata Metadata { get; } - /// /// Gets or sets the pixel at the specified position. /// @@ -188,18 +161,6 @@ namespace SixLabors.ImageSharp set => this.PixelBuffer[x, y] = value; } - /// - /// Gets the size of the frame. - /// - /// The - public Size Size() => new Size(this.Width, this.Height); - - /// - /// Gets the bounds of the frame. - /// - /// The - public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); - /// /// Gets a reference to the pixel at the specified position. /// @@ -232,12 +193,13 @@ namespace SixLabors.ImageSharp Guard.NotNull(pixelSource, nameof(pixelSource)); Buffer2D.SwapOrCopyContent(this.PixelBuffer, pixelSource.PixelBuffer); + this.UpdateSize(this.PixelBuffer.Size()); } /// /// Disposes the object and frees resources for the Garbage Collector. /// - internal void Dispose() + public override void Dispose() { if (this.isDisposed) { @@ -251,6 +213,17 @@ namespace SixLabors.ImageSharp this.isDisposed = true; } + internal override void CopyPixelsTo(Span destination) + { + if (typeof(TPixel) == typeof(TDestinationPixel)) + { + Span dest1 = MemoryMarshal.Cast(destination); + this.PixelBuffer.Span.CopyTo(dest1); + } + + PixelOperations.Instance.To(this.Configuration, this.PixelBuffer.Span, destination); + } + /// public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>({this.Width}x{this.Height})"; @@ -325,8 +298,5 @@ namespace SixLabors.ImageSharp span.Fill(value); } } - - /// - void IDisposable.Dispose() => this.Dispose(); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index a3d614a06..3b0211434 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -5,6 +5,16 @@ SixLabors.ImageSharp SixLabors.ImageSharp A cross-platform library for the processing of image files; written in C# + en + + $(packageversion) + 0.0.1 + + netcoreapp2.1;netstandard1.3;netstandard2.0;net472 + + true + true + SixLabors.ImageSharp SixLabors.ImageSharp Image Resize Crop Gif Jpg Jpeg Bitmap Png Core SixLabors.ImageSharp diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 2b93b710b..5d0364dd8 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -80,7 +80,12 @@ namespace SixLabors.ImageSharp /// The width of the image in pixels. /// The height of the image in pixels. /// The images metadata. - internal Image(Configuration configuration, MemorySource memorySource, int width, int height, ImageMetadata metadata) + internal Image( + Configuration configuration, + MemorySource memorySource, + int width, + int height, + ImageMetadata metadata) : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { this.Frames = new ImageFrameCollection(this, width, height, memorySource); @@ -95,7 +100,12 @@ namespace SixLabors.ImageSharp /// The height of the image in pixels. /// The color to initialize the pixels with. /// The images metadata. - 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(), metadata, width, height) { this.Frames = new ImageFrameCollection(this, width, height, backgroundColor); @@ -114,10 +124,13 @@ namespace SixLabors.ImageSharp this.Frames = new ImageFrameCollection(this, frames); } + /// + protected override ImageFrameCollection NonGenericFrameCollection => this.Frames; + /// /// Gets the frames. /// - public ImageFrameCollection Frames { get; } + public new ImageFrameCollection Frames { get; } /// /// Gets the root frame. @@ -149,7 +162,8 @@ namespace SixLabors.ImageSharp /// Returns a new with all the same pixel data as the original. public Image Clone(Configuration configuration) { - IEnumerable> clonedFrames = this.Frames.Select(x => x.Clone(configuration)); + IEnumerable> clonedFrames = + this.Frames.Select, ImageFrame>(x => x.Clone(configuration)); return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); } @@ -161,7 +175,8 @@ namespace SixLabors.ImageSharp /// The . public override Image CloneAs(Configuration configuration) { - IEnumerable> clonedFrames = this.Frames.Select(x => x.CloneAs(configuration)); + IEnumerable> clonedFrames = + this.Frames.Select, ImageFrame>(x => x.CloneAs(configuration)); return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); } @@ -214,4 +229,4 @@ namespace SixLabors.ImageSharp return rootSize; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs index 4cff1f8c1..8481fc62c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.cs +++ b/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 { /// @@ -222,6 +223,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromArgb32(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToArgb32(configuration, sourcePixels, destinationPixels); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt index 0a58504e1..3a4c7b459 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Argb32.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Argb32"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs index 225e4b5e0..4b2b3a02e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/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 { /// @@ -196,6 +197,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromBgr24(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToBgr24(configuration, sourcePixels, destinationPixels); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt index 84b89aa32..cfaefeda9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgr24.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Bgr24"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs index ce049115e..95d85c7c8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/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 { /// @@ -222,6 +223,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromBgra32(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToBgra32(configuration, sourcePixels, destinationPixels); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt index 004ceff51..58ecfa5a6 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Bgra32.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Bgra32"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs index 288c5d92e..e6ed75d49 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.cs +++ b/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 { /// @@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromGray16(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToGray16(configuration, sourcePixels, destinationPixels); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt index 3cbc01e88..ac484bdbc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray16.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Gray16"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs index f7e94788e..c9a3a06fe 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.cs +++ b/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 { /// @@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromGray8(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToGray8(configuration, sourcePixels, destinationPixels); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt index d35843ccd..ffa15af9d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Gray8.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Gray8"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs index 02a8869ec..ef224c02d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/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 { /// @@ -196,6 +197,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgb24(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgb24(configuration, sourcePixels, destinationPixels); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt index d96c3684b..fc149b238 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb24.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Rgb24"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs index 30c9972bb..694388899 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.cs +++ b/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 { /// @@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgb48(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgb48(configuration, sourcePixels, destinationPixels); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt index 7bff33638..957c8f886 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgb48.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Rgb48"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs index da2ce3770..f0c8bc884 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.cs +++ b/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 { /// @@ -211,6 +212,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgba32(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgba32(configuration, sourcePixels, destinationPixels); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt index 6b9e2d124..905e2cc58 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba32.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Rgba32"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs index 42c40ad5d..0faa6eb44 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.cs +++ b/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 { /// @@ -185,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats dp.FromRgba64(sp); } } + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgba64(configuration, sourcePixels, destinationPixels); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt index d15945f94..03179a392 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/Rgba64.PixelOperations.Generated.tt @@ -16,4 +16,4 @@ namespace SixLabors.ImageSharp.PixelFormats <# GenerateAllDefaultConversionMethods("Rgba64"); #> } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude index 584615532..17e9469f3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Generated/_Common.ttinclude +++ b/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) + { +#> + /// + internal override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span<<#=pixelType#>> destinationPixels) + { + PixelOperations.Instance.To<#=pixelType#>(configuration, sourcePixels, destinationPixels); + } +<#+ + } void GenerateDefaultSelfConversionMethods(string pixelType) { @@ -117,7 +130,7 @@ using System.Runtime.InteropServices; } #> /// - internal override void FromVector4(Configuration configuration, Span sourceVectors, Span<<#=pixelType#>> destPixels, PixelConversionModifiers modifiers) + internal override void FromVector4Destructive(Configuration configuration, Span 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); } #> diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs index 6fce1c757..195a8bedb 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -62,11 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [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); /// [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); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs index 1c278b434..09597a949 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Gray8.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -67,15 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [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); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -166,4 +158,4 @@ namespace SixLabors.ImageSharp.PixelFormats this.PackedValue = ImageMaths.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs index 3112126e7..b331dbd99 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.PixelOperations.cs +++ b/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(sourcePixels).CopyTo(destVectors); Vector4Converters.ApplyForwardConversionModifiers(destVectors, modifiers); } + + internal override void ToGray8(Configuration configuration, ReadOnlySpan sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Vector4 sourceBaseRef = ref Unsafe.As(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 sourcePixels, Span destPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destPixels, nameof(destPixels)); + + ref Vector4 sourceBaseRef = ref Unsafe.As(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); + } + } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index d009822e8..2b2d79c73 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/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 { /// @@ -89,63 +91,56 @@ namespace SixLabors.ImageSharp.PixelFormats Span destVectors) => this.ToVector4(configuration, sourcePixels, destVectors, PixelConversionModifiers.None); - /// - /// Converts 'sourceColors.Length' pixels from 'sourceColors' into 'destinationColors'. - /// - /// The destination pixel type. - /// A to configure internal operations - /// The to the source colors. - /// The to the destination colors. - internal virtual void To( + internal virtual void From( Configuration configuration, - ReadOnlySpan sourceColors, - Span destinationColors) - where TDestinationPixel : struct, IPixel + ReadOnlySpan sourcePixels, + Span destinationPixels) + where TSourcePixel : struct, IPixel { - 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 tempVectors = configuration.MemoryAllocator.Allocate(SliceLength)) { - ref Gray16 gray16Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destinationColors)); - for (int i = 0; i < count; i++) + Span 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 s = sourcePixels.Slice(start, SliceLength); + Span d = destinationPixels.Slice(start, SliceLength); + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan); + this.FromVector4Destructive(configuration, vectorSpan, d); } - return; - } - - if (typeof(TDestinationPixel) == typeof(Gray8)) - { - ref Gray8 gray8Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(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 s = sourcePixels.Slice(endOfCompleteSlices); + Span d = destinationPixels.Slice(endOfCompleteSlices); + vectorSpan = vectorSpan.Slice(0, remainder); + PixelOperations.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()); - } + /// + /// Converts 'sourcePixels.Length' pixels from 'sourcePixels' into 'destinationPixels'. + /// + /// The destination pixel type. + /// A to configure internal operations. + /// The to the source pixels. + /// The to the destination pixels. + internal virtual void To( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + where TDestinationPixel : struct, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + PixelOperations.Instance.From(configuration, sourcePixels, destinationPixels); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 9625ca216..529a49bf7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select( + IEnumerable> frames = source.Frames.Select, ImageFrame>( x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions, x.Metadata.DeepClone())); // Use the overload to prevent an extra frame being added diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 4c4c27def..25f8d7849 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -36,7 +36,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.Metadata.DeepClone())); + IEnumerable> frames = source.Frames.Select, ImageFrame>( + x => new ImageFrame( + source.GetConfiguration(), + this.CropRectangle.Width, + this.CropRectangle.Height, + x.Metadata.DeepClone())); // Use the overload to prevent an extra frame being added return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 15d8141ba..80b03000b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select( + IEnumerable> frames = source.Frames.Select, ImageFrame>( x => new ImageFrame( source.GetConfiguration(), this.TargetDimensions.Width, @@ -140,4 +140,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index fdd1d339d..fb03057a4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -68,7 +68,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.Width, this.Height, x.Metadata.DeepClone())); + IEnumerable> frames = source.Frames.Select, ImageFrame>( + x => new ImageFrame( + source.GetConfiguration(), + this.Width, + this.Height, + x.Metadata.DeepClone())); // Use the overload to prevent an extra frame being added return new Image(source.GetConfiguration(), source.Metadata.DeepClone(), frames); diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs new file mode 100644 index 000000000..37c3a27cd --- /dev/null +++ b/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( + () => + { + this.Collection.AddFrame(new ImageFrame(Configuration.Default, 1, 1)); + }); + + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } + + [Fact] + public void AddNewFrame_Frame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws( + () => + { + this.Collection.AddFrame((ImageFrame)null); + }); + + Assert.StartsWith("Value cannot be null.", ex.Message); + } + + [Fact] + public void AddNewFrame_PixelBuffer_DataMustNotBeNull() + { + Rgba32[] data = null; + + ArgumentNullException ex = Assert.Throws( + () => + { + this.Collection.AddFrame(data); + }); + + Assert.StartsWith("Value cannot be null.", ex.Message); + } + + [Fact] + public void AddNewFrame_PixelBuffer_BufferIncorrectSize() + { + ArgumentOutOfRangeException ex = Assert.Throws( + () => + { + 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( + () => + { + this.Collection.InsertFrame(1, new ImageFrame(Configuration.Default, 1, 1)); + }); + + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } + + [Fact] + public void InsertNewFrame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws( + () => + { + this.Collection.InsertFrame(1, null); + }); + + Assert.StartsWith("Value cannot be null.", ex.Message); + } + + [Fact] + public void Constructor_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws( + () => + { + var collection = new ImageFrameCollection( + this.Image, + new[] + { + new ImageFrame(Configuration.Default, 10, 10), + new ImageFrame(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( + this.Image, + new[] { new ImageFrame(Configuration.Default, 10, 10) }); + + InvalidOperationException ex = Assert.Throws( + () => + { + collection.RemoveFrame(0); + }); + Assert.Equal("Cannot remove last frame.", ex.Message); + } + + [Fact] + public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() + { + var collection = new ImageFrameCollection( + this.Image, + new[] + { + new ImageFrame(Configuration.Default, 10, 10), + new ImageFrame(Configuration.Default, 10, 10) + }); + + collection.RemoveFrame(0); + Assert.Equal(1, collection.Count); + } + + [Fact] + public void RootFrameIsFrameAtIndexZero() + { + var collection = new ImageFrameCollection( + this.Image, + new[] + { + new ImageFrame(Configuration.Default, 10, 10), + new ImageFrame(Configuration.Default, 10, 10) + }); + + Assert.Equal(collection.RootFrame, collection[0]); + } + + [Fact] + public void ConstructorPopulatesFrames() + { + var collection = new ImageFrameCollection( + this.Image, + new[] + { + new ImageFrame(Configuration.Default, 10, 10), + new ImageFrame(Configuration.Default, 10, 10) + }); + + Assert.Equal(2, collection.Count); + } + + [Fact] + public void DisposeClearsCollection() + { + var collection = new ImageFrameCollection( + this.Image, + new[] + { + new ImageFrame(Configuration.Default, 10, 10), + new ImageFrame(Configuration.Default, 10, 10) + }); + + collection.Dispose(); + + Assert.Equal(0, collection.Count); + } + + [Fact] + public void Dispose_DisposesAllInnerFrames() + { + var collection = new ImageFrameCollection( + this.Image, + new[] + { + new ImageFrame(Configuration.Default, 10, 10), + new ImageFrame(Configuration.Default, 10, 10) + }); + + IPixelSource[] framesSnapShot = collection.OfType>().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(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + img.Frames.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); // add a frame anyway + using (Image cloned = img.Frames.CloneFrame(0)) + { + Assert.Equal(2, img.Frames.Count); + cloned.ComparePixelBufferTo(img.GetPixelSpan()); + } + } + } + + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] + public void ExtractFrame(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + var sourcePixelData = img.GetPixelSpan().ToArray(); + + img.Frames.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); + using (Image 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(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(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(Configuration.Default, 10, 10); + Assert.False(this.Image.Frames.Contains(frame)); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs new file mode 100644 index 000000000..0956cce9c --- /dev/null +++ b/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 sourceImage = new Image( + 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 actualFrame = (ImageFrame)this.Collection[1]; + + actualFrame.ComparePixelBufferTo(expectedAllBlue); + } + + [Fact] + public void InsertFrame_OfDifferentPixelType() + { + using (Image sourceImage = new Image( + 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 actualFrame = (ImageFrame)this.Collection[0]; + + actualFrame.ComparePixelBufferTo(expectedAllBlue); + + } + + [Fact] + public void Constructor_ShouldCreateOneFrame() + { + Assert.Equal(1, this.Collection.Count); + } + + [Fact] + public void AddNewFrame_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws( + () => + { + this.Collection.AddFrame(new ImageFrame(Configuration.Default, 1, 1)); + }); + + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } + + [Fact] + public void AddNewFrame_Frame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws( + () => + { + this.Collection.AddFrame((ImageFrame)null); + }); + + Assert.StartsWith("Value cannot be null.", ex.Message); + } + + [Fact] + public void InsertNewFrame_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws( + () => + { + this.Collection.InsertFrame(1, new ImageFrame(Configuration.Default, 1, 1)); + }); + + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } + + [Fact] + public void InsertNewFrame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws( + () => + { + this.Collection.InsertFrame(1, null); + }); + + Assert.StartsWith("Value cannot be null.", ex.Message); + } + + + [Fact] + public void RemoveAtFrame_ThrowIfRemovingLastFrame() + { + + InvalidOperationException ex = Assert.Throws( + () => + { + this.Collection.RemoveFrame(0); + }); + Assert.Equal("Cannot remove last frame.", ex.Message); + } + + [Fact] + public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() + { + this.Collection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); + + this.Collection.RemoveFrame(0); + Assert.Equal(1, this.Collection.Count); + } + + [Fact] + public void RootFrameIsFrameAtIndexZero() + { + Assert.Equal(this.Collection.RootFrame, this.Collection[0]); + } + + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32 | PixelTypes.Bgr24)] + public void CloneFrame(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + ImageFrameCollection nonGenericFrameCollection = img.Frames; + + nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); // add a frame anyway + using (Image cloned = nonGenericFrameCollection.CloneFrame(0)) + { + Assert.Equal(2, img.Frames.Count); + + Image expectedClone = (Image)cloned; + + expectedClone.ComparePixelBufferTo(img.GetPixelSpan()); + } + } + } + + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] + public void ExtractFrame(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image img = provider.GetImage()) + { + var sourcePixelData = img.GetPixelSpan().ToArray(); + + ImageFrameCollection nonGenericFrameCollection = img.Frames; + + nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); + using (Image cloned = nonGenericFrameCollection.ExportFrame(0)) + { + Assert.Equal(1, img.Frames.Count); + + Image expectedClone = (Image)cloned; + expectedClone.ComparePixelBufferTo(sourcePixelData); + } + } + } + + [Fact] + public void CreateFrame_Default() + { + this.Image.Frames.CreateFrame(); + + Assert.Equal(2, this.Image.Frames.Count); + + ImageFrame frame = (ImageFrame)this.Image.Frames[1]; + + frame.ComparePixelBufferTo(default(Rgba32)); + } + + [Fact] + public void CreateFrame_CustomFillColor() + { + this.Image.Frames.CreateFrame(Rgba32.HotPink); + + Assert.Equal(2, this.Image.Frames.Count); + + ImageFrame frame = (ImageFrame)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(Configuration.Default, 10, 10); + Assert.False(this.Image.Frames.Contains(frame)); + } + + /// + /// Integration test for end-to end API validation. + /// + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void ConstructGif_FromDifferentPixelTypes(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (Image dest = new Image(source.GetConfiguration(), source.Width, source.Height)) + { + // Giphy.gif has 5 frames + + ImportFrameAs(source.Frames, dest.Frames, 0); + ImportFrameAs(source.Frames, dest.Frames, 1); + ImportFrameAs(source.Frames, dest.Frames, 2); + ImportFrameAs(source.Frames, dest.Frames, 3); + ImportFrameAs(source.Frames, dest.Frames, 4); + + // Drop the original empty root frame: + dest.Frames.RemoveFrame(0); + + dest.DebugSave(provider, 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(ImageFrameCollection source, ImageFrameCollection destination, int index) + where TPixel : struct, IPixel + { + using (Image temp = source.CloneFrame(index)) + { + using (Image temp2 = temp.CloneAs()) + { + destination.AddFrame(temp2.Frames.RootFrame); + } + } + } + + private static void CompareGifMetadata(ImageFrame a, ImageFrame b) + { + // TODO: all metadata classes should be equatable! + + GifFrameMetadata aData = a.Metadata.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); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs new file mode 100644 index 000000000..a29f45271 --- /dev/null +++ b/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 Image { get; } + protected ImageFrameCollection 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(10, 10); + this.Collection = new ImageFrameCollection(this.Image, 10, 10, default(Rgba32)); + } + + public void Dispose() + { + this.Image.Dispose(); + this.Collection.Dispose(); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs deleted file mode 100644 index 392397057..000000000 --- a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs +++ /dev/null @@ -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 image; - private ImageFrameCollection 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(10, 10); - this.collection = new ImageFrameCollection(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(() => - { - this.collection.AddFrame(new ImageFrame(Configuration.Default, 1, 1)); - }); - - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } - - [Fact] - public void AddNewFrame_Frame_FramesNotBeNull() - { - ArgumentNullException ex = Assert.Throws(() => - { - this.collection.AddFrame((ImageFrame)null); - }); - - Assert.StartsWith("Value cannot be null.", ex.Message); - } - - [Fact] - public void AddNewFrame_PixelBuffer_DataMustNotBeNull() - { - Rgba32[] data = null; - - ArgumentNullException ex = Assert.Throws(() => - { - this.collection.AddFrame(data); - }); - - Assert.StartsWith("Value cannot be null.", ex.Message); - } - - [Fact] - public void AddNewFrame_PixelBuffer_BufferIncorrectSize() - { - ArgumentOutOfRangeException ex = Assert.Throws(() => - { - 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(() => - { - this.collection.InsertFrame(1, new ImageFrame(Configuration.Default, 1, 1)); - }); - - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } - - [Fact] - public void InsertNewFrame_FramesNotBeNull() - { - ArgumentNullException ex = Assert.Throws(() => - { - this.collection.InsertFrame(1, null); - }); - - Assert.StartsWith("Value cannot be null.", ex.Message); - } - - [Fact] - public void Constructor_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws(() => - { - var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(Configuration.Default,10,10), - new ImageFrame(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(this.image, new[] { - new ImageFrame(Configuration.Default,10,10) - }); - - InvalidOperationException ex = Assert.Throws(() => - { - collection.RemoveFrame(0); - }); - Assert.Equal("Cannot remove last frame.", ex.Message); - } - - [Fact] - public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() - { - - var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(Configuration.Default,10,10), - new ImageFrame(Configuration.Default,10,10) - }); - - collection.RemoveFrame(0); - Assert.Equal(1, collection.Count); - } - - [Fact] - public void RootFrameIsFrameAtIndexZero() - { - var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(Configuration.Default,10,10), - new ImageFrame(Configuration.Default,10,10) - }); - - Assert.Equal(collection.RootFrame, collection[0]); - } - - [Fact] - public void ConstructorPopulatesFrames() - { - var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(Configuration.Default,10,10), - new ImageFrame(Configuration.Default,10,10) - }); - - Assert.Equal(2, collection.Count); - } - - [Fact] - public void DisposeClearsCollection() - { - var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(Configuration.Default,10,10), - new ImageFrame(Configuration.Default,10,10) - }); - - collection.Dispose(); - - Assert.Equal(0, collection.Count); - } - - [Fact] - public void Dispose_DisposesAllInnerFrames() - { - var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(Configuration.Default,10,10), - new ImageFrame(Configuration.Default,10,10) - }); - - IPixelSource[] framesSnapShot = collection.OfType>().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(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image img = provider.GetImage()) - { - img.Frames.AddFrame(new ImageFrame(Configuration.Default, 10, 10));// add a frame anyway - using (Image cloned = img.Frames.CloneFrame(0)) - { - Assert.Equal(2, img.Frames.Count); - cloned.ComparePixelBufferTo(img.GetPixelSpan()); - } - } - } - - [Theory] - [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] - public void ExtractFrame(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image img = provider.GetImage()) - { - var sourcePixelData = img.GetPixelSpan().ToArray(); - - img.Frames.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); - using (Image 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(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(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(Configuration.Default, 10, 10); - Assert.False(this.image.Frames.Contains(frame)); - } - - public void Dispose() - { - this.image.Dispose(); - this.collection.Dispose(); - } - } -} diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 406719ab4..4f92f4007 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -2,6 +2,13 @@ + netcoreapp2.1;net462;net472 + True + latest + full + portable + True + SixLabors.ImageSharp.Tests SixLabors.ImageSharp.Tests AnyCPU;x64;x86 SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs new file mode 100644 index 000000000..d03cfeee2 --- /dev/null +++ b/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( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + where TSourcePixel : struct, IPixel where TDestinationPixel : struct, IPixel + { + 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 uniformDest = + MemoryMarshal.Cast(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(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(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()); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs index c539e9dcf..f1f56fcd4 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs +++ b/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 RgbaData = new TheoryData @@ -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; - } - } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra5551OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Bgra5551OperationsTests.cs new file mode 100644 index 000000000..aa9b7bb1b --- /dev/null +++ b/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 + { + public Bgra5551OperationsTests(ITestOutputHelper output) + : base(output) + { + } + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs index c3de33547..f44401ae9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray16OperationsTests.cs +++ b/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(PixelOperations.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 sourceSpan = source.AsSpan(); - var expected = new Gray16[count]; - - for (int i = 0; i < count; i++) - { - int i2 = i * 2; - expected[i].FromGray16(MemoryMarshal.Cast(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(ref gray); - expected[i2] = bytes[0]; - expected[i2 + 1] = bytes[1]; - } - - TestOperation( - source, - expected, - (s, d) => Operations.ToGray16Bytes(this.Configuration, s, d.GetSpan(), count) - ); - } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs index acd6ef23a..30c429000 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Gray8OperationsTests.cs @@ -24,91 +24,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [Fact] public void IsSpecialImplementation() => Assert.IsType(PixelOperations.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 sourceSpan = source.AsSpan(); - var expected = new Gray8[count]; - - for (int i = 0; i < count; i++) - { - int i2 = i * 2; - expected[i].FromGray16(MemoryMarshal.Cast(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(ref gray); - expected[i2] = bytes[0]; - expected[i2 + 1] = bytes[1]; - } - - TestOperation( - source, - expected, - (s, d) => Operations.ToGray16Bytes(this.Configuration, s, d.GetSpan(), count) - ); - } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs index 1c7d312d3..37793911a 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgb24OperationsTests.cs @@ -21,4 +21,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index d9ae9131f..71adc1749 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/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 Generic_To_Data = new TheoryData + { + 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 dummy) + where TDestPixel : struct, IPixel + { + const int Count = 2134; + TPixel[] source = CreatePixelTestData(Count); + TDestPixel[] expected = new TDestPixel[Count]; + + PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); + + TestOperation(source, expected, (s, d) => Operations.To(this.Configuration, (ReadOnlySpan)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(ref T1 arg1); internal static Vector4[] CreateExpectedVector4Data(TPixel[] source, RefAction 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 expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); + Span actual = MemoryMarshal.Cast(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 expected = this.ExpectedDestBuffer.AsSpan(); @@ -915,4 +1042,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations } } } -} \ No newline at end of file +}