Browse Source

Inplace JpegColorConverter basic infra

pull/1773/head
Anton Firszov 4 years ago
parent
commit
033ed8e1b8
  1. 28
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs
  2. 43
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs
  3. 27
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs
  4. 25
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs
  5. 28
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  6. 140
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

28
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs

@ -65,8 +65,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
#endif
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
#if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> rBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> gBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> bBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
int n = values.Component0.Length / 8;
for (int i = 0; i < n; i++)
{
ref Vector256<float> r = ref Unsafe.Add(ref rBase, i);
ref Vector256<float> g = ref Unsafe.Add(ref gBase, i);
ref Vector256<float> b = ref Unsafe.Add(ref bBase, i);
r = Avx.Multiply(r, scale);
g = Avx.Multiply(g, scale);
b = Avx.Multiply(b, scale);
}
#endif
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromRgbBasic.ConvertCore(values, result, this.MaximumValue);
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue);
}
}
}

43
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs

@ -3,6 +3,7 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
@ -20,6 +21,48 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
ConvertCore(values, result, this.MaximumValue);
}
public override void ConvertToRgbInplace(in ComponentValues values)
{
ConvertCoreInplace(values, this.MaximumValue);
}
internal static void ConvertCoreInplace(ComponentValues values, float maxValue)
{
// TODO: Optimize this
ConvertComponent(values.Component0, maxValue);
ConvertComponent(values.Component1, maxValue);
ConvertComponent(values.Component2, maxValue);
static void ConvertComponent(Span<float> values, float maxValue)
{
Span<Vector4> vecValues = MemoryMarshal.Cast<float, Vector4>(values);
var scaleVector = new Vector4(1 / maxValue);
for (int i = 0; i < vecValues.Length; i++)
{
vecValues[i] *= scaleVector;
}
values = values.Slice(vecValues.Length * 4);
if (values.Length > 0)
{
float scaleValue = 1f / maxValue;
values[0] *= scaleValue;
if (values.Length > 1)
{
values[1] *= scaleValue;
if (values.Length > 2)
{
values[2] *= scaleValue;
}
}
}
}
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue)
{
ReadOnlySpan<float> rVals = values.Component0;

27
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs

@ -60,8 +60,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{
ref Vector<float> rBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> gBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> bBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
var scale = new Vector<float>(1 / this.MaximumValue);
// Walking 8 elements at one step:
int n = values.Component0.Length / 8;
for (int i = 0; i < n; i++)
{
ref Vector<float> r = ref Unsafe.Add(ref rBase, i);
ref Vector<float> g = ref Unsafe.Add(ref gBase, i);
ref Vector<float> b = ref Unsafe.Add(ref bBase, i);
r *= scale;
g *= scale;
b *= scale;
}
}
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) =>
FromRgbBasic.ConvertCore(values, result, this.MaximumValue);
protected override void ConvertCoreInplace(in ComponentValues values) =>
FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue);
}
}
}

25
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs

@ -38,9 +38,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
this.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder));
}
public override void ConvertToRgbInplace(in ComponentValues values)
{
int length = values.Component0.Length;
int remainder = values.Component0.Length % this.vectorSize;
int simdCount = length - remainder;
if (simdCount > 0)
{
// This implementation is actually AVX specific.
// An AVX register is capable of storing 8 float-s.
if (!this.IsAvailable)
{
throw new InvalidOperationException(
"This converter can be used only on architecture having 256 byte floating point SIMD registers!");
}
this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount));
}
this.ConvertCoreInplace(values.Slice(simdCount, remainder));
}
protected abstract void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result);
protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException();
protected abstract void ConvertCore(in ComponentValues values, Span<Vector4> result);
protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException();
}
}
}

28
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -82,6 +82,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <param name="result">The destination buffer of <see cref="Vector4"/> values</param>
public abstract void ConvertToRgba(in ComponentValues values, Span<Vector4> result);
public virtual void ConvertToRgbInplace(in ComponentValues values) => throw new NotImplementedException();
/// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for all supported colorspaces and precisions.
/// </summary>
@ -181,22 +183,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <summary>
/// The component 0 (eg. Y)
/// </summary>
public readonly ReadOnlySpan<float> Component0;
public readonly Span<float> Component0;
/// <summary>
/// The component 1 (eg. Cb)
/// </summary>
public readonly ReadOnlySpan<float> Component1;
public readonly Span<float> Component1;
/// <summary>
/// The component 2 (eg. Cr)
/// </summary>
public readonly ReadOnlySpan<float> Component2;
public readonly Span<float> Component2;
/// <summary>
/// The component 4
/// </summary>
public readonly ReadOnlySpan<float> Component3;
public readonly Span<float> Component3;
/// <summary>
/// Initializes a new instance of the <see cref="ComponentValues"/> struct.
@ -226,12 +228,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
private ComponentValues(
internal ComponentValues(
int componentCount,
ReadOnlySpan<float> c0,
ReadOnlySpan<float> c1,
ReadOnlySpan<float> c2,
ReadOnlySpan<float> c3)
Span<float> c0,
Span<float> c1,
Span<float> c2,
Span<float> c3)
{
this.ComponentCount = componentCount;
this.Component0 = c0;
@ -242,10 +244,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
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;
Span<float> c0 = this.Component0.Slice(start, length);
Span<float> c1 = this.ComponentCount > 1 ? this.Component1.Slice(start, length) : Span<float>.Empty;
Span<float> c2 = this.ComponentCount > 2 ? this.Component2.Slice(start, length) : Span<float>.Empty;
Span<float> c3 = this.ComponentCount > 3 ? this.Component3.Slice(start, length) : Span<float>.Empty;
return new ComponentValues(this.ComponentCount, c0, c1, c2, c3);
}

