Browse Source

fix accuracy issues

pull/742/head
Anton Firszov 7 years ago
parent
commit
664d838291
  1. 20
      src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
  2. 57
      src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs
  3. 70
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  4. 9
      tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs

20
src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs

@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp
{
Guard.IsTrue(
dest.Length % Vector<byte>.Count == 0,
nameof(source),
nameof(dest),
"dest.Length should be divisable by Vector<byte>.Count!");
int n = dest.Length / Vector<byte>.Count;
@ -93,8 +93,6 @@ namespace SixLabors.ImageSharp
ref Vector<float> sourceBase = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(source));
ref Vector<byte> destBase = ref Unsafe.As<byte, Vector<byte>>(ref MemoryMarshal.GetReference(dest));
Vector<float> scale = new Vector<float>(255);
for (int i = 0; i < n; i++)
{
ref Vector<float> s = ref Unsafe.Add(ref sourceBase, i * 4);
@ -104,10 +102,10 @@ namespace SixLabors.ImageSharp
Vector<float> f2 = Unsafe.Add(ref s, 2);
Vector<float> f3 = Unsafe.Add(ref s, 3);
Vector<uint> w0 = ConvertToUInt32(f0, scale);
Vector<uint> w1 = ConvertToUInt32(f1, scale);
Vector<uint> w2 = ConvertToUInt32(f2, scale);
Vector<uint> w3 = ConvertToUInt32(f3, scale);
Vector<uint> w0 = ConvertToUInt32(f0);
Vector<uint> w1 = ConvertToUInt32(f1);
Vector<uint> w2 = ConvertToUInt32(f2);
Vector<uint> w3 = ConvertToUInt32(f3);
Vector<ushort> u0 = Vector.Narrow(w0, w1);
Vector<ushort> u1 = Vector.Narrow(w2, w3);
@ -119,10 +117,12 @@ namespace SixLabors.ImageSharp
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector<uint> ConvertToUInt32(Vector<float> vf, Vector<float> scale)
private static Vector<uint> ConvertToUInt32(Vector<float> vf)
{
vf = Vector.Min(Vector.Max(vf, Vector<float>.Zero), Vector<float>.One);
vf *= scale;
Vector<float> maxBytes = new Vector<float>(255f);
vf *= maxBytes;
vf += new Vector<float>(0.5f);
vf = Vector.Min(Vector.Max(vf, Vector<float>.Zero), maxBytes);
Vector<int> vi = Vector.ConvertToInt32(vf);
return Vector.AsVectorUInt32(vi);
}

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

@ -52,22 +52,13 @@ namespace SixLabors.ImageSharp.PixelFormats
return;
}
int remainder = count % 2;
int alignedCount = count - remainder;
if (alignedCount > 0)
if (SimdUtils.ExtendedIntrinsics.IsAvailable)
{
ReadOnlySpan<float> rawSrc = MemoryMarshal.Cast<Vector4, float>(sourceVectors.Slice(0, alignedCount));
Span<byte> rawDest = MemoryMarshal.Cast<Rgba32, byte>(destinationColors);
SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(rawSrc, rawDest);
ConvertFromVector4ExtendedIntrinsics(sourceVectors, destinationColors, count);
}
if (remainder > 0)
else
{
// actually: remainder == 1
int lastIdx = count - 1;
destinationColors[lastIdx].PackFromVector4(sourceVectors[lastIdx]);
ConvertFromVector4StandardIntrinsics(sourceVectors, destinationColors, count);
}
}
@ -144,6 +135,46 @@ namespace SixLabors.ImageSharp.PixelFormats
destinationVectors[lastIdx] = sourceColors[lastIdx].ToVector4();
}
}
private static void ConvertFromVector4ExtendedIntrinsics(ReadOnlySpan<Vector4> sourceVectors, Span<Rgba32> destinationColors, int count)
{
int remainder = count % 8;
int alignedCount = count - remainder;
if (alignedCount > 0)
{
ReadOnlySpan<float> rawSrc = MemoryMarshal.Cast<Vector4, float>(sourceVectors);
Span<byte> rawDest = MemoryMarshal.Cast<Rgba32, byte>(destinationColors.Slice(0, alignedCount));
SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(rawSrc, rawDest);
}
if (remainder > 0)
{
PackFromVector4Common(sourceVectors.Slice(alignedCount), destinationColors.Slice(alignedCount), remainder);
}
}
private static void ConvertFromVector4StandardIntrinsics(ReadOnlySpan<Vector4> sourceVectors, Span<Rgba32> destinationColors, int count)
{
int remainder = count % 2;
int alignedCount = count - remainder;
if (alignedCount > 0)
{
ReadOnlySpan<float> rawSrc = MemoryMarshal.Cast<Vector4, float>(sourceVectors.Slice(0, alignedCount));
Span<byte> rawDest = MemoryMarshal.Cast<Rgba32, byte>(destinationColors);
SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(rawSrc, rawDest);
}
if (remainder > 0)
{
// actually: remainder == 1
int lastIdx = count - 1;
destinationColors[lastIdx].PackFromVector4(sourceVectors[lastIdx]);
}
}
}
}
}

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

