Browse Source

Merge pull request #329 from SixLabors/advanced-pixel-reference

Expose advanced pixel reference API.
pull/334/head
Scott Williams 9 years ago
committed by GitHub
parent
commit
e3664e35db
  1. 46
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  2. 4
      src/ImageSharp/Common/Extensions/SimdUtils.cs
  3. 52
      src/ImageSharp/Image/ImageExtensions.cs
  4. 2
      src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs
  5. 38
      tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs
  6. 30
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  7. 80
      tests/ImageSharp.Tests/Image/ImageSaveTests.cs
  8. 24
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

46
src/ImageSharp/Advanced/ImageExtensions.cs → src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -10,15 +10,39 @@ namespace SixLabors.ImageSharp.Advanced
/// <summary> /// <summary>
/// Extension methods over Image{TPixel} /// Extension methods over Image{TPixel}
/// </summary> /// </summary>
internal static class ImageExtensions public static class AdvancedImageExtensions
{ {
/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer,
/// allowing direct manipulation of pixel data through unsafe operations.
/// The pixel buffer is a contigous memory area containing Width*Height TPixel elements layed out in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image frame</param>
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource<TPixel>)source);
/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer,
/// allowing direct manipulation of pixel data through unsafe operations.
/// The pixel buffer is a contigous memory area containing Width*Height TPixel elements layed out in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer();
/// <summary> /// <summary>
/// Gets the representation of the pixels as an area of contiguous memory in the given pixel format. /// Gets the representation of the pixels as an area of contiguous memory in the given pixel format.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns> /// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source) internal static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> GetSpan(source); => GetSpan(source);
@ -29,7 +53,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <param name="row">The row.</param> /// <param name="row">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns> /// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int row) internal static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int row)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> GetSpan(source, row); => GetSpan(source, row);
@ -39,7 +63,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns> /// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source) internal static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelSpan(); => source.Frames.RootFrame.GetPixelSpan();
@ -50,7 +74,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <param name="row">The row.</param> /// <param name="row">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns> /// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int row) internal static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int row)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelRowSpan(row); => source.Frames.RootFrame.GetPixelRowSpan(row);
@ -60,7 +84,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
/// <returns>Returns the configuration.</returns> /// <returns>Returns the configuration.</returns>
public static Configuration GetConfiguration<TPixel>(this Image<TPixel> source) internal static Configuration GetConfiguration<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> GetConfiguration((IConfigurable)source); => GetConfiguration((IConfigurable)source);
@ -107,5 +131,15 @@ namespace SixLabors.ImageSharp.Advanced
/// <returns>Returns the bounds of the image</returns> /// <returns>Returns the bounds of the image</returns>
private static Configuration GetConfiguration(IConfigurable source) private static Configuration GetConfiguration(IConfigurable source)
=> source?.Configuration ?? Configuration.Default; => source?.Configuration ?? Configuration.Default;
/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer.
/// Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
/// <param name="source">The source image frame</param>
/// <returns>A reference to the element.</returns>
private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(IPixelSource<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref source.PixelBuffer.Span.DangerousGetPinnableReference();
} }
} }

4
src/ImageSharp/Common/Extensions/SimdUtils.cs

