Browse Source

Merge branch 'master' into js/projective-transforms

pull/546/head
James Jackson-South 8 years ago
parent
commit
4f9edfae38
  1. 21
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/SolidBrush{TPixel}.cs
  2. 74
      src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs
  3. 115
      src/ImageSharp/IImageFrameCollection.cs
  4. 139
      src/ImageSharp/ImageFrameCollection.cs
  5. 59
      src/ImageSharp/ImageFrame{TPixel}.cs
  6. 37
      src/ImageSharp/Image{TPixel}.cs
  7. 24
      src/ImageSharp/PixelFormats/PixelBlenderMode.cs
  8. 92
      tests/ImageSharp.Tests/Drawing/BlendedShapes.cs
  9. 173
      tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
  10. 154
      tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs
  11. 2
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  12. 15
      tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs
  13. 60
      tests/ImageSharp.Tests/Image/ImageTests.cs
  14. 2
      tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs
  15. 52
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
  16. 52
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
  17. 5
      tests/ImageSharp.Tests/TestUtilities/TestUtils.cs

21
src/ImageSharp.Drawing/Processing/Drawing/Brushes/SolidBrush{TPixel}.cs

@ -90,16 +90,23 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes
MemoryManager memoryManager = this.Target.MemoryManager;
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
if (this.Options.BlendPercentage == 1f)
{
Span<float> amountSpan = amountBuffer.Span;
for (int i = 0; i < scanline.Length; i++)
this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, scanline);
}
else
{
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
{
amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
}
Span<float> amountSpan = amountBuffer.Span;
for (int i = 0; i < scanline.Length; i++)
{
amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
}
this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan);
this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan);
}
}
}
}

74
src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs

@ -3,6 +3,7 @@
using System;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Drawing.Brushes;
@ -49,39 +50,66 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors
int minY = Math.Max(0, startY);
int maxY = Math.Min(source.Height, endY);
// Reset offset if necessary.
if (minX > 0)
{
startX = 0;
}
if (minY > 0)
{
startY = 0;
}
int width = maxX - minX;
using (IBuffer<float> amount = source.MemoryManager.Allocate<float>(width))
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(
source,
sourceRectangle,
this.options))
// If there's no reason for blending, then avoid it.
if (this.IsSolidBrushWithoutBlending(out SolidBrush<TPixel> solidBrush))
{
amount.Span.Fill(this.options.BlendPercentage);
Parallel.For(
minY,
maxY,
configuration.ParallelOptions,
y =>
{
int offsetY = y - startY;
int offsetX = minX - startX;
{
source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color);
});
}
else
{
// Reset offset if necessary.
if (minX > 0)
{
startX = 0;
}
if (minY > 0)
{
startY = 0;
}
using (IBuffer<float> amount = source.MemoryManager.Allocate<float>(width))
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(
source,
sourceRectangle,
this.options))
{
amount.Span.Fill(1f);
Parallel.For(
minY,
maxY,
configuration.ParallelOptions,
y =>
{
int offsetY = y - startY;
int offsetX = minX - startX;
applicator.Apply(amount.Span, offsetX, offsetY);
});
applicator.Apply(amount.Span, offsetX, offsetY);
});
}
}
}
private bool IsSolidBrushWithoutBlending(out SolidBrush<TPixel> solidBrush)
{
solidBrush = this.brush as SolidBrush<TPixel>;
return solidBrush != null
&& ((this.options.BlenderMode == PixelBlenderMode.Normal && this.options.BlendPercentage == 1f
&& solidBrush.Color.ToVector4().W == 1f)
|| (this.options.BlenderMode == PixelBlenderMode.Over && this.options.BlendPercentage == 1f
&& solidBrush.Color.ToVector4().W == 1f)
|| (this.options.BlenderMode == PixelBlenderMode.Src));
}
}
}

115
src/ImageSharp/IImageFrameCollection.cs

