Browse Source

Merge pull request #552 from woutware/Issue-551-solidbrush-blend-performance

Issue 551 solidbrush blend performance
pull/561/head
James Jackson-South 8 years ago
committed by GitHub
parent
commit
6b4de9780b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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. 15
      tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs
  9. 60
      tests/ImageSharp.Tests/Image/ImageTests.cs
  10. 15
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.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; MemoryManager memoryManager = this.Target.MemoryManager;
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length)) if (this.Options.BlendPercentage == 1f)
{ {
Span<float> amountSpan = amountBuffer.Span; this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, scanline);
}
for (int i = 0; i < scanline.Length; i++) 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;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Drawing.Brushes; using SixLabors.ImageSharp.Processing.Drawing.Brushes;
@ -49,39 +50,66 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors
int minY = Math.Max(0, startY); int minY = Math.Max(0, startY);
int maxY = Math.Min(source.Height, endY); 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; int width = maxX - minX;
using (IBuffer<float> amount = source.MemoryManager.Allocate<float>(width)) // If there's no reason for blending, then avoid it.
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator( if (this.IsSolidBrushWithoutBlending(out SolidBrush<TPixel> solidBrush))
source,
sourceRectangle,
this.options))
{ {
amount.Span.Fill(1f);
Parallel.For( Parallel.For(
minY, minY,
maxY, maxY,
configuration.ParallelOptions, configuration.ParallelOptions,
y => y =>
{ {
int offsetY = y - startY; source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color);
int offsetX = minX - startX; });
}
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 namespace SixLabors.ImageSharp
{ {
/// <inheritdoc/> /// <summary>
internal sealed class ImageFrameCollection<TPixel> : IImageFrameCollection<TPixel> /// 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> where TPixel : struct, IPixel<TPixel>
{ {
private readonly IList<ImageFrame<TPixel>> frames = new List<ImageFrame<TPixel>>(); private readonly IList<ImageFrame<TPixel>> frames = new List<ImageFrame<TPixel>>();
private readonly Image<TPixel> parent; 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)); Guard.NotNull(parent, nameof(parent));
this.parent = parent; this.parent = parent;
// Frames are already cloned within the caller // 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) 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; 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; 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]; 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); public int IndexOf(ImageFrame<TPixel> frame) => this.frames.IndexOf(frame);
/// <inheritdoc/> /// <summary>
public ImageFrame<TPixel> InsertFrame(int index, ImageFrame<TPixel> frame) /// 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); this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = frame.Clone(); ImageFrame<TPixel> clonedFrame = source.Clone();
this.frames.Insert(index, clonedFrame); this.frames.Insert(index, clonedFrame);
return clonedFrame; return clonedFrame;
} }
/// <inheritdoc/> /// <summary>
public ImageFrame<TPixel> AddFrame(ImageFrame<TPixel> frame) /// 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); this.ValidateFrame(source);
ImageFrame<TPixel> clonedFrame = frame.Clone(); ImageFrame<TPixel> clonedFrame = source.Clone();
this.frames.Add(clonedFrame); this.frames.Add(clonedFrame);
return clonedFrame; return clonedFrame;
} }
/// <inheritdoc/> /// <summary>
public ImageFrame<TPixel> AddFrame(TPixel[] data) /// 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( var frame = ImageFrame.LoadPixelData(
this.parent.GetMemoryManager(), this.parent.GetMemoryManager(),
new Span<TPixel>(data), new Span<TPixel>(source),
this.RootFrame.Width, this.RootFrame.Width,
this.RootFrame.Height); this.RootFrame.Height);
this.frames.Add(frame); this.frames.Add(frame);
return 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) public void RemoveFrame(int index)
{ {
if (index == 0 && this.Count == 1) if (index == 0 && this.Count == 1)
@ -98,26 +135,42 @@ namespace SixLabors.ImageSharp
frame.Dispose(); 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) public bool Contains(ImageFrame<TPixel> frame)
{ {
return this.frames.Contains(frame); return this.frames.Contains(frame);
} }
/// <inheritdoc/> /// <summary>
public void MoveFrame(int sourceIndex, int destIndex) /// 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; return;
} }
ImageFrame<TPixel> frameAtIndex = this.frames[sourceIndex]; ImageFrame<TPixel> frameAtIndex = this.frames[sourceIndex];
this.frames.RemoveAt(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) public Image<TPixel> ExportFrame(int index)
{ {
ImageFrame<TPixel> frame = this[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 }); 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) public Image<TPixel> CloneFrame(int index)
{ {
ImageFrame<TPixel> frame = this[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 }); 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() 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); this.frames.Add(frame);
return frame; return frame;
} }