@ -17,11 +17,11 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Indicates AVX2 architecture where both float and integer registers are of size 256 byte. /// Indicates AVX2 architecture where both float and integer registers are of size 256 byte.
/// </summary> /// </summary>
public static readonly bool IsAvx2 = Vector<float>.Count == 8 && Vector<int>.Count == 8; public static readonly bool IsAvx2CompatibleArchitecture = Vector<float>.Count == 8 && Vector<int>.Count == 8;
internal static void GuardAvx2(string operation) internal static void GuardAvx2(string operation)
{ {
if (!IsAvx2) if (!IsAvx2CompatibleArchitecture)
{ {
throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!"); throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!");
} }

52
src/ImageSharp/Image/ImageExtensions.cs

@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp
} }
/// <summary> /// <summary>
/// Saves the raw image to the given bytes. /// Saves the raw image pixels to a byte array in row-major order.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp
=> source.GetPixelSpan().AsBytes().ToArray(); => source.GetPixelSpan().AsBytes().ToArray();
/// <summary> /// <summary>
/// Saves the raw image to the given bytes. /// Saves the raw image pixels to the given byte array in row-major order.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
@ -131,26 +131,21 @@ namespace SixLabors.ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, byte[] buffer) public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, byte[] buffer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> SavePixelData(source, new Span<byte>(buffer)); => SavePixelData(source, buffer.AsSpan().NonPortableCast<byte, TPixel>());
/// <summary> /// <summary>
/// Saves the raw image to the given bytes. /// Saves the raw image pixels to the given TPixel array in row-major order.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param> /// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
private static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, Span<byte> buffer) public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, TPixel[] buffer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ => SavePixelData(source, new Span<TPixel>(buffer));
Span<byte> byteBuffer = source.GetPixelSpan().AsBytes();
Guard.MustBeGreaterThanOrEqualTo(buffer.Length, byteBuffer.Length, nameof(buffer));
byteBuffer.CopyTo(buffer);
}
/// <summary> /// <summary>
/// Saves the raw image to the given bytes. /// Saves the raw image pixels to a byte array in row-major order.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
@ -161,7 +156,7 @@ namespace SixLabors.ImageSharp
=> source.Frames.RootFrame.SavePixelData(); => source.Frames.RootFrame.SavePixelData();
/// <summary> /// <summary>
/// Saves the raw image to the given bytes. /// Saves the raw image pixels to the given byte array in row-major order.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
@ -172,13 +167,13 @@ namespace SixLabors.ImageSharp
=> source.Frames.RootFrame.SavePixelData(buffer); => source.Frames.RootFrame.SavePixelData(buffer);
/// <summary> /// <summary>
/// Saves the raw image to the given bytes. /// Saves the raw image pixels to the given TPixel array in row-major order.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam> /// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param> /// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
private static void SavePixelData<TPixel>(this Image<TPixel> source, Span<byte> buffer) public static void SavePixelData<TPixel>(this Image<TPixel> source, TPixel[] buffer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(buffer); => source.Frames.RootFrame.SavePixelData(buffer);
@ -200,5 +195,32 @@ namespace SixLabors.ImageSharp
return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}";
} }
} }
/// <summary>
/// Saves the raw image to the given bytes.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
internal static void SavePixelData<TPixel>(this Image<TPixel> source, Span<byte> buffer)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(buffer.NonPortableCast<byte, TPixel>());
/// <summary>
/// Saves the raw image to the given bytes.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="buffer">The buffer to save the raw pixel data to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
internal static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, Span<TPixel> buffer)
where TPixel : struct, IPixel<TPixel>
{
Span<TPixel> sourceBuffer = source.GetPixelSpan();
Guard.MustBeGreaterThanOrEqualTo(buffer.Length, sourceBuffer.Length, nameof(buffer));
sourceBuffer.CopyTo(buffer);
}
} }
} }

2
src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs

@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp
{ {
GuardSpans(sourceVectors, nameof(sourceVectors), destColors, nameof(destColors), count); GuardSpans(sourceVectors, nameof(sourceVectors), destColors, nameof(destColors), count);
if (!SimdUtils.IsAvx2) if (!SimdUtils.IsAvx2CompatibleArchitecture)
{ {
base.PackFromVector4(sourceVectors, destColors, count); base.PackFromVector4(sourceVectors, destColors, count);
return; return;

38
tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs

@ -0,0 +1,38 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using Xunit;
using SixLabors.ImageSharp.Advanced;
using System.Runtime.CompilerServices;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Advanced
{
using SixLabors.ImageSharp.PixelFormats;
public class AdvancedImageExtensionsTests
{
[Theory]
[WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public unsafe void DangerousGetPinnableReference_CopyToBuffer<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
TPixel[] targetBuffer = new TPixel[image.Width * image.Height];
ref byte source = ref Unsafe.As<TPixel, byte>(ref targetBuffer[0]);
ref byte dest = ref Unsafe.As<TPixel, byte>(ref image.DangerousGetPinnableReferenceToPixelBuffer());
fixed (byte* targetPtr = &source)
fixed (byte* pixelBasePtr = &dest)
{
uint dataSizeInBytes = (uint)(image.Width * image.Height * Unsafe.SizeOf<TPixel>());
Unsafe.CopyBlock(targetPtr, pixelBasePtr, dataSizeInBytes);
}
image.ComparePixelBufferTo(targetBuffer);
}
}
}
}

30
tests/ImageSharp.Tests/Common/SimdUtilsTests.cs

@ -109,6 +109,16 @@ namespace SixLabors.ImageSharp.Tests.Common
AssertEvenRoundIsCorrect(r, v); AssertEvenRoundIsCorrect(r, v);
} }
private bool SkipOnNonAvx2([CallerMemberName] string testCaseName = null)
{
if (!SimdUtils.IsAvx2CompatibleArchitecture)
{
this.Output.WriteLine("Skipping AVX2 specific test case: " + testCaseName);
return true;
}
return false;
}
[Theory] [Theory]
[InlineData(1, 0)] [InlineData(1, 0)]
[InlineData(1, 8)] [InlineData(1, 8)]
@ -116,6 +126,11 @@ namespace SixLabors.ImageSharp.Tests.Common
[InlineData(3, 128)] [InlineData(3, 128)]
public void BulkConvertNormalizedFloatToByte_WithRoundedData(int seed, int count) public void BulkConvertNormalizedFloatToByte_WithRoundedData(int seed, int count)
{ {
if (this.SkipOnNonAvx2())
{
return;
}
float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, 0, 256); float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, 0, 256);
float[] normalized = orig.Select(f => f / 255f).ToArray(); float[] normalized = orig.Select(f => f / 255f).ToArray();
@ -135,6 +150,11 @@ namespace SixLabors.ImageSharp.Tests.Common
[InlineData(3, 128)] [InlineData(3, 128)]
public void BulkConvertNormalizedFloatToByte_WithNonRoundedData(int seed, int count) public void BulkConvertNormalizedFloatToByte_WithNonRoundedData(int seed, int count)
{ {
if (this.SkipOnNonAvx2())
{
return;
}
float[] source = new Random(seed).GenerateRandomFloatArray(count, 0, 1f); float[] source = new Random(seed).GenerateRandomFloatArray(count, 0, 1f);
byte[] dest = new byte[count]; byte[] dest = new byte[count];
@ -155,6 +175,11 @@ namespace SixLabors.ImageSharp.Tests.Common
[InlineData(3, 128)] [InlineData(3, 128)]
public void BulkConvertNormalizedFloatToByteClampOverflows(int seed, int count) public void BulkConvertNormalizedFloatToByteClampOverflows(int seed, int count)
{ {
if (this.SkipOnNonAvx2())
{
return;
}
float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, -50, 444); float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, -50, 444);
float[] normalized = orig.Select(f => f / 255f).ToArray(); float[] normalized = orig.Select(f => f / 255f).ToArray();
@ -185,6 +210,11 @@ namespace SixLabors.ImageSharp.Tests.Common
[Fact] [Fact]
private void BulkConvertNormalizedFloatToByte_Step() private void BulkConvertNormalizedFloatToByte_Step()
{ {
if (this.SkipOnNonAvx2())
{
return;
}
float[] source = {0, 7, 42, 255, 0.5f, 1.1f, 2.6f, 16f}; float[] source = {0, 7, 42, 255, 0.5f, 1.1f, 2.6f, 16f};
byte[] expected = source.Select(f => (byte)Math.Round(f)).ToArray(); byte[] expected = source.Select(f => (byte)Math.Round(f)).ToArray();

80
tests/ImageSharp.Tests/Image/ImageSaveTests.cs

@ -9,9 +9,12 @@ using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Moq; using Moq;
using Xunit; using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests
{ {
using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Tests the <see cref="Image"/> class. /// Tests the <see cref="Image"/> class.
/// </summary> /// </summary>
@ -47,76 +50,39 @@ namespace SixLabors.ImageSharp.Tests
this.Image = new Image<Rgba32>(config, 1, 1); this.Image = new Image<Rgba32>(config, 1, 1);
} }
[Fact] [Theory]
public void SavePixelData_Rgba32() [WithTestPatternImages(13, 19, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public void SavePixelData_ToPixelStructArray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{ {
using (var img = new Image<Rgba32>(2, 2)) using (Image<TPixel> image = provider.GetImage())
{ {
img[0, 0] = Rgba32.White; TPixel[] buffer = new TPixel[image.Width*image.Height];
img[1, 0] = Rgba32.Black; image.SavePixelData(buffer);
img[0, 1] = Rgba32.Red; image.ComparePixelBufferTo(buffer);
img[1, 1] = Rgba32.Blue;
var buffer = new byte[2 * 2 * 4]; // width * height * bytes per pixel // TODO: We need a separate test-case somewhere ensuring that image pixels are stored in row-major order!
img.SavePixelData(buffer);
Assert.Equal(255, buffer[0]); // 0, 0, R
Assert.Equal(255, buffer[1]); // 0, 0, G
Assert.Equal(255, buffer[2]); // 0, 0, B
Assert.Equal(255, buffer[3]); // 0, 0, A
Assert.Equal(0, buffer[4]); // 1, 0, R
Assert.Equal(0, buffer[5]); // 1, 0, G
Assert.Equal(0, buffer[6]); // 1, 0, B
Assert.Equal(255, buffer[7]); // 1, 0, A
Assert.Equal(255, buffer[8]); // 0, 1, R
Assert.Equal(0, buffer[9]); // 0, 1, G
Assert.Equal(0, buffer[10]); // 0, 1, B
Assert.Equal(255, buffer[11]); // 0, 1, A
Assert.Equal(0, buffer[12]); // 1, 1, R
Assert.Equal(0, buffer[13]); // 1, 1, G
Assert.Equal(255, buffer[14]); // 1, 1, B
Assert.Equal(255, buffer[15]); // 1, 1, A
} }
} }
[Theory]
[Fact] [WithTestPatternImages(19, 13, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public void SavePixelData_Bgr24() public void SavePixelData_ToByteArray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{ {
using (var img = new Image<Bgr24>(2, 2)) using (Image<TPixel> image = provider.GetImage())
{ {
img[0, 0] = NamedColors<Bgr24>.White; byte[] buffer = new byte[image.Width*image.Height*Unsafe.SizeOf<TPixel>()];
img[1, 0] = NamedColors<Bgr24>.Black;
img[0, 1] = NamedColors<Bgr24>.Red;
img[1, 1] = NamedColors<Bgr24>.Blue;
var buffer = new byte[2 * 2 * 3]; // width * height * bytes per pixel
img.SavePixelData(buffer);
Assert.Equal(255, buffer[0]); // 0, 0, B image.SavePixelData(buffer);
Assert.Equal(255, buffer[1]); // 0, 0, G
Assert.Equal(255, buffer[2]); // 0, 0, R
Assert.Equal(0, buffer[3]); // 1, 0, B image.ComparePixelBufferTo(buffer.AsSpan().NonPortableCast<byte, TPixel>());
Assert.Equal(0, buffer[4]); // 1, 0, G
Assert.Equal(0, buffer[5]); // 1, 0, R
Assert.Equal(0, buffer[6]); // 0, 1, B
Assert.Equal(0, buffer[7]); // 0, 1, G
Assert.Equal(255, buffer[8]); // 0, 1, R
Assert.Equal(255, buffer[9]); // 1, 1, B
Assert.Equal(0, buffer[10]); // 1, 1, G
Assert.Equal(0, buffer[11]); // 1, 1, R
} }
} }
[Fact] [Fact]
public void SavePixelData_Rgba32_Buffer_must_be_bigger() public void SavePixelData_Rgba32_WhenBufferIsTooSmall_Throws()
{ {
using (var img = new Image<Rgba32>(2, 2)) using (var img = new Image<Rgba32>(2, 2))
{ {

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

@ -17,6 +17,8 @@ namespace SixLabors.ImageSharp.Tests
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using Xunit;
public static class TestImageExtensions public static class TestImageExtensions
{ {
/// <summary> /// <summary>
@ -81,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests
grayscale, grayscale,
appendPixelTypeToFileName); appendPixelTypeToFileName);
} }
/// <summary> /// <summary>
/// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough.
/// The output file should be named identically to the output produced by <see cref="DebugSave{TPixel}(Image{TPixel}, ITestImageProvider, object, string, bool)"/>. /// The output file should be named identically to the output produced by <see cref="DebugSave{TPixel}(Image{TPixel}, ITestImageProvider, object, string, bool)"/>.
@ -153,6 +155,24 @@ namespace SixLabors.ImageSharp.Tests
} }
} }
public static Image<TPixel> ComparePixelBufferTo<TPixel>(
this Image<TPixel> image,
Span<TPixel> expectedPixels)
where TPixel : struct, IPixel<TPixel>
{
Span<TPixel> actual = image.GetPixelSpan();
Assert.True(expectedPixels.Length == actual.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}!" );
}
return image;
}
public static Image<TPixel> CompareToOriginal<TPixel>( public static Image<TPixel> CompareToOriginal<TPixel>(
this Image<TPixel> image, this Image<TPixel> image,
ITestImageProvider provider) ITestImageProvider provider)
@ -160,7 +180,7 @@ namespace SixLabors.ImageSharp.Tests
{ {
return CompareToOriginal(image, provider, ImageComparer.Tolerant()); return CompareToOriginal(image, provider, ImageComparer.Tolerant());
} }
public static Image<TPixel> CompareToOriginal<TPixel>( public static Image<TPixel> CompareToOriginal<TPixel>(
this Image<TPixel> image, this Image<TPixel> image,
ITestImageProvider provider, ITestImageProvider provider,

Loading…
Cancel
Save