@ -1,115 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Encapsulates a collection of <see cref="ImageFrame{T}"/> instances that make up an <see cref="Image{T}"/>.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
public interface IImageFrameCollection<TPixel> : IEnumerable<ImageFrame<TPixel>>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets the number of frames.
/// </summary>
int Count { get; }
/// <summary>
/// Gets the root frame.
/// </summary>
ImageFrame<TPixel> RootFrame { get; }
/// <summary>
/// Gets the <see cref="ImageFrame{TPixel}"/> at the specified index.
/// </summary>
/// <value>
/// The <see cref="ImageFrame{TPixel}"/>.
/// </value>
/// <param name="index">The index.</param>
/// <returns>The <see cref="ImageFrame{TPixel}"/> at the specified index.</returns>
ImageFrame<TPixel> this[int index] { get; }
/// <summary>
/// Creates an <see cref="Image{T}"/> with only the frame at the specified index
/// with the same metadata as the original image.
/// </summary>
/// <param name="index">The zero-based index of the frame to clone.</param>
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
Image<TPixel> CloneFrame(int index);
/// <summary>
/// Removes the frame at the specified index and creates a new image with only the removed frame
/// with the same metadata as the original image.
/// </summary>
/// <param name="index">The zero-based index of the frame to export.</param>
/// <exception cref="InvalidOperationException">Cannot remove last frame.</exception>
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
Image<TPixel> ExportFrame(int index);
/// <summary>
/// Removes the frame at the specified index and frees all freeable resources associated with it.
/// </summary>
/// <param name="index">The zero-based index of the frame to remove.</param>
/// <exception cref="InvalidOperationException">Cannot remove last frame.</exception>
void RemoveFrame(int index);
/// <summary>
/// Creates a new <seealso cref="ImageFrame{TPixel}"/> and appends it to the end of the collection.
/// </summary>
/// <returns>The new <see cref="ImageFrame{TPixel}"/>.</returns>
ImageFrame<TPixel> CreateFrame();
/// <summary>
/// Clones the <paramref name="source"/> frame and appends the clone to the end of the collection.
/// </summary>
/// <param name="source">The raw pixel data to generate the <seealso cref="ImageFrame{TPixel}"/> from.</param>
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> source);
/// <summary>
/// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the
/// new frame at the end of the collection.
/// </summary>
/// <param name="source">The raw pixel data to generate the <seealso cref="ImageFrame{TPixel}"/> from.</param>
/// <returns>The new <see cref="ImageFrame{TPixel}"/>.</returns>
ImageFrame<TPixel> AddFrame(TPixel[] source);
/// <summary>
/// Clones and inserts the <paramref name="source"/> into the <seealso cref="IImageFrameCollection{TPixel}"/> at the specified <paramref name="index"/>.
/// </summary>
/// <param name="index">The zero-based index to insert the frame at.</param>
/// <param name="source">The <seealso cref="ImageFrame{TPixel}"/> to clone and insert into the <seealso cref="IImageFrameCollection{TPixel}"/>.</param>
/// <exception cref="ArgumentException">Frame must have the same dimensions as the image.</exception>
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
ImageFrame<TPixel> InsertFrame(int index, ImageFrame<TPixel> source);
/// <summary>
/// Moves an <seealso cref="ImageFrame{TPixel}"/> from <paramref name="sourceIndex"/> to <paramref name="destinationIndex"/>.
/// </summary>
/// <param name="sourceIndex">The zero-based index of the frame to move.</param>
/// <param name="destinationIndex">The index to move the frame to.</param>
void MoveFrame(int sourceIndex, int destinationIndex);
/// <summary>
/// Determines the index of a specific <paramref name="frame"/> in the <seealso cref="IImageFrameCollection{TPixel}"/>.
/// </summary>
/// <param name="frame">The <seealso cref="ImageFrame{TPixel}"/> to locate in the <seealso cref="IImageFrameCollection{TPixel}"/>.</param>
/// <returns>The index of item if found in the list; otherwise, -1.</returns>
int IndexOf(ImageFrame<TPixel> frame);
/// <summary>
/// Determines whether the <seealso cref="IImageFrameCollection{TPixel}"/> contains the <paramref name="frame"/>.
/// </summary>
/// <param name="frame">The frame.</param>
/// <returns>
/// <c>true</c> if the <seealso cref="IImageFrameCollection{TPixel}"/> contains the specified frame; otherwise, <c>false</c>.
/// </returns>
bool Contains(ImageFrame<TPixel> frame);
}
}

139
src/ImageSharp/ImageFrameCollection.cs

