Browse Source

Merge beta-1 into image-frames

pull/326/head
Scott Williams 9 years ago
parent
commit
a09cf7292f
  1. 4
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  2. 8
      src/ImageSharp.Drawing/Paths/ShapeRegion.cs
  3. 5
      src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
  4. 5
      src/ImageSharp.Drawing/Region.cs
  5. 2
      src/ImageSharp/Advanced/ImageExtensions.cs
  6. 11
      src/ImageSharp/Common/Extensions/Vector4Extensions.cs
  7. 67
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
  8. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromCmyk.cs
  9. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromGrayScale.cs
  10. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromRgb.cs
  11. 195
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
  12. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYccK.cs
  13. 26
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs
  14. 66
      src/ImageSharp/Image/Image.LoadPixelData.cs
  15. 4
      src/ImageSharp/Image/ImageExtensions.cs
  16. 1
      src/ImageSharp/Memory/BufferArea{T}.cs
  17. 13
      tests/ImageSharp.Sandbox46/Program.cs
  18. 4
      tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
  19. 12
      tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs
  20. 387
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  21. 23
      tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs

4
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -37,8 +37,8 @@
<ProjectReference Include="..\ImageSharp\ImageSharp.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SixLabors.Shapes.Text" Version="0.1.0-alpha0019" />
<PackageReference Include="SixLabors.Shapes" Version="0.1.0-alpha0019" />
<PackageReference Include="SixLabors.Shapes.Text" Version="0.1.0-alpha0020" />
<PackageReference Include="SixLabors.Shapes" Version="0.1.0-alpha0020" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
<PrivateAssets>All</PrivateAssets>
</PackageReference>

8
src/ImageSharp.Drawing/Paths/ShapeRegion.cs

@ -42,18 +42,18 @@ namespace SixLabors.ImageSharp.Drawing
public override Rectangle Bounds { get; }
/// <inheritdoc/>
public override int Scan(float y, Span<float> 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<PointF>(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;

5
src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs

@ -93,7 +93,6 @@ namespace SixLabors.ImageSharp.Drawing.Processors
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options))
{
float[] buffer = arrayPool.Rent(maxIntersections);
Span<float> bufferSpan = buffer.AsSpan().Slice(0, maxIntersections);
int scanlineWidth = maxX - minX;
using (var scanline = new Buffer<float>(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)
{

5
src/ImageSharp.Drawing/Region.cs

@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Drawing
/// Gets the bounding box that entirely surrounds this region.
/// </summary>
/// <remarks>
/// This should always contains all possible points returned from <see cref="Scan(float, Span{float})"/>.
/// This should always contains all possible points returned from <see cref="Scan(float, float[], int)"/>.
/// </remarks>
public abstract Rectangle Bounds { get; }
@ -29,7 +29,8 @@ namespace SixLabors.ImageSharp.Drawing
/// </summary>
/// <param name="y">The position along the y axis to find intersections.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="offset">The point in the buffer to start setting offset.</param>
/// <returns>The number of intersections found.</returns>
public abstract int Scan(float y, Span<float> buffer);
public abstract int Scan(float y, float[] buffer, int offset);
}
}

2
src/ImageSharp/Advanced/ImageExtensions.cs

@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <summary>
/// Extension methods over Image{TPixel}
/// </summary>
public static class ImageExtensions
internal static class ImageExtensions
{
/// <summary>
/// Gets the representation of the pixels as an area of contiguous memory in the given pixel format.

11
src/ImageSharp/Common/Extensions/Vector4Extensions.cs

@ -79,5 +79,16 @@ namespace SixLabors.ImageSharp
return MathF.Pow((signal + 0.055F) / 1.055F, 2.4F);
}
/// <summary>
/// Transform all scalars in 'v' in a way that converting them to <see cref="int"/> would have rounding semantics.
/// </summary>
[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);
}
}
}

67
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<float> area)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(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<float> 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];

2
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)

2
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)

2
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)

195
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<Vector4> result)
{
ConvertCore(values, result);
}
internal static void ConvertCore(ComponentValues values, Span<Vector4> result)
{
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()!
ReadOnlySpan<float> 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<Vector4> 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));
}
/// <summary>
/// SIMD convert using buffers of sizes divisable by 8.
/// </summary>
internal static void ConvertCore(ComponentValues values, Span<Vector4> result)
{
DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisable by 8!");
ref Vector4Pair yBase =
ref Unsafe.As<float, Vector4Pair>(ref values.Component0.DangerousGetPinnableReference());
ref Vector4Pair cbBase =
ref Unsafe.As<float, Vector4Pair>(ref values.Component1.DangerousGetPinnableReference());
ref Vector4Pair crBase =
ref Unsafe.As<float, Vector4Pair>(ref values.Component2.DangerousGetPinnableReference());
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(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);
}
}
/// <summary>
/// Its faster to process multiple Vector4-s together
/// </summary>
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;
/// <summary>
/// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order.
/// </summary>
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;
}
}
}
}
}

2
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)

26
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// <summary>
/// The avalilable converters
/// </summary>
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() };
/// <summary>
/// Initializes a new instance of the <see cref="JpegColorConverter"/> class.
@ -108,6 +108,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
}
}
}
private ComponentValues(
int componentCount,
ReadOnlySpan<float> c0,
ReadOnlySpan<float> c1,
ReadOnlySpan<float> c2,
ReadOnlySpan<float> c3)
{
this.ComponentCount = componentCount;
this.Component0 = c0;
this.Component1 = c1;
this.Component2 = c2;
this.Component3 = c3;
}
public ComponentValues Slice(int start, int length)
{
ReadOnlySpan<float> c0 = this.Component0.Slice(start, length);
ReadOnlySpan<float> c1 = this.ComponentCount > 1 ? this.Component1.Slice(start, length) : ReadOnlySpan<float>.Empty;
ReadOnlySpan<float> c2 = this.ComponentCount > 2 ? this.Component2.Slice(start, length) : ReadOnlySpan<float>.Empty;
ReadOnlySpan<float> c3 = this.ComponentCount > 3 ? this.Component3.Slice(start, length) : ReadOnlySpan<float>.Empty;
return new ComponentValues(this.ComponentCount, c0, c1, c2, c3);
}
}
}
}