@ -160,31 +160,6 @@ namespace SixLabors.ImageSharp.Tests.Common
Assert.Equal(expected, dest);
}
private static float Clamp255(float x) => Math.Min(255f, Math.Max(0f, x));
[Theory]
[InlineData(1, 0)]
[InlineData(1, 8)]
[InlineData(2, 16)]
[InlineData(3, 128)]
public void BulkConvertNormalizedFloatToByteClampOverflows(int seed, int count)
{
if (this.SkipOnNonAvx2())
{
return;
}
float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, -50, 444);
float[] normalized = orig.Select(f => f / 255f).ToArray();
byte[] dest = new byte[count];
SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(normalized, dest);
byte[] expected = orig.Select(f => (byte)Clamp255(f)).ToArray();
Assert.Equal(expected, dest);
}
[Theory]
[InlineData(1, 0)]
@ -222,23 +197,44 @@ namespace SixLabors.ImageSharp.Tests.Common
Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f));
}
public static readonly TheoryData<int> BulkConvertNormalizedFloatToByteClampOverflows_Data =
new TheoryData<int>
{
0, 64, 1024
};
[Theory]
[InlineData(1, 0)]
[InlineData(2, 32)]
[InlineData(3, 128)]
public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int seed, int count)
[MemberData(nameof(BulkConvertNormalizedFloatToByteClampOverflows_Data))]
public void BulkConvertNormalizedFloatToByteClampOverflows(int count)
{
float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, -50, 444);
float[] normalized = orig.Select(f => f / 255f).ToArray();
if (this.SkipOnNonAvx2())
{
return;
}
byte[] dest = new byte[count];
float[] source = new Random(count).GenerateRandomFloatArray(count, -0.1f, 1.2f);
byte[] expected = source.Select(NormalizedFloatToByte).ToArray();
byte[] actual = new byte[count];
SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(normalized, dest);
SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(source, actual);
byte[] expected = orig.Select(f => (byte)Clamp255(f)).ToArray();
Assert.Equal(expected, actual);
}
Assert.Equal(expected, dest);
[Theory]
[MemberData(nameof(BulkConvertNormalizedFloatToByteClampOverflows_Data))]
public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count)
{
float[] source = new Random(count).GenerateRandomFloatArray(count, -0.1f, 1.2f);
byte[] expected = source.Select(NormalizedFloatToByte).ToArray();
byte[] actual = new byte[count];
SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(source, actual);
Assert.Equal(expected, actual);
}
private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, f * 255f + 0.5f));
[Theory]
[InlineData(0)]
@ -265,7 +261,7 @@ namespace SixLabors.ImageSharp.Tests.Common
float[] source = { 0, 7, 42, 255, 0.5f, 1.1f, 2.6f, 16f };
var expected = source.Select(f => (byte)Math.Round(f)).ToArray();
byte[] expected = source.Select(f => (byte)Math.Round(f)).ToArray();
source = source.Select(f => f / 255f).ToArray();
@ -299,8 +295,6 @@ namespace SixLabors.ImageSharp.Tests.Common
iiRef = x;
//Tuple8.OfUInt32 ii = Unsafe.As<Vector<float>, Tuple8.OfUInt32>(ref x);
ref Tuple8.OfByte d = ref MemoryMarshal.Cast<byte, Tuple8.OfByte>(dest)[0];
d.LoadFrom(ref ii);

9
tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs

@ -33,19 +33,20 @@ namespace SixLabors.ImageSharp.Tests
return values;
}
public static float[] GenerateRandomRoundedFloatArray(this Random rnd, int length, int minVal, int maxValExclusive)
public static float[] GenerateRandomRoundedFloatArray(this Random rnd, int length, float minVal, float maxVal)
{
float[] values = new float[length];
for (int i = 0; i < length; i++)
{
int val = rnd.Next(minVal, maxValExclusive);
values[i] = (float)val;
values[i] = (float) Math.Round(rnd.GetRandomFloat(minVal, maxVal));
}
return values;
}
public static byte[] GenerateRandomByteArray(this Random rnd, int length)
{
byte[] values = new byte[length];
@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests
return values;
}
private static float GetRandomFloat(Random rnd, float minVal, float maxVal)
private static float GetRandomFloat(this Random rnd, float minVal, float maxVal)
{
return (float)rnd.NextDouble() * (maxVal - minVal) + minVal;
}

Loading…
Cancel
Save