@ -9,21 +9,24 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <inheritdoc/>
internal sealed class ImageFrameCollection<TPixel> : IImageFrameCollection<TPixel>
/// <summary>
/// Encapsulates a collection of <see cref="ImageFrame{T}"/> instances that make up an <see cref="Image{T}"/>.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
public sealed class ImageFrameCollection<TPixel> : IEnumerable<ImageFrame<TPixel>>
where TPixel : struct, IPixel<TPixel>
{
private readonly IList<ImageFrame<TPixel>> frames = new List<ImageFrame<TPixel>>();
private readonly Image<TPixel> parent;
internal ImageFrameCollection(Image<TPixel> parent, int width, int height)
internal ImageFrameCollection(Image<TPixel> parent, int width, int height, TPixel backgroundColor)
{
Guard.NotNull(parent, nameof(parent));
this.parent = parent;
// Frames are already cloned within the caller
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration().MemoryManager, width, height));
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, backgroundColor));
}
internal ImageFrameCollection(Image<TPixel> parent, IEnumerable<ImageFrame<TPixel>> frames)
@ -41,51 +44,85 @@ namespace SixLabors.ImageSharp
}
}
/// <inheritdoc/>
/// <summary>
/// Gets the number of frames.
/// </summary>
public int Count => this.frames.Count;
/// <inheritdoc/>
/// <summary>
/// Gets the root frame.
/// </summary>
public ImageFrame<TPixel> RootFrame => this.frames.Count > 0 ? this.frames[0] : null;
/// <inheritdoc/>
/// <summary>
/// Gets the <see cref="ImageFrame{TPixel}"/> at the specified index.
/// </summary>
/// <value>
/// The <see cref="ImageFrame{TPixel}"/>.
/// </value>
/// <param name="index">The index.</param>
/// <returns>The <see cref="ImageFrame{TPixel}"/> at the specified index.</returns>
public ImageFrame<TPixel> this[int index] => this.frames[index];
/// <inheritdoc/>
/// <summary>
/// Determines the index of a specific <paramref name="frame"/> in the <seealso cref="ImageFrameCollection{TPixel}"/>.
/// </summary>
/// <param name="frame">The <seealso cref="ImageFrame{TPixel}"/> to locate in the <seealso cref="ImageFrameCollection{TPixel}"/>.</param>
/// <returns>The index of item if found in the list; otherwise, -1.</returns>
public int IndexOf(ImageFrame<TPixel> frame) => this.frames.IndexOf(frame);
/// <inheritdoc/>
public ImageFrame<TPixel> InsertFrame(int index, ImageFrame<TPixel> frame)
/// <summary>
/// Clones and inserts the <paramref name="source"/> into the <seealso cref="ImageFrameCollection{TPixel}"/> at the specified <paramref name="index"/>.
/// </summary>
/// <param name="index">The zero-based index to insert the frame at.</param>
/// <param name="source">The <seealso cref="ImageFrame{TPixel}"/> to clone and insert into the <seealso cref="ImageFrameCollection{TPixel}"/>.</param>
/// <exception cref="ArgumentException">Frame must have the same dimensions as the image.</exception>
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
public ImageFrame<TPixel> InsertFrame(int index, ImageFrame<TPixel> source)
{
this.ValidateFrame(frame);
ImageFrame<TPixel> clonedFrame = frame.Clone();
this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone();
this.frames.Insert(index, clonedFrame);
return clonedFrame;
}
/// <inheritdoc/>
public ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> frame)
/// <summary>
/// Clones the <paramref name="source"/> frame and appends the clone to the end of the collection.
/// </summary>
/// <param name="source">The raw pixel data to generate the <seealso cref="ImageFrame{TPixel}"/> from.</param>
/// <returns>The cloned <see cref="ImageFrame{TPixel}"/>.</returns>
public ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> source)
{
this.ValidateFrame(frame);
ImageFrame<TPixel> clonedFrame = frame.Clone();
this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = source.Clone();
this.frames.Add(clonedFrame);
return clonedFrame;
}
/// <inheritdoc/>
public ImageFrame<TPixel> AddFrame(TPixel[] data)
/// <summary>
/// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the
/// new frame at the end of the collection.
/// </summary>
/// <param name="source">The raw pixel data to generate the <seealso cref="ImageFrame{TPixel}"/> from.</param>
/// <returns>The new <see cref="ImageFrame{TPixel}"/>.</returns>
public ImageFrame<TPixel> AddFrame(TPixel[] source)
{
Guard.NotNull(data, nameof(data));
Guard.NotNull(source, nameof(source));
var frame = ImageFrame.LoadPixelData(
this.parent.GetMemoryManager(),
new Span<TPixel>(data),
new Span<TPixel>(source),
this.RootFrame.Width,
this.RootFrame.Height);
this.frames.Add(frame);
return frame;
}
/// <inheritdoc/>
/// <summary>
/// Removes the frame at the specified index and frees all freeable resources associated with it.
/// </summary>
/// <param name="index">The zero-based index of the frame to remove.</param>
/// <exception cref="InvalidOperationException">Cannot remove last frame.</exception>
public void RemoveFrame(int index)
{
if (index == 0 && this.Count == 1)
@ -98,26 +135,42 @@ namespace SixLabors.ImageSharp
frame.Dispose();
}
/// <inheritdoc/>
/// <summary>
/// Determines whether the <seealso cref="ImageFrameCollection{TPixel}"/> contains the <paramref name="frame"/>.
/// </summary>
/// <param name="frame">The frame.</param>
/// <returns>
/// <c>true</c> if the <seealso cref="ImageFrameCollection{TPixel}"/> contains the specified frame; otherwise, <c>false</c>.
/// </returns>
public bool Contains(ImageFrame<TPixel> frame)
{
return this.frames.Contains(frame);
}
/// <inheritdoc/>
public void MoveFrame(int sourceIndex, int destIndex)
/// <summary>
/// Moves an <seealso cref="ImageFrame{TPixel}"/> from <paramref name="sourceIndex"/> to <paramref name="destinationIndex"/>.
/// </summary>
/// <param name="sourceIndex">The zero-based index of the frame to move.</param>
/// <param name="destinationIndex">The index to move the frame to.</param>
public void MoveFrame(int sourceIndex, int destinationIndex)
{
if (sourceIndex == destIndex)
if (sourceIndex == destinationIndex)
{
return;
}
ImageFrame<TPixel> frameAtIndex = this.frames[sourceIndex];
this.frames.RemoveAt(sourceIndex);
this.frames.Insert(destIndex, frameAtIndex);
this.frames.Insert(destinationIndex, frameAtIndex);
}
/// <inheritdoc/>
/// <summary>
/// Removes the frame at the specified index and creates a new image with only the removed frame
/// with the same metadata as the original image.
/// </summary>
/// <param name="index">The zero-based index of the frame to export.</param>
/// <exception cref="InvalidOperationException">Cannot remove last frame.</exception>
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
public Image<TPixel> ExportFrame(int index)
{
ImageFrame<TPixel> frame = this[index];
@ -132,7 +185,12 @@ namespace SixLabors.ImageSharp
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { frame });
}
/// <inheritdoc/>
/// <summary>
/// Creates an <see cref="Image{T}"/> with only the frame at the specified index
/// with the same metadata as the original image.
/// </summary>
/// <param name="index">The zero-based index of the frame to clone.</param>
/// <returns>The new <see cref="Image{TPixel}"/> with the specified frame.</returns>
public Image<TPixel> CloneFrame(int index)
{
ImageFrame<TPixel> frame = this[index];
@ -140,10 +198,31 @@ namespace SixLabors.ImageSharp
return new Image<TPixel>(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { clonedFrame });
}
/// <inheritdoc/>
/// <summary>
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection.
/// </summary>
/// <returns>
/// The new <see cref="ImageFrame{TPixel}" />.
/// </returns>
public ImageFrame<TPixel> CreateFrame()
{
var frame = new ImageFrame<TPixel>(this.parent.GetConfiguration().MemoryManager, this.RootFrame.Width, this.RootFrame.Height);
return this.CreateFrame(default);
}
/// <summary>
/// Creates a new <seealso cref="ImageFrame{TPixel}" /> and appends it to the end of the collection.
/// </summary>
/// <param name="backgroundColor">The background color to initialize the pixels with.</param>
/// <returns>
/// The new <see cref="ImageFrame{TPixel}" />.
/// </returns>
public ImageFrame<TPixel> CreateFrame(TPixel backgroundColor)
{
var frame = new ImageFrame<TPixel>(
this.parent.GetConfiguration(),
this.RootFrame.Width,
this.RootFrame.Height,
backgroundColor);
this.frames.Add(frame);
return frame;
}

