diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
index 4f8ab3462..dfef69b75 100644
--- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
+++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
@@ -37,8 +37,8 @@
-
-
+
+
All
diff --git a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs
index a303179e8..1965a7433 100644
--- a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs
+++ b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs
@@ -42,18 +42,18 @@ namespace SixLabors.ImageSharp.Drawing
public override Rectangle Bounds { get; }
///
- public override int Scan(float y, Span buffer)
+ public override int Scan(float y, float[] buffer, int offset)
{
var start = new PointF(this.Bounds.Left - 1, y);
var end = new PointF(this.Bounds.Right + 1, y);
using (var innerBuffer = new Buffer(buffer.Length))
{
- var span = innerBuffer.Span;
- int count = this.Shape.FindIntersections(start, end, span);
+ PointF[] array = innerBuffer.Array;
+ int count = this.Shape.FindIntersections(start, end, array, offset);
for (int i = 0; i < count; i++)
{
- buffer[i] = span[i].X;
+ buffer[i] = array[i].X;
}
return count;
diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
index 6340b1db3..f0e56c76a 100644
--- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
+++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
@@ -93,7 +93,6 @@ namespace SixLabors.ImageSharp.Drawing.Processors
using (BrushApplicator applicator = this.Brush.CreateApplicator(source, rect, this.Options))
{
float[] buffer = arrayPool.Rent(maxIntersections);
- Span bufferSpan = buffer.AsSpan().Slice(0, maxIntersections);
int scanlineWidth = maxX - minX;
using (var scanline = new Buffer(scanlineWidth))
{
@@ -117,14 +116,14 @@ namespace SixLabors.ImageSharp.Drawing.Processors
float subpixelFractionPoint = subpixelFraction / subpixelCount;
for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction)
{
- int pointsFound = region.Scan(subPixel, bufferSpan);
+ int pointsFound = region.Scan(subPixel, buffer, 0);
if (pointsFound == 0)
{
// nothing on this line skip
continue;
}
- QuickSort(bufferSpan.Slice(0, pointsFound));
+ QuickSort(buffer.AsSpan().Slice(0, pointsFound));
for (int point = 0; point < pointsFound; point += 2)
{
diff --git a/src/ImageSharp.Drawing/Region.cs b/src/ImageSharp.Drawing/Region.cs
index 5d4d471f1..c5e7c1cfd 100644
--- a/src/ImageSharp.Drawing/Region.cs
+++ b/src/ImageSharp.Drawing/Region.cs
@@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Drawing
/// Gets the bounding box that entirely surrounds this region.
///
///
- /// This should always contains all possible points returned from .
+ /// This should always contains all possible points returned from .
///
public abstract Rectangle Bounds { get; }
@@ -29,7 +29,8 @@ namespace SixLabors.ImageSharp.Drawing
///
/// The position along the y axis to find intersections.
/// The buffer.
+ /// The point in the buffer to start setting offset.
/// The number of intersections found.
- public abstract int Scan(float y, Span buffer);
+ public abstract int Scan(float y, float[] buffer, int offset);
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Advanced/ImageExtensions.cs b/src/ImageSharp/Advanced/ImageExtensions.cs
index 4369cf67a..f4043b5ad 100644
--- a/src/ImageSharp/Advanced/ImageExtensions.cs
+++ b/src/ImageSharp/Advanced/ImageExtensions.cs
@@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Advanced
///
/// Extension methods over Image{TPixel}
///
- public static class ImageExtensions
+ internal static class ImageExtensions
{
///
/// Gets the representation of the pixels as an area of contiguous memory in the given pixel format.
diff --git a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
index 5fbc3960a..1809dd329 100644
--- a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
+++ b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
@@ -79,5 +79,16 @@ namespace SixLabors.ImageSharp
return MathF.Pow((signal + 0.055F) / 1.055F, 2.4F);
}
+
+ ///
+ /// Transform all scalars in 'v' in a way that converting them to would have rounding semantics.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static Vector4 PseudoRound(this Vector4 v)
+ {
+ var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1));
+
+ return v + (sign * 0.5f);
+ }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
index 4d0ec3393..474c75adf 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
@@ -305,14 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
}
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row)
- {
- ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float));
- ref byte d = ref Unsafe.Add(ref destBase, row * destStride);
- Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
- }
-
+ // [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CopyTo(BufferArea area)
{
ref byte selfBase = ref Unsafe.As(ref this);
@@ -336,17 +329,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
this.CopyTo(area);
return;
}
+ else if (horizontalScale == 2 && verticalScale == 2)
+ {
+ this.CopyTo2x2(area);
+ return;
+ }
// TODO: Optimize: implement all the cases with loopless special code! (T4?)
for (int y = 0; y < 8; y++)
{
int yy = y * verticalScale;
+ int y8 = y * 8;
for (int x = 0; x < 8; x++)
{
int xx = x * horizontalScale;
- float value = this[(y * 8) + x];
+ float value = this[y8 + x];
for (int i = 0; i < verticalScale; i++)
{
@@ -359,6 +358,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
}
}
+ private void CopyTo2x2(BufferArea area)
+ {
+ ref float destBase = ref area.GetReferenceToOrigo();
+ int destStride = area.Stride;
+
+ this.CopyRow2x2Impl(ref destBase, 0, destStride);
+ this.CopyRow2x2Impl(ref destBase, 1, destStride);
+ this.CopyRow2x2Impl(ref destBase, 2, destStride);
+ this.CopyRow2x2Impl(ref destBase, 3, destStride);
+ this.CopyRow2x2Impl(ref destBase, 4, destStride);
+ this.CopyRow2x2Impl(ref destBase, 5, destStride);
+ this.CopyRow2x2Impl(ref destBase, 6, destStride);
+ this.CopyRow2x2Impl(ref destBase, 7, destStride);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row)
+ {
+ ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float));
+ ref byte d = ref Unsafe.Add(ref destBase, row * destStride);
+ Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void CopyRow2x2Impl(ref float destBase, int row, int destStride)
+ {
+ ref Vector4 selfLeft = ref Unsafe.Add(ref this.V0L, 2 * row);
+ ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1);
+ ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride);
+
+ Stride2VectorCopyImpl(ref selfLeft, ref destLocalOrigo);
+ Stride2VectorCopyImpl(ref selfRight, ref Unsafe.Add(ref destLocalOrigo, 8));
+
+ Stride2VectorCopyImpl(ref selfLeft, ref Unsafe.Add(ref destLocalOrigo, destStride));
+ Stride2VectorCopyImpl(ref selfRight, ref Unsafe.Add(ref destLocalOrigo, destStride + 8));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Stride2VectorCopyImpl(ref Vector4 s, ref float destBase)
+ {
+ Unsafe.Add(ref destBase, 0) = s.X;
+ Unsafe.Add(ref destBase, 1) = s.X;
+ Unsafe.Add(ref destBase, 2) = s.Y;
+ Unsafe.Add(ref destBase, 3) = s.Y;
+ Unsafe.Add(ref destBase, 4) = s.Z;
+ Unsafe.Add(ref destBase, 5) = s.Z;
+ Unsafe.Add(ref destBase, 6) = s.W;
+ Unsafe.Add(ref destBase, 7) = s.W;
+ }
+
public float[] ToArray()
{
float[] result = new float[Size];
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs
index 524cc76df..908ffb85e 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs
@@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
- private class FromCmyk : JpegColorConverter
+ internal class FromCmyk : JpegColorConverter
{
public FromCmyk()
: base(JpegColorSpace.Cmyk)
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs
index 9ff263dcf..2b19eebac 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs
@@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
- private class FromGrayScale : JpegColorConverter
+ internal class FromGrayScale : JpegColorConverter
{
public FromGrayScale()
: base(JpegColorSpace.GrayScale)
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs
index f4a702783..c4a30ea3e 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs
@@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
- private class FromRgb : JpegColorConverter
+ internal class FromRgb : JpegColorConverter
{
public FromRgb()
: base(JpegColorSpace.RGB)
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
index 24e8d753b..059b2e89a 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
@@ -1,18 +1,24 @@
using System;
using System.Numerics;
+using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
- private class FromYCbCr : JpegColorConverter
+ internal class FromYCbCrBasic : JpegColorConverter
{
- public FromYCbCr()
+ public FromYCbCrBasic()
: base(JpegColorSpace.YCbCr)
{
}
public override void ConvertToRGBA(ComponentValues values, Span result)
+ {
+ ConvertCore(values, result);
+ }
+
+ internal static void ConvertCore(ComponentValues values, Span result)
{
// TODO: We can optimize a lot here with Vector and SRCS.Unsafe()!
ReadOnlySpan yVals = values.Component0;
@@ -39,5 +45,190 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
}
}
}
+
+ internal class FromYCbCrSimd : JpegColorConverter
+ {
+ public FromYCbCrSimd()
+ : base(JpegColorSpace.YCbCr)
+ {
+ }
+
+ public override void ConvertToRGBA(ComponentValues values, Span result)
+ {
+ int remainder = result.Length % 8;
+ int simdCount = result.Length - remainder;
+ if (simdCount > 0)
+ {
+ ConvertCore(values.Slice(0, simdCount), result.Slice(0, simdCount));
+ }
+
+ FromYCbCrBasic.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder));
+ }
+
+ ///
+ /// SIMD convert using buffers of sizes divisable by 8.
+ ///
+ internal static void ConvertCore(ComponentValues values, Span result)
+ {
+ DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisable by 8!");
+
+ ref Vector4Pair yBase =
+ ref Unsafe.As(ref values.Component0.DangerousGetPinnableReference());
+ ref Vector4Pair cbBase =
+ ref Unsafe.As(ref values.Component1.DangerousGetPinnableReference());
+ ref Vector4Pair crBase =
+ ref Unsafe.As(ref values.Component2.DangerousGetPinnableReference());
+
+ ref Vector4Octet resultBase =
+ ref Unsafe.As(ref result.DangerousGetPinnableReference());
+
+ var chromaOffset = new Vector4(-128f);
+
+ // Walking 8 elements at one step:
+ int n = result.Length / 8;
+
+ for (int i = 0; i < n; i++)
+ {
+ // y = yVals[i];
+ Vector4Pair y = Unsafe.Add(ref yBase, i);
+
+ // cb = cbVals[i] - 128F;
+ Vector4Pair cb = Unsafe.Add(ref cbBase, i);
+ cb.AddInplace(chromaOffset);
+
+ // cr = crVals[i] - 128F;
+ Vector4Pair cr = Unsafe.Add(ref crBase, i);
+ cr.AddInplace(chromaOffset);
+
+ // r = y + (1.402F * cr);
+ Vector4Pair r = y;
+ Vector4Pair tmp = cr;
+ tmp.MultiplyInplace(1.402F);
+ r.AddInplace(ref tmp);
+
+ // g = y - (0.344136F * cb) - (0.714136F * cr);
+ Vector4Pair g = y;
+ tmp = cb;
+ tmp.MultiplyInplace(-0.344136F);
+ g.AddInplace(ref tmp);
+ tmp = cr;
+ tmp.MultiplyInplace(-0.714136F);
+ g.AddInplace(ref tmp);
+
+ // b = y + (1.772F * cb);
+ Vector4Pair b = y;
+ tmp = cb;
+ tmp.MultiplyInplace(1.772F);
+ b.AddInplace(ref tmp);
+
+ r.RoundAndDownscale();
+ g.RoundAndDownscale();
+ b.RoundAndDownscale();
+
+ // Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
+ ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
+ destination.Collect(ref r, ref g, ref b);
+ }
+ }
+
+ ///
+ /// Its faster to process multiple Vector4-s together
+ ///
+ private struct Vector4Pair
+ {
+ public Vector4 A;
+
+ public Vector4 B;
+
+ private static readonly Vector4 Scale = new Vector4(1 / 255f);
+
+ private static readonly Vector4 Half = new Vector4(0.5f);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void RoundAndDownscale()
+ {
+ // Emulate rounding:
+ this.A += Half;
+ this.B += Half;
+
+ // Downscale by 1/255
+ this.A *= Scale;
+ this.B *= Scale;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void MultiplyInplace(float value)
+ {
+ this.A *= value;
+ this.B *= value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddInplace(Vector4 value)
+ {
+ this.A += value;
+ this.B += value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddInplace(ref Vector4Pair other)
+ {
+ this.A += other.A;
+ this.B += other.B;
+ }
+ }
+
+ private struct Vector4Octet
+ {
+#pragma warning disable SA1132 // Do not combine fields
+ public Vector4 V0, V1, V2, V3, V4, V5, V6, V7;
+
+ ///
+ /// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order.
+ ///
+ public void Collect(ref Vector4Pair r, ref Vector4Pair g, ref Vector4Pair b)
+ {
+ this.V0.X = r.A.X;
+ this.V0.Y = g.A.X;
+ this.V0.Z = b.A.X;
+ this.V0.W = 1f;
+
+ this.V1.X = r.A.Y;
+ this.V1.Y = g.A.Y;
+ this.V1.Z = b.A.Y;
+ this.V1.W = 1f;
+
+ this.V2.X = r.A.Z;
+ this.V2.Y = g.A.Z;
+ this.V2.Z = b.A.Z;
+ this.V2.W = 1f;
+
+ this.V3.X = r.A.W;
+ this.V3.Y = g.A.W;
+ this.V3.Z = b.A.W;
+ this.V3.W = 1f;
+
+ this.V4.X = r.B.X;
+ this.V4.Y = g.B.X;
+ this.V4.Z = b.B.X;
+ this.V4.W = 1f;
+
+ this.V5.X = r.B.Y;
+ this.V5.Y = g.B.Y;
+ this.V5.Z = b.B.Y;
+ this.V5.W = 1f;
+
+ this.V6.X = r.B.Z;
+ this.V6.Y = g.B.Z;
+ this.V6.Z = b.B.Z;
+ this.V6.W = 1f;
+
+ this.V7.X = r.B.W;
+ this.V7.Y = g.B.W;
+ this.V7.Z = b.B.W;
+ this.V7.W = 1f;
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs
index 3449cc6b1..7e6edbdce 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs
@@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
internal abstract partial class JpegColorConverter
{
- private class FromYccK : JpegColorConverter
+ internal class FromYccK : JpegColorConverter
{
public FromYccK()
: base(JpegColorSpace.Ycck)
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs
index 567713422..1c2dda01f 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs
@@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
///
/// The avalilable converters
///
- private static readonly JpegColorConverter[] Converters = { new FromYCbCr(), new FromYccK(), new FromCmyk(), new FromGrayScale(), new FromRgb() };
+ private static readonly JpegColorConverter[] Converters = { new FromYCbCrSimd(), new FromYccK(), new FromCmyk(), new FromGrayScale(), new FromRgb() };
///
/// Initializes a new instance of the class.
@@ -108,6 +108,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
}
}
}
+
+ private ComponentValues(
+ int componentCount,
+ ReadOnlySpan c0,
+ ReadOnlySpan c1,
+ ReadOnlySpan c2,
+ ReadOnlySpan c3)
+ {
+ this.ComponentCount = componentCount;
+ this.Component0 = c0;
+ this.Component1 = c1;
+ this.Component2 = c2;
+ this.Component3 = c3;
+ }
+
+ public ComponentValues Slice(int start, int length)
+ {
+ ReadOnlySpan c0 = this.Component0.Slice(start, length);
+ ReadOnlySpan c1 = this.ComponentCount > 1 ? this.Component1.Slice(start, length) : ReadOnlySpan.Empty;
+ ReadOnlySpan c2 = this.ComponentCount > 2 ? this.Component2.Slice(start, length) : ReadOnlySpan.Empty;
+ ReadOnlySpan c3 = this.ComponentCount > 3 ? this.Component3.Slice(start, length) : ReadOnlySpan.Empty;
+
+ return new ComponentValues(this.ComponentCount, c0, c1, c2, c3);
+ }
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Image/Image.LoadPixelData.cs b/src/ImageSharp/Image/Image.LoadPixelData.cs
index 87edf0bc8..5f1a1617f 100644
--- a/src/ImageSharp/Image/Image.LoadPixelData.cs
+++ b/src/ImageSharp/Image/Image.LoadPixelData.cs
@@ -25,10 +25,34 @@ namespace SixLabors.ImageSharp
/// The height of the final image.
/// The pixel format.
/// A new .
- public static Image LoadPixelData(Span data, int width, int height)
+ public static Image LoadPixelData(TPixel[] data, int width, int height)
where TPixel : struct, IPixel
=> LoadPixelData(Configuration.Default, data, width, height);
+ ///
+ /// Create a new instance of the class from the raw data.
+ ///
+ /// The byte array containing image data.
+ /// The width of the final image.
+ /// The height of the final image.
+ /// The pixel format.
+ /// A new .
+ private static Image LoadPixelData(Span data, int width, int height)
+ where TPixel : struct, IPixel
+ => LoadPixelData(Configuration.Default, data, width, height);
+
+ ///
+ /// Create a new instance of the class from the given byte array in format.
+ ///
+ /// The byte array containing image data.
+ /// The width of the final image.
+ /// The height of the final image.
+ /// The pixel format.
+ /// A new .
+ public static Image LoadPixelData(byte[] data, int width, int height)
+ where TPixel : struct, IPixel
+ => LoadPixelData(Configuration.Default, data, width, height);
+
///
/// Create a new instance of the class from the given byte array in format.
///
@@ -37,7 +61,7 @@ namespace SixLabors.ImageSharp
/// The height of the final image.
/// The pixel format.
/// A new .
- public static Image LoadPixelData(Span data, int width, int height)
+ private static Image LoadPixelData(Span data, int width, int height)
where TPixel : struct, IPixel
=> LoadPixelData(Configuration.Default, data, width, height);
@@ -50,7 +74,20 @@ namespace SixLabors.ImageSharp
/// The height of the final image.
/// The pixel format.
/// A new .
- public static Image LoadPixelData(Configuration config, Span data, int width, int height)
+ public static Image LoadPixelData(Configuration config, byte[] data, int width, int height)
+ where TPixel : struct, IPixel
+ => LoadPixelData(config, new Span(data).NonPortableCast(), width, height);
+
+ ///
+ /// Create a new instance of the class from the given byte array in format.
+ ///
+ /// The config for the decoder.
+ /// The byte array containing image data.
+ /// The width of the final image.
+ /// The height of the final image.
+ /// The pixel format.
+ /// A new .
+ private static Image LoadPixelData(Configuration config, Span data, int width, int height)
where TPixel : struct, IPixel
=> LoadPixelData(config, data.NonPortableCast(), width, height);
@@ -63,7 +100,28 @@ namespace SixLabors.ImageSharp
/// The height of the final image.
/// The pixel format.
/// A new .
- public static Image LoadPixelData(Configuration config, Span data, int width, int height)
+ public static Image LoadPixelData(Configuration config, TPixel[] data, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ int count = width * height;
+ Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
+
+ var image = new Image(config, width, height);
+ SpanHelper.Copy(data, image.GetPixelSpan(), count);
+
+ return image;
+ }
+
+ ///
+ /// Create a new instance of the class from the raw data.
+ ///
+ /// The config for the decoder.
+ /// The Span containing the image Pixel data.
+ /// The width of the final image.
+ /// The height of the final image.
+ /// The pixel format.
+ /// A new .
+ private static Image LoadPixelData(Configuration config, Span data, int width, int height)
where TPixel : struct, IPixel
{
int count = width * height;
diff --git a/src/ImageSharp/Image/ImageExtensions.cs b/src/ImageSharp/Image/ImageExtensions.cs
index 377dc2580..c5b3d6f31 100644
--- a/src/ImageSharp/Image/ImageExtensions.cs
+++ b/src/ImageSharp/Image/ImageExtensions.cs
@@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp
/// The source image
/// The buffer to save the raw pixel data to.
/// Thrown if the stream is null.
- public static void SavePixelData(this ImageFrame source, Span buffer)
+ private static void SavePixelData(this ImageFrame source, Span buffer)
where TPixel : struct, IPixel
{
Span byteBuffer = source.GetPixelSpan().AsBytes();
@@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp
/// The source image
/// The buffer to save the raw pixel data to.
/// Thrown if the stream is null.
- public static void SavePixelData(this Image source, Span buffer)
+ private static void SavePixelData(this Image source, Span buffer)
where TPixel : struct, IPixel
=> source.Frames.RootFrame.SavePixelData(buffer);
diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs
index 92e78e9c0..8ead22680 100644
--- a/src/ImageSharp/Memory/BufferArea{T}.cs
+++ b/src/ImageSharp/Memory/BufferArea{T}.cs
@@ -60,6 +60,7 @@ namespace SixLabors.ImageSharp.Memory
/// Gets a reference to the [0,0] element.
///
/// The reference to the [0,0] element
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetReferenceToOrigo() =>
ref this.DestinationBuffer.Span[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X];
diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs
index 532bf9574..869a720df 100644
--- a/tests/ImageSharp.Sandbox46/Program.cs
+++ b/tests/ImageSharp.Sandbox46/Program.cs
@@ -41,14 +41,21 @@ namespace SixLabors.ImageSharp.Sandbox46
///
public static void Main(string[] args)
{
- RunDecodeJpegProfilingTests();
- // RunToVector4ProfilingTest();
+ RunJpegColorProfilingTests();
- //RunResizeProfilingTest();
+ // RunDecodeJpegProfilingTests();
+ // RunToVector4ProfilingTest();
+ // RunResizeProfilingTest();
Console.ReadLine();
}
+ private static void RunJpegColorProfilingTests()
+ {
+ new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(false);
+ new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(true);
+ }
+
private static void RunResizeProfilingTest()
{
ResizeProfilingBenchmarks test = new ResizeProfilingBenchmarks(new ConsoleOutput());
diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
index 9540ee81b..db6c1157c 100644
--- a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
@@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
Image img = new Image(1, 1);
processor.Apply(img, bounds);
- region.Verify(x => x.Scan(It.IsAny(), It.IsAny>()), Times.Exactly(4));
+ region.Verify(x => x.Scan(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(4));
}
[Fact]
@@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
region.Setup(x => x.Bounds).Returns(bounds);
region.Setup(x => x.MaxIntersections).Returns(10);
- region.Setup(x => x.Scan(It.IsAny(), It.IsAny>()))
+ region.Setup(x => x.Scan(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns>((y, span) =>
{
if (y < 5)
diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs
index 41fb5643f..f5950a6ef 100644
--- a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs
@@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
int yToScan = 10;
ShapeRegion region = new ShapeRegion(pathMock.Object);
- pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny>()))
+ pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Callback>((s, e, b) => {
Assert.Equal(yToScan, s.Y);
Assert.Equal(yToScan, e.Y);
@@ -84,9 +84,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
Assert.True(e.X > bounds.Right);
}).Returns(0);
- int i = region.Scan(yToScan, new float[0]);
+ int i = region.Scan(yToScan, new float[0], 0);
- pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once);
+ pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
}
[Fact]
@@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
int yToScan = 10;
ShapeRegion region = new ShapeRegion(pathMock.Object);
- pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny>()))
+ pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Callback>((s, e, b) => {
Assert.Equal(yToScan, s.Y);
Assert.Equal(yToScan, e.Y);
@@ -103,9 +103,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
Assert.True(e.X > bounds.Right);
}).Returns(0);
- int i = region.Scan(yToScan, new float[0]);
+ int i = region.Scan(yToScan, new float[0], 0);
- pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once);
+ pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
}
[Fact]
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
index 590cd322e..e9db65105 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
@@ -8,19 +8,21 @@ using SixLabors.ImageSharp.Memory;
using Xunit;
using Xunit.Abstractions;
+// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
public class JpegColorConverterTests
{
- private const float Precision = 0.1f;
+ private const float Precision = 1/255f;
- private const int InputBufferLength = 42;
-
- // The result buffer could be shorter
- private const int ResultBufferLength = 40;
-
- private readonly Vector4[] result = new Vector4[ResultBufferLength];
+ public static readonly TheoryData CommonConversionData =
+ new TheoryData
+ {
+ { 40, 40, 1 },
+ { 42, 40, 2 },
+ { 42, 39, 3 }
+ };
private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter();
@@ -31,161 +33,300 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private ITestOutputHelper Output { get; }
- private static JpegColorConverter.ComponentValues CreateRandomValues(int componentCount, float maxVal = 255f)
- {
- var rnd = new Random(42);
- Buffer2D[] buffers = new Buffer2D[componentCount];
- for (int i = 0; i < componentCount; i++)
- {
- float[] values = new float[InputBufferLength];
+ private static int R(float f) => (int)MathF.Round(f, MidpointRounding.AwayFromZero);
- for (int j = 0; j < InputBufferLength; j++)
- {
- values[j] = (float)rnd.NextDouble() * maxVal;
- }
-
- // no need to dispose when buffer is not array owner
- buffers[i] = new Buffer2D(values, values.Length, 1);
- }
- return new JpegColorConverter.ComponentValues(buffers, 0);
+ // TODO: Move this to a proper test class!
+ [Theory]
+ [InlineData(0.32, 54.5, -3.5, -4.1)]
+ [InlineData(5.3, 536.4, 4.5, 8.1)]
+ public void Vector4_PseudoRound(float x, float y, float z, float w)
+ {
+ var v = new Vector4(x, y, z, w);
+
+ Vector4 actual = v.PseudoRound();
+
+ Assert.Equal(
+ R(v.X),
+ (int)actual.X
+ );
+ Assert.Equal(
+ R(v.Y),
+ (int)actual.Y
+ );
+ Assert.Equal(
+ R(v.Z),
+ (int)actual.Z
+ );
+ Assert.Equal(
+ R(v.W),
+ (int)actual.W
+ );
}
- [Fact]
- public void ConvertFromYCbCr()
+ [Theory]
+ [MemberData(nameof(CommonConversionData))]
+ public void ConvertFromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed)
{
- var converter = JpegColorConverter.GetConverter(JpegColorSpace.YCbCr);
-
- JpegColorConverter.ComponentValues values = CreateRandomValues(3);
+ ValidateConversion(new JpegColorConverter.FromYCbCrBasic(), 3, inputBufferLength, resultBufferLength, seed, ValidateYCbCr);
+ }
- converter.ConvertToRGBA(values, this.result);
+ private static void ValidateYCbCr(JpegColorConverter.ComponentValues values, Span result, int i)
+ {
+ float y = values.Component0[i];
+ float cb = values.Component1[i];
+ float cr = values.Component2[i];
+ var ycbcr = new YCbCr(y, cb, cr);
- for (int i = 0; i < ResultBufferLength; i++)
- {
- float y = values.Component0[i];
- float cb = values.Component1[i];
- float cr = values.Component2[i];
- var ycbcr = new YCbCr(y, cb, cr);
+ Vector4 rgba = result[i];
+ var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
+ var expected = ColorSpaceConverter.ToRgb(ycbcr);
- Vector4 rgba = this.result[i];
- var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
- var expected = ColorSpaceConverter.ToRgb(ycbcr);
+ Assert.True(actual.AlmostEquals(expected, Precision), $"{actual} != {expected}");
+ Assert.Equal(1, rgba.W);
+ }
- Assert.True(actual.AlmostEquals(expected, Precision));
- Assert.Equal(1, rgba.W);
- }
+ [Theory]
+ [InlineData(64, 1)]
+ [InlineData(16, 2)]
+ [InlineData(8, 3)]
+ public void FromYCbCrSimd_ConvertCore(int size, int seed)
+ {
+ ValidateConversion(JpegColorConverter.FromYCbCrSimd.ConvertCore, 3, size, size, seed, ValidateYCbCr);
}
- [Fact]
- public void ConvertFromCmyk()
+ [Theory]
+ [MemberData(nameof(CommonConversionData))]
+ public void FromYCbCrSimd(int inputBufferLength, int resultBufferLength, int seed)
{
- var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk);
+ ValidateConversion(new JpegColorConverter.FromYCbCrSimd(), 3, inputBufferLength, resultBufferLength, seed, ValidateYCbCr);
+ }
- JpegColorConverter.ComponentValues values = CreateRandomValues(4);
+ [Theory]
+ [MemberData(nameof(CommonConversionData))]
+ public void ConvertFromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed)
+ {
+ ValidateConversion(JpegColorSpace.YCbCr, 3, inputBufferLength, resultBufferLength, seed, ValidateYCbCr);
+ }
- converter.ConvertToRGBA(values, this.result);
+ // Becnhmark, for local execution only
+ //[Theory]
+ //[InlineData(false)]
+ //[InlineData(true)]
+ public void BenchmarkYCbCr(bool simd)
+ {
+ int count = 2053;
+ int times = 50000;
+
+ JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1);
+ Vector4[] result = new Vector4[count];
+
+ JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd() : new JpegColorConverter.FromYCbCrBasic();
+
+ // Warm up:
+ converter.ConvertToRGBA(values, result);
+
+ using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}"))
+ {
+ for (int i = 0; i < times; i++)
+ {
+ converter.ConvertToRGBA(values, result);
+ }
+ }
+ }
+ [Theory]
+ [MemberData(nameof(CommonConversionData))]
+ public void ConvertFromCmyk(int inputBufferLength, int resultBufferLength, int seed)
+ {
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
- for (int i = 0; i < ResultBufferLength; i++)
- {
- float c = values.Component0[i];
- float m = values.Component1[i];
- float y = values.Component2[i];
- float k = values.Component3[i] / 255F;
-
- v.X = c * k;
- v.Y = m * k;
- v.Z = y * k;
- v.W = 1F;
-
- v *= scale;
-
- Vector4 rgba = this.result[i];
- var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
- var expected = new Rgb(v.X, v.Y, v.Z);
-
- Assert.True(actual.AlmostEquals(expected, Precision));
- Assert.Equal(1, rgba.W);
- }
+ ValidateConversion(
+ JpegColorSpace.Cmyk,
+ 4,
+ inputBufferLength,
+ resultBufferLength,
+ seed,
+ (values, result, i) =>
+ {
+ float c = values.Component0[i];
+ float m = values.Component1[i];
+ float y = values.Component2[i];
+ float k = values.Component3[i] / 255F;
+
+ v.X = c * k;
+ v.Y = m * k;
+ v.Z = y * k;
+ v.W = 1F;
+
+ v *= scale;
+
+ Vector4 rgba = result[i];
+ var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
+ var expected = new Rgb(v.X, v.Y, v.Z);
+
+ Assert.True(actual.AlmostEquals(expected, Precision));
+ Assert.Equal(1, rgba.W);
+ });
}
- [Fact]
- public void ConvertFromYcck()
+ [Theory]
+ [MemberData(nameof(CommonConversionData))]
+ public void ConvertFromGrayScale(int inputBufferLength, int resultBufferLength, int seed)
{
- var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck);
-
- JpegColorConverter.ComponentValues values = CreateRandomValues(4);
+ ValidateConversion(
+ JpegColorSpace.GrayScale,
+ 1,
+ inputBufferLength,
+ resultBufferLength,
+ seed,
+ (values, result, i) =>
+ {
+ float y = values.Component0[i];
+ Vector4 rgba = result[i];
+ var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
+ var expected = new Rgb(y / 255F, y / 255F, y / 255F);
+
+ Assert.True(actual.AlmostEquals(expected, Precision));
+ Assert.Equal(1, rgba.W);
+ });
+ }
- converter.ConvertToRGBA(values, this.result);
+ [Theory]
+ [MemberData(nameof(CommonConversionData))]
+ public void ConvertFromRgb(int inputBufferLength, int resultBufferLength, int seed)
+ {
+ ValidateConversion(
+ JpegColorSpace.RGB,
+ 3,
+ inputBufferLength,
+ resultBufferLength,
+ seed,
+ (values, result, i) =>
+ {
+ float r = values.Component0[i];
+ float g = values.Component1[i];
+ float b = values.Component2[i];
+ Vector4 rgba = result[i];
+ var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
+ var expected = new Rgb(r / 255F, g / 255F, b / 255F);
+
+ Assert.True(actual.AlmostEquals(expected, Precision));
+ Assert.Equal(1, rgba.W);
+ });
+ }
+ [Theory]
+ [MemberData(nameof(CommonConversionData))]
+ public void ConvertFromYcck(int inputBufferLength, int resultBufferLength, int seed)
+ {
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
- for (int i = 0; i < ResultBufferLength; i++)
- {
- float y = values.Component0[i];
- float cb = values.Component1[i] - 128F;
- float cr = values.Component2[i] - 128F;
- float k = values.Component3[i] / 255F;
-
- v.X = (255F - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k;
- v.Y = (255F - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k;
- v.Z = (255F - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k;
- v.W = 1F;
+ ValidateConversion(
+ JpegColorSpace.Ycck,
+ 4,
+ inputBufferLength,
+ resultBufferLength,
+ seed,
+ (values, result, i) =>
+ {
+ float y = values.Component0[i];
+ float cb = values.Component1[i] - 128F;
+ float cr = values.Component2[i] - 128F;
+ float k = values.Component3[i] / 255F;
+
+ v.X = (255F - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k;
+ v.Y = (255F - MathF.Round(
+ y - (0.344136F * cb) - (0.714136F * cr),
+ MidpointRounding.AwayFromZero)) * k;
+ v.Z = (255F - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k;
+ v.W = 1F;
+
+ v *= scale;
+
+ Vector4 rgba = result[i];
+ var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
+ var expected = new Rgb(v.X, v.Y, v.Z);
+
+ Assert.True(actual.AlmostEquals(expected, Precision));
+ Assert.Equal(1, rgba.W);
+ });
+ }
- v *= scale;
+ private static JpegColorConverter.ComponentValues CreateRandomValues(
+ int componentCount,
+ int inputBufferLength,
+ int seed,
+ float maxVal = 255f)
+ {
+ var rnd = new Random(seed);
+ Buffer2D[] buffers = new Buffer2D[componentCount];
+ for (int i = 0; i < componentCount; i++)
+ {
+ float[] values = new float[inputBufferLength];
- Vector4 rgba = this.result[i];
- var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
- var expected = new Rgb(v.X, v.Y, v.Z);
+ for (int j = 0; j < inputBufferLength; j++)
+ {
+ values[j] = (float)rnd.NextDouble() * maxVal;
+ }
- Assert.True(actual.AlmostEquals(expected, Precision));
- Assert.Equal(1, rgba.W);
+ // no need to dispose when buffer is not array owner
+ buffers[i] = new Buffer2D(values, values.Length, 1);
}
+ return new JpegColorConverter.ComponentValues(buffers, 0);
}
- [Fact]
- public void ConvertFromGrayScale()
+ private static void ValidateConversion(
+ JpegColorSpace colorSpace,
+ int componentCount,
+ int inputBufferLength,
+ int resultBufferLength,
+ int seed,
+ Action, int> validatePixelValue)
{
- var converter = JpegColorConverter.GetConverter(JpegColorSpace.GrayScale);
-
- JpegColorConverter.ComponentValues values = CreateRandomValues(1);
-
- converter.ConvertToRGBA(values, this.result);
-
- for (int i = 0; i < ResultBufferLength; i++)
- {
- float y = values.Component0[i];
- Vector4 rgba = this.result[i];
- var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
- var expected = new Rgb(y / 255F, y / 255F, y / 255F);
-
- Assert.True(actual.AlmostEquals(expected, Precision));
- Assert.Equal(1, rgba.W);
- }
+ ValidateConversion(
+ JpegColorConverter.GetConverter(colorSpace),
+ componentCount,
+ inputBufferLength,
+ resultBufferLength,
+ seed,
+ validatePixelValue);
}
- [Fact]
- public void ConvertFromRgb()
+ private static void ValidateConversion(
+ JpegColorConverter converter,
+ int componentCount,
+ int inputBufferLength,
+ int resultBufferLength,
+ int seed,
+ Action, int> validatePixelValue)
{
- var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB);
+ ValidateConversion(
+ converter.ConvertToRGBA,
+ componentCount,
+ inputBufferLength,
+ resultBufferLength,
+ seed,
+ validatePixelValue);
+ }
- JpegColorConverter.ComponentValues values = CreateRandomValues(3);
+ private static void ValidateConversion(
+ Action> doConvert,
+ int componentCount,
+ int inputBufferLength,
+ int resultBufferLength,
+ int seed,
+ Action, int> validatePixelValue)
+ {
+ JpegColorConverter.ComponentValues values = CreateRandomValues(componentCount, inputBufferLength, seed);
+ Vector4[] result = new Vector4[resultBufferLength];
- converter.ConvertToRGBA(values, this.result);
+ doConvert(values, result);
- for (int i = 0; i < ResultBufferLength; i++)
+ for (int i = 0; i < resultBufferLength; i++)
{
- float r = values.Component0[i];
- float g = values.Component1[i];
- float b = values.Component2[i];
- Vector4 rgba = this.result[i];
- var actual = new Rgb(rgba.X, rgba.Y, rgba.Z);
- var expected = new Rgb(r / 255F, g / 255F, b / 255F);
-
- Assert.True(actual.AlmostEquals(expected, Precision));
- Assert.Equal(1, rgba.W);
+ validatePixelValue(values, result, i);
}
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs
index 7725994c4..c892c09de 100644
--- a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs
@@ -56,4 +56,27 @@ namespace SixLabors.ImageSharp.Tests
protected ITestOutputHelper Output { get; }
}
+
+ public class MeasureGuard : IDisposable
+ {
+ private readonly string operation;
+
+ private readonly Stopwatch stopwatch = new Stopwatch();
+
+ public MeasureGuard(ITestOutputHelper output, string operation)
+ {
+ this.operation = operation;
+ this.Output = output;
+ this.Output.WriteLine(operation + " ...");
+ this.stopwatch.Start();
+ }
+
+ private ITestOutputHelper Output { get; }
+
+ public void Dispose()
+ {
+ this.stopwatch.Stop();
+ this.Output.WriteLine($"{this.operation} completed in {this.stopwatch.ElapsedMilliseconds}ms");
+ }
+ }
}
\ No newline at end of file