66
src/ImageSharp/Image/Image.LoadPixelData.cs

@ -25,10 +25,34 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Span<TPixel> data, int width, int height)
public static Image<TPixel> LoadPixelData<TPixel>(TPixel[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(Configuration.Default, data, width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
private static Image<TPixel> LoadPixelData<TPixel>(Span<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(Configuration.Default, data, width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(byte[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </summary>
@ -37,7 +61,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Span<byte> data, int width, int height)
private static Image<TPixel> LoadPixelData<TPixel>(Span<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height);
@ -50,7 +74,20 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, Span<byte> data, int width, int height)
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, byte[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(config, new Span<byte>(data).NonPortableCast<byte, TPixel>(), width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
private static Image<TPixel> LoadPixelData<TPixel>(Configuration config, Span<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(config, data.NonPortableCast<byte, TPixel>(), width, height);
@ -63,7 +100,28 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, Span<TPixel> data, int width, int height)
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, TPixel[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
int count = width * height;
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
var image = new Image<TPixel>(config, width, height);
SpanHelper.Copy(data, image.GetPixelSpan(), count);
return image;
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The Span containing the image Pixel data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
private static Image<TPixel> LoadPixelData<TPixel>(Configuration config, Span<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
int count = width * height;

4
src/ImageSharp/Image/ImageExtensions.cs

@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp
/// <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>
public static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, Span<byte> buffer)
private static void SavePixelData<TPixel>(this ImageFrame<TPixel> source, Span<byte> buffer)
where TPixel : struct, IPixel<TPixel>
{
Span<byte> byteBuffer = source.GetPixelSpan().AsBytes();
@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp
/// <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>
public static void SavePixelData<TPixel>(this Image<TPixel> source, Span<byte> buffer)
private static void SavePixelData<TPixel>(this Image<TPixel> source, Span<byte> buffer)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.SavePixelData(buffer);

1
src/ImageSharp/Memory/BufferArea{T}.cs

@ -60,6 +60,7 @@ namespace SixLabors.ImageSharp.Memory
/// Gets a reference to the [0,0] element.
/// </summary>
/// <returns>The reference to the [0,0] element</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetReferenceToOrigo() =>
ref this.DestinationBuffer.Span[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X];

13
tests/ImageSharp.Sandbox46/Program.cs

@ -41,14 +41,21 @@ namespace SixLabors.ImageSharp.Sandbox46
/// </param>
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());

4
tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
Image<Rgba32> img = new Image<Rgba32>(1, 1);
processor.Apply(img, bounds);
region.Verify(x => x.Scan(It.IsAny<float>(), It.IsAny<Span<float>>()), Times.Exactly(4));
region.Verify(x => x.Scan(It.IsAny<float>(), It.IsAny<float[]>(), It.IsAny<int>()), 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<float>(), It.IsAny<Span<float>>()))
region.Setup(x => x.Scan(It.IsAny<float>(), It.IsAny<float[]>(), It.IsAny<int>()))
.Returns<float, Span<float>>((y, span) =>
{
if (y < 5)

12
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<PointF>(), It.IsAny<PointF>(), It.IsAny<Span<PointF>>()))
pathMock.Setup(x => x.FindIntersections(It.IsAny<PointF>(), It.IsAny<PointF>(), It.IsAny<PointF[]>(), It.IsAny<int>()))
.Callback<PointF, PointF, Span<PointF>>((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<PointF>(), It.IsAny<PointF>(), It.IsAny<Span<PointF>>()), Times.Once);
pathMock.Verify(x => x.FindIntersections(It.IsAny<PointF>(), It.IsAny<PointF>(), It.IsAny<PointF[]>(), It.IsAny<int>()), 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<PointF>(), It.IsAny<PointF>(), It.IsAny<Span<PointF>>()))
pathMock.Setup(x => x.FindIntersections(It.IsAny<PointF>(), It.IsAny<PointF>(), It.IsAny<PointF[]>(), It.IsAny<int>()))
.Callback<PointF, PointF, Span<PointF>>((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<PointF>(), It.IsAny<PointF>(), It.IsAny<Span<PointF>>()), Times.Once);
pathMock.Verify(x => x.FindIntersections(It.IsAny<PointF>(), It.IsAny<PointF>(), It.IsAny<PointF[]>(), It.IsAny<int>()), Times.Once);
}
[Fact]

387
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<int, int, int> CommonConversionData =
new TheoryData<int, int, int>
{
{ 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<float>[] buffers = new Buffer2D<float>[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<float>(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<Vector4> 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<float>[] buffers = new Buffer2D<float>[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<float>(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<JpegColorConverter.ComponentValues, Span<Vector4>, 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<JpegColorConverter.ComponentValues, Span<Vector4>, 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<JpegColorConverter.ComponentValues, Span<Vector4>> doConvert,
int componentCount,
int inputBufferLength,
int resultBufferLength,
int seed,
Action<JpegColorConverter.ComponentValues, Span<Vector4>, 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);
}
}
}

23
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");
}
}
}
Loading…
Cancel
Save