59
src/ImageSharp/ImageFrame{TPixel}.cs

@ -4,6 +4,7 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
@ -18,8 +19,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class ImageFrame<TPixel> : IPixelSource<TPixel>, IDisposable
where TPixel : struct, IPixel<TPixel>
{
where TPixel : struct, IPixel<TPixel> {
private bool isDisposed;
/// <summary>
@ -29,8 +29,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
internal ImageFrame(MemoryManager memoryManager, int width, int height)
: this(memoryManager, width, height, new ImageFrameMetaData())
{
: this(memoryManager, width, height, new ImageFrameMetaData()) {
}
/// <summary>
@ -52,6 +51,38 @@ namespace SixLabors.ImageSharp
this.MetaData = metaData;
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The <see cref="Configuration"/> to use for buffer allocation and parallel options to clear the buffer with.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to clear the image with.</param>
internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor)
: this(configuration, width, height, backgroundColor, new ImageFrameMetaData()) {
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The <see cref="Configuration"/> to use for buffer allocation and parallel options to clear the buffer with.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to clear the image with.</param>
/// <param name="metaData">The meta data.</param>
internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor, ImageFrameMetaData metaData)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
Guard.NotNull(metaData, nameof(metaData));
this.MemoryManager = configuration.MemoryManager;
this.PixelBuffer = this.MemoryManager.Allocate2D<TPixel>(width, height, false);
this.Clear(configuration.ParallelOptions, backgroundColor);
this.MetaData = metaData;
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class.
/// </summary>
@ -59,8 +90,7 @@ namespace SixLabors.ImageSharp
/// <param name="size">The <see cref="Size"/> of the frame.</param>
/// <param name="metaData">The meta data.</param>
internal ImageFrame(MemoryManager memoryManager, Size size, ImageFrameMetaData metaData)
: this(memoryManager, size.Width, size.Height, metaData)
{
: this(memoryManager, size.Width, size.Height, metaData) {
}
/// <summary>
@ -267,6 +297,23 @@ namespace SixLabors.ImageSharp
return target;
}
/// <summary>
/// Clears the bitmap.
/// </summary>
/// <param name="parallelOptions">The parallel options.</param>
/// <param name="value">The value to initialize the bitmap with.</param>
internal void Clear(ParallelOptions parallelOptions, TPixel value) {
Parallel.For(
0,
this.Height,
parallelOptions,
(int y) =>
{
Span<TPixel> targetRow = this.GetPixelRowSpan(y);
targetRow.Fill(value);
});
}
/// <summary>
/// Clones the current instance.
/// </summary>

37
src/ImageSharp/Image{TPixel}.cs

@ -37,6 +37,21 @@ namespace SixLabors.ImageSharp
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to initialize the pixels with.</param>
public Image(Configuration configuration, int width, int height, TPixel backgroundColor)
: this(configuration, width, height, backgroundColor, new ImageMetaData())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
@ -63,7 +78,25 @@ namespace SixLabors.ImageSharp
this.configuration = configuration ?? Configuration.Default;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData();
this.frames = new ImageFrameCollection<TPixel>(this, width, height);
this.frames = new ImageFrameCollection<TPixel>(this, width, height, default(TPixel));
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to initialize the pixels with.</param>
/// <param name="metadata">The images metadata.</param>
internal Image(Configuration configuration, int width, int height, TPixel backgroundColor, ImageMetaData metadata) {
this.configuration = configuration ?? Configuration.Default;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData();
this.frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);
}
/// <summary>
@ -102,7 +135,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Gets the frames.
/// </summary>
public IImageFrameCollection<TPixel> Frames => this.frames;
public ImageFrameCollection<TPixel> Frames => this.frames;
/// <summary>
/// Gets the root frame.

24
src/ImageSharp/PixelFormats/PixelBlenderMode.cs

@ -54,62 +54,62 @@ namespace SixLabors.ImageSharp.PixelFormats
HardLight,
/// <summary>
/// returns the source colors
/// returns the source colors.
/// </summary>
Src,
/// <summary>
/// returns the source over the destination
/// returns the source over the destination.
/// </summary>
Atop,
/// <summary>
/// returns the detination over the source
/// returns the destination over the source.
/// </summary>
Over,
/// <summary>
/// the source where the desitnation and source overlap
/// The source where the destination and source overlap.
/// </summary>
In,
/// <summary>
/// the destination where the desitnation and source overlap
/// The destination where the destination and source overlap.
/// </summary>
Out,
/// <summary>
/// the destination where the source does not overlap it
/// The destination where the source does not overlap it.
/// </summary>
Dest,
/// <summary>
/// the source where they dont overlap othersie dest in overlapping parts
/// The source where they don't overlap othersie dest in overlapping parts.
/// </summary>
DestAtop,
/// <summary>
/// the destnation over the source
/// The destination over the source.
/// </summary>
DestOver,
/// <summary>
/// the destination where the desitnation and source overlap
/// The destination where the destination and source overlap.
/// </summary>
DestIn,
/// <summary>
/// the source where the desitnation and source overlap
/// The source where the destination and source overlap.
/// </summary>
DestOut,
/// <summary>
/// the clear.
/// The clear.
/// </summary>
Clear,
/// <summary>
/// clear where they overlap
/// Clear where they overlap.
/// </summary>
Xor
}