59
src/ImageSharp/ImageFrame{TPixel}.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -18,8 +19,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class ImageFrame<TPixel> : IPixelSource<TPixel>, IDisposable public sealed class ImageFrame<TPixel> : IPixelSource<TPixel>, IDisposable
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel> {
{
private bool isDisposed; private bool isDisposed;
/// <summary> /// <summary>
@ -29,8 +29,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the image in pixels.</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="height">The height of the image in pixels.</param>
internal ImageFrame(MemoryManager memoryManager, int width, int height) internal ImageFrame(MemoryManager memoryManager, int width, int height)
: this(memoryManager, width, height, new ImageFrameMetaData()) : this(memoryManager, width, height, new ImageFrameMetaData()) {
{
} }
/// <summary> /// <summary>
@ -52,6 +51,38 @@ namespace SixLabors.ImageSharp
this.MetaData = metaData; 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> /// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class. /// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class.
/// </summary> /// </summary>
@ -59,8 +90,7 @@ namespace SixLabors.ImageSharp
/// <param name="size">The <see cref="Size"/> of the frame.</param> /// <param name="size">The <see cref="Size"/> of the frame.</param>
/// <param name="metaData">The meta data.</param> /// <param name="metaData">The meta data.</param>
internal ImageFrame(MemoryManager memoryManager, Size size, ImageFrameMetaData metaData) internal ImageFrame(MemoryManager memoryManager, Size size, ImageFrameMetaData metaData)
: this(memoryManager, size.Width, size.Height, metaData) : this(memoryManager, size.Width, size.Height, metaData) {
{
} }
/// <summary> /// <summary>
@ -267,6 +297,23 @@ namespace SixLabors.ImageSharp
return target; 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> /// <summary>
/// Clones the current instance. /// Clones the current instance.
/// </summary> /// </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> /// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class /// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image. /// with the height and the width of the image.
@ -63,7 +78,25 @@ namespace SixLabors.ImageSharp
this.configuration = configuration ?? Configuration.Default; this.configuration = configuration ?? Configuration.Default;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8); this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata ?? new ImageMetaData(); 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> /// <summary>
@ -102,7 +135,7 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Gets the frames. /// Gets the frames.
/// </summary> /// </summary>
public IImageFrameCollection<TPixel> Frames => this.frames; public ImageFrameCollection<TPixel> Frames => this.frames;
/// <summary> /// <summary>
/// Gets the root frame. /// Gets the root frame.

24
src/ImageSharp/PixelFormats/PixelBlenderMode.cs

@ -54,62 +54,62 @@ namespace SixLabors.ImageSharp.PixelFormats
HardLight, HardLight,
/// <summary> /// <summary>
/// returns the source colors /// returns the source colors.
/// </summary> /// </summary>
Src, Src,
/// <summary> /// <summary>
/// returns the source over the destination /// returns the source over the destination.
/// </summary> /// </summary>
Atop, Atop,
/// <summary> /// <summary>
/// returns the detination over the source /// returns the destination over the source.
/// </summary> /// </summary>
Over, Over,
/// <summary> /// <summary>
/// the source where the desitnation and source overlap /// The source where the destination and source overlap.
/// </summary> /// </summary>
In, In,
/// <summary> /// <summary>
/// the destination where the desitnation and source overlap /// The destination where the destination and source overlap.
/// </summary> /// </summary>
Out, Out,
/// <summary> /// <summary>
/// the destination where the source does not overlap it /// The destination where the source does not overlap it.
/// </summary> /// </summary>
Dest, Dest,
/// <summary> /// <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> /// </summary>
DestAtop, DestAtop,
/// <summary> /// <summary>
/// the destnation over the source /// The destination over the source.
/// </summary> /// </summary>
DestOver, DestOver,
/// <summary> /// <summary>
/// the destination where the desitnation and source overlap /// The destination where the destination and source overlap.
/// </summary> /// </summary>
DestIn, DestIn,
/// <summary> /// <summary>
/// the source where the desitnation and source overlap /// The source where the destination and source overlap.
/// </summary> /// </summary>
DestOut, DestOut,
/// <summary> /// <summary>
/// the clear. /// The clear.
/// </summary> /// </summary>
Clear, Clear,
/// <summary> /// <summary>
/// clear where they overlap /// Clear where they overlap.
/// </summary> /// </summary>
Xor Xor
} }

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"); System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
this.image = new Image<Rgba32>(10, 10); 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] [Fact]
@ -225,10 +225,21 @@ namespace SixLabors.ImageSharp.Tests
} }
[Fact] [Fact]
public void CreateFrame() public void CreateFrame_Default()
{ {
this.image.Frames.CreateFrame(); 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); Assert.Equal(2, this.image.Frames.Count);
this.image.Frames[1].ComparePixelBufferTo(Rgba32.HotPink);
} }
[Fact] [Fact]

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