140
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -416,39 +416,77 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
int resultBufferLength,
int seed)
{
JpegColorConverter.ComponentValues values = CreateRandomValues(componentCount, inputBufferLength, seed);
var result = new Vector4[resultBufferLength];
JpegColorConverter.ComponentValues original = CreateRandomValues(componentCount, inputBufferLength, seed);
JpegColorConverter.ComponentValues values = Copy(original);
converter.ConvertToRgba(values, result);
converter.ConvertToRgbInplace(values);
for (int i = 0; i < resultBufferLength; i++)
{
Validate(converter.ColorSpace, values, result, i);
Validate(converter.ColorSpace, original, values, i);
}
static JpegColorConverter.ComponentValues Copy(JpegColorConverter.ComponentValues values)
{
Span<float> c0 = values.Component0.ToArray();
Span<float> c1 = values.ComponentCount > 1 ? values.Component1.ToArray().AsSpan() : default;
Span<float> c2 = values.ComponentCount > 2 ? values.Component2.ToArray().AsSpan() : default;
Span<float> c3 = values.ComponentCount > 3 ? values.Component3.ToArray().AsSpan() : default;
return new JpegColorConverter.ComponentValues(values.ComponentCount, c0, c1, c2, c3);
}
}
private static void Validate(
JpegColorSpace colorSpace,
in JpegColorConverter.ComponentValues values,
in JpegColorConverter.ComponentValues original,
Vector4[] result,
int i)
{
switch (colorSpace)
{
case JpegColorSpace.Grayscale:
ValidateGrayScale(values, result, i);
ValidateGrayScale(original, result, i);
break;
case JpegColorSpace.Ycck:
ValidateCyyK(values, result, i);
ValidateCyyK(original, result, i);
break;
case JpegColorSpace.Cmyk:
ValidateCmyk(values, result, i);
ValidateCmyk(original, result, i);
break;
case JpegColorSpace.RGB:
ValidateRgb(values, result, i);
ValidateRgb(original, result, i);
break;
case JpegColorSpace.YCbCr:
ValidateYCbCr(values, result, i);
ValidateYCbCr(original, result, i);
break;
default:
Assert.True(false, $"Colorspace {colorSpace} not supported!");
break;
}
}
private static void Validate(
JpegColorSpace colorSpace,
in JpegColorConverter.ComponentValues original,
in JpegColorConverter.ComponentValues result,
int i)
{
switch (colorSpace)
{
case JpegColorSpace.Grayscale:
ValidateGrayScale(original, result, i);
break;
case JpegColorSpace.Ycck:
ValidateCyyK(original, result, i);
break;
case JpegColorSpace.Cmyk:
ValidateCmyk(original, result, i);
break;
case JpegColorSpace.RGB:
ValidateRgb(original, result, i);
break;
case JpegColorSpace.YCbCr:
ValidateYCbCr(original, result, i);
break;
default:
Assert.True(false, $"Colorspace {colorSpace} not supported!");
@ -471,6 +509,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(1, rgba.W);
}
private static void ValidateYCbCr(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues 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);
var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]);
var expected = ColorSpaceConverter.ToRgb(ycbcr);
Assert.Equal(expected, actual, ColorSpaceComparer);
}
private static void ValidateCyyK(in JpegColorConverter.ComponentValues values, Vector4[] result, int i)
{
var v = new Vector4(0, 0, 0, 1F);
@ -498,6 +549,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(1, rgba.W);
}
private static void ValidateCyyK(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i)
{
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
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 - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k;
v.Y = (255F - (float)Math.Round(
y - (0.344136F * cb) - (0.714136F * cr),
MidpointRounding.AwayFromZero)) * k;
v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k;
v.W = 1F;
v *= scale;
var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]);
var expected = new Rgb(v.X, v.Y, v.Z);
Assert.Equal(expected, actual, ColorSpaceComparer);
}
private static void ValidateRgb(in JpegColorConverter.ComponentValues values, Vector4[] result, int i)
{
float r = values.Component0[i];
@ -511,6 +587,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(1, rgba.W);
}
private static void ValidateRgb(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i)
{
float r = values.Component0[i];
float g = values.Component1[i];
float b = values.Component2[i];
var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]);
var expected = new Rgb(r / 255F, g / 255F, b / 255F);
Assert.Equal(expected, actual, ColorSpaceComparer);
}
private static void ValidateGrayScale(in JpegColorConverter.ComponentValues values, Vector4[] result, int i)
{
float y = values.Component0[i];
@ -522,6 +610,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(1, rgba.W);
}
private static void ValidateGrayScale(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i)
{
float y = values.Component0[i];
var actual = new Rgb(result.Component0[i], result.Component0[i], result.Component0[i]);
var expected = new Rgb(y / 255F, y / 255F, y / 255F);
Assert.Equal(expected, actual, ColorSpaceComparer);
}
private static void ValidateCmyk(in JpegColorConverter.ComponentValues values, Vector4[] result, int i)
{
var v = new Vector4(0, 0, 0, 1F);
@ -546,5 +643,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(expected, actual, ColorSpaceComparer);
Assert.Equal(1, rgba.W);
}
private static void ValidateCmyk(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i)
{
var v = new Vector4(0, 0, 0, 1F);
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F);
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;
var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]);
var expected = new Rgb(v.X, v.Y, v.Z);
Assert.Equal(expected, actual, ColorSpaceComparer);
}
}
}

Loading…
Cancel
Save