92
tests/ImageSharp.Tests/Drawing/BlendedShapes.cs

@ -1,92 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Drawing;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
using SixLabors.ImageSharp.Processing;
public class BlendedShapes
{
public static IEnumerable<object[]> modes = ((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode)))
.Select(x => new object[] { x });
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Mutate(x => x
.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))
.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)
));
img.DebugSave(provider, new { mode });
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues_transparent<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Mutate(x => x.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)));
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.Transparent, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
img.DebugSave(provider, new { mode });
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues_transparent50Percent<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Mutate(x => x.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY)));
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY)));
var c = NamedColors<TPixel>.Red.ToVector4();
c.W *= 0.5f;
TPixel pixel = default(TPixel);
pixel.PackFromVector4(c);
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, pixel, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
img.DebugSave(provider, new { mode });
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues_doldidEllips<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Mutate(x => x.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.Black, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
img.DebugSave(provider, new { mode });
}
}
}
}

173
tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs

@ -1,79 +1,164 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Drawing;
using SixLabors.ImageSharp.Processing.Overlays;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Drawing.Brushes;
using SixLabors.Shapes;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing
{
public class FillSolidBrushTests : FileTestBase
[GroupOutput("Drawing")]
public class FillSolidBrushTests
{
[Fact]
public void ImageShouldBeFloodFilledWithColorOnDefaultBackground()
[Theory]
[WithBlankImages(1, 1, PixelTypes.Rgba32)]
[WithBlankImages(7, 4, PixelTypes.Rgba32)]
[WithBlankImages(16, 7, PixelTypes.Rgba32)]
[WithBlankImages(33, 32, PixelTypes.Rgba32)]
[WithBlankImages(400, 500, PixelTypes.Rgba32)]
public void DoesNotDependOnSize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush");
using (var image = new Image<Rgba32>(500, 500))
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Fill(Rgba32.HotPink));
image.Save($"{path}/DefaultBack.png");
using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{
Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]);
TPixel color = NamedColors<TPixel>.HotPink;
image.Mutate(c => c.Fill(color));
Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]);
}
image.DebugSave(provider, appendPixelTypeToFileName: false);
image.ComparePixelBufferTo(color);
}
}
[Fact]
public void ImageShouldBeFloodFilledWithColor()
[Theory]
[WithBlankImages(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)]
public void DoesNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush");
using (var image = new Image<Rgba32>(500, 500))
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x
.BackgroundColor(Rgba32.Blue)
.Fill(Rgba32.HotPink));
image.Save($"{path}/Simple.png");
TPixel color = NamedColors<TPixel>.HotPink;
image.Mutate(c => c.Fill(color));
using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{
Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]);
Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]);
}
image.DebugSave(provider, appendSourceFileOrDescription: false);
image.ComparePixelBufferTo(color);
}
}
[Fact]
public void ImageShouldBeFloodFilledWithColorOpacity()
[Theory]
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")]
[WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")]
public void WhenColorIsOpaque_OverridePreviousColor<TPixel>(TestImageProvider<TPixel> provider, string newColorName)
where TPixel : struct, IPixel<TPixel>
{
string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush");
using (var image = new Image<Rgba32>(500, 500))
using (Image<TPixel> image = provider.GetImage())
{
var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150);
TPixel color = TestUtils.GetPixelOfNamedColor<TPixel>(newColorName);
image.Mutate(c => c.Fill(color));
image.DebugSave(provider, newColorName, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
image.ComparePixelBufferTo(color);
}
}
public static readonly TheoryData<bool, string, float, PixelBlenderMode, float> BlendData =
new TheoryData<bool, string, float, PixelBlenderMode, float>()
{
{ false, "Blue", 0.5f, PixelBlenderMode.Normal, 1.0f },
{ false, "Blue", 1.0f, PixelBlenderMode.Normal, 0.5f },
{ false, "Green", 0.5f, PixelBlenderMode.Normal, 0.3f },
{ false, "HotPink", 0.8f, PixelBlenderMode.Normal, 0.8f },
{ false, "Blue", 0.5f, PixelBlenderMode.Multiply, 1.0f },
{ false, "Blue", 1.0f, PixelBlenderMode.Multiply, 0.5f },
{ false, "Green", 0.5f, PixelBlenderMode.Multiply, 0.3f },
{ false, "HotPink", 0.8f, PixelBlenderMode.Multiply, 0.8f },
{ false, "Blue", 0.5f, PixelBlenderMode.Add, 1.0f },
{ false, "Blue", 1.0f, PixelBlenderMode.Add, 0.5f },
{ false, "Green", 0.5f, PixelBlenderMode.Add, 0.3f },
{ false, "HotPink", 0.8f, PixelBlenderMode.Add, 0.8f },
image.Mutate(x => x
.BackgroundColor(Rgba32.Blue)
.Fill(color));
image.Save($"{path}/Opacity.png");
{ true, "Blue", 0.5f, PixelBlenderMode.Normal, 1.0f },
{ true, "Blue", 1.0f, PixelBlenderMode.Normal, 0.5f },
{ true, "Green", 0.5f, PixelBlenderMode.Normal, 0.3f },
{ true, "HotPink", 0.8f, PixelBlenderMode.Normal, 0.8f },
//shift background color towards forground color by the opacity amount
var mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
{ true, "Blue", 0.5f, PixelBlenderMode.Multiply, 1.0f },
{ true, "Blue", 1.0f, PixelBlenderMode.Multiply, 0.5f },
{ true, "Green", 0.5f, PixelBlenderMode.Multiply, 0.3f },
{ true, "HotPink", 0.8f, PixelBlenderMode.Multiply, 0.8f },
{ true, "Blue", 0.5f, PixelBlenderMode.Add, 1.0f },
{ true, "Blue", 1.0f, PixelBlenderMode.Add, 0.5f },
{ true, "Green", 0.5f, PixelBlenderMode.Add, 0.3f },
{ true, "HotPink", 0.8f, PixelBlenderMode.Add, 0.8f },
};
using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
[Theory]
[WithSolidFilledImages(nameof(BlendData), 16, 16, "Red", PixelTypes.Rgba32)]
public void BlendFillColorOverBackround<TPixel>(
TestImageProvider<TPixel> provider,
bool triggerFillRegion,
string newColorName,
float alpha,
PixelBlenderMode blenderMode,
float blendPercentage)
where TPixel : struct, IPixel<TPixel>
{
var vec = TestUtils.GetPixelOfNamedColor<RgbaVector>(newColorName).ToVector4();
vec.W = alpha;
TPixel fillColor = default;
fillColor.PackFromVector4(vec);
using (Image<TPixel> image = provider.GetImage())
{
TPixel bgColor = image[0, 0];
var options = new GraphicsOptions(false)
{
BlenderMode = blenderMode,
BlendPercentage = blendPercentage
};
if (triggerFillRegion)
{
var region = new ShapeRegion(new RectangularPolygon(0, 0, 16, 16));
image.Mutate(c => c.Fill(options, new SolidBrush<TPixel>(fillColor), region));
}
else
{
Assert.Equal(mergedColor, sourcePixels[9, 9]);
Assert.Equal(mergedColor, sourcePixels[199, 149]);
image.Mutate(c => c.Fill(options, new SolidBrush<TPixel>(fillColor)));
}
var testOutputDetails = new
{
triggerFillRegion = triggerFillRegion,
newColorName = newColorName,
alpha = alpha,
blenderMode = blenderMode,
blendPercentage = blendPercentage
};
image.DebugSave(
provider,
testOutputDetails,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(blenderMode);
TPixel expectedPixel = blender.Blend(bgColor, fillColor, blendPercentage);
image.ComparePixelBufferTo(expectedPixel);
}
}
}
}