@ -13,10 +13,60 @@ namespace SixLabors.ImageSharp.Tests
/// <summary> /// <summary>
/// Tests the <see cref="Image"/> class. /// Tests the <see cref="Image"/> class.
/// </summary> /// </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] [Fact]
public void ConstructorByteArray() public void Load_ByteArray()
{ {
Assert.Throws<ArgumentNullException>(() => Assert.Throws<ArgumentNullException>(() =>
{ {
@ -32,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests
} }
[Fact] [Fact]
public void ConstructorFileSystem() public void Load_FileSystemPath()
{ {
TestFile file = TestFile.Create(TestImages.Bmp.Car); TestFile file = TestFile.Create(TestImages.Bmp.Car);
using (Image<Rgba32> image = Image.Load<Rgba32>(file.FullPath)) using (Image<Rgba32> image = Image.Load<Rgba32>(file.FullPath))
@ -43,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests
} }
[Fact] [Fact]
public void ConstructorFileSystem_FileNotFound() public void Load_FileSystemPath_FileNotFound()
{ {
System.IO.FileNotFoundException ex = Assert.Throws<System.IO.FileNotFoundException>( System.IO.FileNotFoundException ex = Assert.Throws<System.IO.FileNotFoundException>(
() => () =>
@ -53,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests
} }
[Fact] [Fact]
public void ConstructorFileSystem_NullPath() public void Load_FileSystemPath_NullPath()
{ {
ArgumentNullException ex = Assert.Throws<ArgumentNullException>( ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
() => () =>

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

@ -361,14 +361,25 @@ namespace SixLabors.ImageSharp.Tests
public static Image<TPixel> ComparePixelBufferTo<TPixel>(this Image<TPixel> image, TPixel expectedPixel) public static Image<TPixel> ComparePixelBufferTo<TPixel>(this Image<TPixel> image, TPixel expectedPixel)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Span<TPixel> actualPixels = image.GetPixelSpan(); 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++) for (int i = 0; i < actualPixels.Length; i++)
{ {
Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!"); Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!");
} }
return image; return imageFrame;
} }
public static ImageFrame<TPixel> ComparePixelBufferTo<TPixel>( public static ImageFrame<TPixel> ComparePixelBufferTo<TPixel>(

Loading…
Cancel
Save