154
tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs

@ -0,0 +1,154 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Drawing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class SolidFillBlendedShapesTests
{
public static IEnumerable<object[]> modes =
((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))).Select(x => new object[] { x });
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect<TPixel>(
TestImageProvider<TPixel> provider,
PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = img.Width / 100;
int scaleY = img.Height / 100;
img.Mutate(
x => x.Fill(
NamedColors<TPixel>.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)
)
.Fill(new GraphicsOptions(true) { BlenderMode = mode },
NamedColors<TPixel>.HotPink,
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))
);
VerifyImage(provider, mode, img);
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse<TPixel>(
TestImageProvider<TPixel> provider,
PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = img.Width / 100;
int scaleY = img.Height / 100;
img.Mutate(
x => x.Fill(
NamedColors<TPixel>.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode },
NamedColors<TPixel>.HotPink,
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode },
NamedColors<TPixel>.Transparent,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))
);
VerifyImage(provider, mode, img);
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse<TPixel>(
TestImageProvider<TPixel> provider,
PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = (img.Width / 100);
int scaleY = (img.Height / 100);
img.Mutate(
x => x.Fill(
NamedColors<TPixel>.DarkBlue,
new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode },
NamedColors<TPixel>.HotPink,
new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY)));
var c = NamedColors<TPixel>.Red.ToVector4();
c.W *= 0.5f;
var pixel = default(TPixel);
pixel.PackFromVector4(c);
img.Mutate(
x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode },
pixel,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))
);
VerifyImage(provider, mode, img); ;
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendBlackEllipse<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = (img.Width / 100);
int scaleY = (img.Height / 100);
img.Mutate(
x => x.Fill(
NamedColors<TPixel>.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode },
NamedColors<TPixel>.Black,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
VerifyImage(provider, mode, img);
}
}
private static void VerifyImage<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode, Image<TPixel> img)
where TPixel : struct, IPixel<TPixel>
{
img.DebugSave(
provider,
new { mode },
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
var comparer = ImageComparer.TolerantPercentage(0.01f, 3);
img.CompareFirstFrameToReferenceOutput(comparer,
provider,
new { mode },
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}
}

2
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests
}
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType);
string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType, true);
using (var actualImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder))
using (var referenceImage = Image.Load<TPixel>(referenceOutputFile, referenceDecoder))

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

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
this.image = new Image<Rgba32>(10, 10);
this.collection = new ImageFrameCollection<Rgba32>(this.image, 10, 10);
this.collection = new ImageFrameCollection<Rgba32>(this.image, 10, 10, default);
}
[Fact]
@ -225,10 +225,21 @@ namespace SixLabors.ImageSharp.Tests
}
[Fact]
public void CreateFrame()
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]

60
tests/ImageSharp.Tests/Image/ImageTests.cs

@ -13,10 +13,60 @@ namespace SixLabors.ImageSharp.Tests
/// <summary>
/// Tests the <see cref="Image"/> class.
/// </summary>
public class ImageTests : FileTestBase
public class ImageTests
{
public class Constructor
{
[Fact]
public void Width_Height()
{
using (var image = new Image<Rgba32>(11, 23))
{
Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height);
Assert.Equal(11*23, image.GetPixelSpan().Length);
image.ComparePixelBufferTo(default(Rgba32));
Assert.Equal(Configuration.Default, image.GetConfiguration());
}
}
[Fact]
public void Configuration_Width_Height()
{
Configuration configuration = Configuration.Default.ShallowCopy();
using (var image = new Image<Rgba32>(configuration, 11, 23))
{
Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height);
Assert.Equal(11 * 23, image.GetPixelSpan().Length);
image.ComparePixelBufferTo(default(Rgba32));
Assert.Equal(configuration, image.GetConfiguration());
}
}
[Fact]
public void Configuration_Width_Height_BackroundColor()
{
Configuration configuration = Configuration.Default.ShallowCopy();
Rgba32 color = Rgba32.Aquamarine;
using (var image = new Image<Rgba32>(configuration, 11, 23, color))
{
Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height);
Assert.Equal(11 * 23, image.GetPixelSpan().Length);
image.ComparePixelBufferTo(color);
Assert.Equal(configuration, image.GetConfiguration());
}
}
}
[Fact]
public void ConstructorByteArray()
public void Load_ByteArray()
{
Assert.Throws<ArgumentNullException>(() =>
{
@ -32,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests
}
[Fact]
public void ConstructorFileSystem()
public void Load_FileSystemPath()
{
TestFile file = TestFile.Create(TestImages.Bmp.Car);
using (Image<Rgba32> image = Image.Load<Rgba32>(file.FullPath))
@ -43,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests
}
[Fact]
public void ConstructorFileSystem_FileNotFound()
public void Load_FileSystemPath_FileNotFound()
{
System.IO.FileNotFoundException ex = Assert.Throws<System.IO.FileNotFoundException>(
() =>
@ -53,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests
}
[Fact]
public void ConstructorFileSystem_NullPath()
public void Load_FileSystemPath_NullPath()
{
ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
() =>

2
tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs

@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests
{
Guard.NotNull(colorName, nameof(colorName));
var c = (Rgba32)typeof(Rgba32).GetTypeInfo().GetField(colorName).GetValue(null);
Rgba32 c = TestUtils.GetPixelOfNamedColor<Rgba32>(colorName);
this.R = c.R;
this.G = c.G;
this.B = c.B;

52
tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

@ -41,16 +41,20 @@ namespace SixLabors.ImageSharp.Tests
/// </summary>
public string TestName { get; set; } = string.Empty;
private string GetTestOutputFileNameImpl(string extension, string details, bool appendPixelTypeToFileName)
private string GetTestOutputFileNameImpl(
string extension,
string details,
bool appendPixelTypeToFileName,
bool appendSourceFileOrDescription)
{
string fn = string.Empty;
if (string.IsNullOrWhiteSpace(extension))
{
extension = null;
}
fn = Path.GetFileNameWithoutExtension(this.SourceFileOrDescription);
string fn = appendSourceFileOrDescription
? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription)
: "";
if (string.IsNullOrWhiteSpace(extension))
{
@ -92,20 +96,24 @@ namespace SixLabors.ImageSharp.Tests
}
private static string Inv(FormattableString formattable) => System.FormattableString.Invariant(formattable);
/// <summary>
/// Gets the recommended file name for the output of the test
/// </summary>
/// <param name="extension">The required extension</param>
/// <param name="testOutputDetails">The settings modifying the output path</param>
/// <param name="appendPixelTypeToFileName">A boolean indicating whether to append the pixel type to output file name.</param>
/// <param name="appendSourceFileOrDescription">A boolean indicating whether to append <see cref="ITestImageProvider.SourceFileOrDescription"/> to the test output file name.</param>
/// <returns>The file test name</returns>
public string GetTestOutputFileName(string extension = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true)
public string GetTestOutputFileName(
string extension = null,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
{
string detailsString = null;
string s = testOutputDetails as string;
if (s != null)
if (testOutputDetails is string s)
{
detailsString = s;
}
@ -128,7 +136,12 @@ namespace SixLabors.ImageSharp.Tests
);
}
}
return this.GetTestOutputFileNameImpl(extension, detailsString, appendPixelTypeToFileName);
return this.GetTestOutputFileNameImpl(
extension,
detailsString,
appendPixelTypeToFileName,
appendSourceFileOrDescription);
}
@ -139,15 +152,22 @@ namespace SixLabors.ImageSharp.Tests
/// <param name="image">The image instance</param>
/// <param name="extension">The requested extension</param>
/// <param name="encoder">Optional encoder</param>
/// /// <param name="appendSourceFileOrDescription">A boolean indicating whether to append <see cref="ITestImageProvider.SourceFileOrDescription"/> to the test output file name.</param>
public string SaveTestOutputFile<TPixel>(
Image<TPixel> image,
string extension = null,
IImageEncoder encoder = null,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true)
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel<TPixel>
{
string path = this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName);
string path = this.GetTestOutputFileName(
extension,
testOutputDetails,
appendPixelTypeToFileName,
appendSourceFileOrDescription);
encoder = encoder ?? TestEnvironment.GetReferenceEncoder(path);
using (FileStream stream = File.OpenWrite(path))
@ -161,9 +181,10 @@ namespace SixLabors.ImageSharp.Tests
int frameCount,
string extension = null,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true)
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
{
string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName);
string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription);
if (!Directory.Exists(baseDir))
{
@ -211,10 +232,11 @@ namespace SixLabors.ImageSharp.Tests
internal string GetReferenceOutputFileName(
string extension,
object testOutputDetails,
bool appendPixelTypeToFileName)
bool appendPixelTypeToFileName,
bool appendSourceFileOrDescription)
{
return TestEnvironment.GetReferenceOutputFileName(
this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName)
this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)
);
}

52
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -61,12 +61,14 @@ namespace SixLabors.ImageSharp.Tests
/// <param name="testOutputDetails">Details to be concatenated to the test output file, describing the parameters of the test.</param>
/// <param name="extension">The extension</param>
/// <param name="appendPixelTypeToFileName">A boolean indicating whether to append the pixel type to the output file name.</param>
/// <param name="appendSourceFileOrDescription">A boolean indicating whether to append <see cref="ITestImageProvider.SourceFileOrDescription"/> to the test output file name.</param>
public static Image<TPixel> DebugSave<TPixel>(
this Image<TPixel> image,
ITestImageProvider provider,
object testOutputDetails = null,
string extension = "png",
bool appendPixelTypeToFileName = true)
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel<TPixel>
{
if (TestEnvironment.RunsOnCI)
@ -79,7 +81,8 @@ namespace SixLabors.ImageSharp.Tests
image,
extension,
testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: appendPixelTypeToFileName);
appendPixelTypeToFileName: appendPixelTypeToFileName,
appendSourceFileOrDescription: appendSourceFileOrDescription);
return image;
}
@ -210,7 +213,8 @@ namespace SixLabors.ImageSharp.Tests
object testOutputDetails = null,
string extension = "png",
bool grayscale = false,
bool appendPixelTypeToFileName = true)
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel<TPixel>
{
using (var firstFrameOnlyImage = new Image<TPixel>(image.Width, image.Height))
@ -218,7 +222,8 @@ namespace SixLabors.ImageSharp.Tests
provider,
testOutputDetails,
extension,
appendPixelTypeToFileName))
appendPixelTypeToFileName,
appendSourceFileOrDescription))
{
firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame);
firstFrameOnlyImage.Frames.RemoveFrame(0);
@ -255,10 +260,15 @@ namespace SixLabors.ImageSharp.Tests
public static Image<TPixel> GetReferenceOutputImage<TPixel>(this ITestImageProvider provider,
object testOutputDetails = null,
string extension = "png",
bool appendPixelTypeToFileName = true)
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel<TPixel>
{
string referenceOutputFile = provider.Utility.GetReferenceOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName);
string referenceOutputFile = provider.Utility.GetReferenceOutputFileName(
extension,
testOutputDetails,
appendPixelTypeToFileName,
appendSourceFileOrDescription);
if (!File.Exists(referenceOutputFile))
{
@ -336,18 +346,42 @@ namespace SixLabors.ImageSharp.Tests
Span<TPixel> expectedPixels)
where TPixel : struct, IPixel<TPixel>
{
Span<TPixel> actual = image.GetPixelSpan();
Span<TPixel> actualPixels = image.GetPixelSpan();
Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!");
Assert.True(expectedPixels.Length == actualPixels.Length, "Buffer sizes are not equal!");
for (int i = 0; i < expectedPixels.Length; i++)
{
Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!");
Assert.True(expectedPixels[i].Equals(actualPixels[i]), $"Pixels are different on position {i}!");
}
return image;
}
public static Image<TPixel> ComparePixelBufferTo<TPixel>(this Image<TPixel> image, TPixel expectedPixel)
where TPixel : struct, IPixel<TPixel>
{
foreach (ImageFrame<TPixel> imageFrame in image.Frames)
{
imageFrame.ComparePixelBufferTo(expectedPixel);
}
return image;
}
public static ImageFrame<TPixel> ComparePixelBufferTo<TPixel>(this ImageFrame<TPixel> imageFrame, TPixel expectedPixel)
where TPixel : struct, IPixel<TPixel>
{
Span<TPixel> actualPixels = imageFrame.GetPixelSpan();
for (int i = 0; i < actualPixels.Length; i++)
{
Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!");
}
return imageFrame;
}
public static ImageFrame<TPixel> ComparePixelBufferTo<TPixel>(
this ImageFrame<TPixel> image,
Span<TPixel> expectedPixels)

5
tests/ImageSharp.Tests/TestUtilities/TestUtils.cs

@ -147,6 +147,11 @@ namespace SixLabors.ImageSharp.Tests
/// <returns>The pixel types</returns>
internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes));
internal static TPixel GetPixelOfNamedColor<TPixel>(string colorName)
where TPixel : struct, IPixel<TPixel>
{
return (TPixel)typeof(NamedColors<TPixel>).GetTypeInfo().GetField(colorName).GetValue(null);
}
/// <summary>
/// Utility for testing image processor extension methods:

Loading…
Cancel
Save