Browse Source

Fix alpha companding.

js/color-alpha-handling^2
James Jackson-South 5 years ago
parent
commit
016de29898
  1. 16
      src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs
  2. 2
      src/ImageSharp/Common/Helpers/Numerics.cs
  3. 4
      src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs
  4. 89
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs
  5. 32
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

16
src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs

@ -22,8 +22,8 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// </remarks>
public static class SRgbCompanding
{
private const int Length = Scale + 2;
private const int Scale = (1 << 14) - 1;
private const int Length = Scale + 2; // 256kb @ 16bit precision.
private const int Scale = (1 << 16) - 1;
private static readonly Lazy<float[]> LazyCompressTable = new Lazy<float[]>(() =>
{
@ -129,10 +129,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Expand(ref Vector4 vector)
{
// Alpha is already a linear representation of opacity so we do not want to convert it.
vector.X = Expand(vector.X);
vector.Y = Expand(vector.Y);
vector.Z = Expand(vector.Z);
vector.W = Expand(vector.W);
}
/// <summary>
@ -142,10 +142,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(ref Vector4 vector)
{
// Alpha is already a linear representation of opacity so we do not want to convert it.
vector.X = Compress(vector.X);
vector.Y = Compress(vector.Y);
vector.Z = Compress(vector.Z);
vector.W = Compress(vector.W);
}
/// <summary>
@ -192,7 +192,9 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
Vector256<float> low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float));
Vector256<float> high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float));
vectorsBase = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF));
// Alpha is already a linear representation of opacity so we do not want to convert it.
Vector256<float> companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF));
vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl);
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
}
}
@ -216,17 +218,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
float f0 = multiplied.X;
float f1 = multiplied.Y;
float f2 = multiplied.Z;
float f3 = multiplied.W;
uint i0 = (uint)f0;
uint i1 = (uint)f1;
uint i2 = (uint)f2;
uint i3 = (uint)f3;
// Alpha is already a linear representation of opacity so we do not want to convert it.
vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0);
vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1);
vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2);
vectorsBase.W = Numerics.Lerp(tablePointer[i3], tablePointer[i3 + 1], f3 - (int)i3);
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
}

2
src/ImageSharp/Common/Helpers/Numerics.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp
internal static class Numerics
{
#if SUPPORTS_RUNTIME_INTRINSICS
private const int BlendAlphaControl = 0b_10_00_10_00;
public const int BlendAlphaControl = 0b_10_00_10_00;
private const int ShuffleAlphaControl = 0b_11_11_11_11;
#endif

4
src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -45,4 +45,4 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils
}
}
}
}
}

89
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs

@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
(s, d) =>
{
Span<TPixel> destPixels = d.GetSpan();
this.Operations.FromVector4Destructive(this.Configuration, (Span<Vector4>)s, destPixels, PixelConversionModifiers.Scale);
this.Operations.FromVector4Destructive(this.Configuration, s, destPixels, PixelConversionModifiers.Scale);
});
}
@ -168,15 +168,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
[MemberData(nameof(ArraySizesData))]
public void FromCompandedScaledVector4(int count)
{
void SourceAction(ref Vector4 v)
{
SRgbCompanding.Expand(ref v);
}
void SourceAction(ref Vector4 v) => SRgbCompanding.Expand(ref v);
void ExpectedAction(ref Vector4 v)
{
SRgbCompanding.Compress(ref v);
}
void ExpectedAction(ref Vector4 v) => SRgbCompanding.Compress(ref v);
Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v));
TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -219,7 +213,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers);
@ -254,7 +249,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(
@ -297,7 +293,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(
@ -343,7 +340,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
PixelConverterTests.ReferenceImplementations.To<TPixel, TDestPixel>(this.Configuration, source, expected);
TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, (ReadOnlySpan<TPixel>)s, d.GetSpan()));
TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()));
}
[Theory]
@ -356,11 +353,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
TestOperation(
source,
expected,
(s, d) =>
{
Span<Vector4> destVectors = d.GetSpan();
this.Operations.ToVector4(this.Configuration, (ReadOnlySpan<TPixel>)s, destVectors, PixelConversionModifiers.Scale);
});
(s, d) => this.Operations.ToVector4(
this.Configuration,
s,
d.GetSpan(),
PixelConversionModifiers.Scale));
}
[Theory]
@ -369,13 +366,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
SRgbCompanding.Compress(ref v);
}
void ExpectedAction(ref Vector4 v)
{
SRgbCompanding.Expand(ref v);
}
void ExpectedAction(ref Vector4 v) => SRgbCompanding.Expand(ref v);
TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -396,13 +389,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
Numerics.UnPremultiply(ref v);
}
void ExpectedAction(ref Vector4 v)
{
Numerics.Premultiply(ref v);
}
void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -419,13 +408,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
Numerics.UnPremultiply(ref v);
}
void ExpectedAction(ref Vector4 v)
{
Numerics.Premultiply(ref v);
}
void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -446,8 +431,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
Numerics.UnPremultiply(ref v);
SRgbCompanding.Compress(ref v);
}
void ExpectedAction(ref Vector4 v)
@ -1006,15 +989,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromRgbPlanes(int count)
{
SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
=> SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
count,
(
r,
g,
b,
actual) => PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual));
}
(r, g, b, actual) => PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual));
public delegate void RefAction<T1>(ref T1 arg1);
@ -1071,7 +1048,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v);
result[i] = v;
@ -1088,7 +1065,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v);
@ -1106,7 +1083,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v);
@ -1129,10 +1106,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
return result;
}
internal static Vector4 GetVector(Random rnd)
{
return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble());
}
internal static Vector4 GetScaledVector(Random rnd)
=> new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble());
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct OctetBytes
@ -1177,14 +1152,22 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
Span<Vector4> expected = MemoryMarshal.Cast<TDest, Vector4>(this.ExpectedDestBuffer.AsSpan());
Span<Vector4> actual = MemoryMarshal.Cast<TDest, Vector4>(this.ActualDestBuffer.GetSpan());
var comparer = new ApproximateFloatComparer(0.0001F);
var comparer = new ApproximateFloatComparer(0.001f);
for (int i = 0; i < count; i++)
{
// ReSharper disable PossibleNullReferenceException
Assert.Equal(expected[i], actual[i], comparer);
}
}
else if (typeof(IPixel).IsAssignableFrom(typeof(TDest)))
{
Span<TDest> expected = this.ExpectedDestBuffer.AsSpan();
Span<TDest> actual = this.ActualDestBuffer.GetSpan();
var comparer = new ApproximateFloatComparer(0.0001F);
// ReSharper restore PossibleNullReferenceException
for (int i = 0; i < count; i++)
{
Assert.Equal((IPixel)expected[i], (IPixel)actual[i], comparer);
}
}
else

32
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
@ -12,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests
internal readonly struct ApproximateFloatComparer :
IEqualityComparer<float>,
IEqualityComparer<Vector2>,
IEqualityComparer<IPixel>,
IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix>
{
@ -32,30 +34,42 @@ namespace SixLabors.ImageSharp.Tests
}
/// <inheritdoc/>
public int GetHashCode(float obj) => obj.GetHashCode();
public int GetHashCode(float obj)
=> obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y);
public bool Equals(Vector2 x, Vector2 y)
=> this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y);
/// <inheritdoc/>
public int GetHashCode(Vector2 obj) => obj.GetHashCode();
public int GetHashCode(Vector2 obj)
=> obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W);
public bool Equals(IPixel x, IPixel y)
=> this.Equals(x.ToScaledVector4(), y.ToScaledVector4());
public int GetHashCode(IPixel obj)
=> obj.ToScaledVector4().GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector4 x, Vector4 y)
=> this.Equals(x.X, y.X)
&& this.Equals(x.Y, y.Y)
&& this.Equals(x.Z, y.Z)
&& this.Equals(x.W, y.W);
/// <inheritdoc/>
public int GetHashCode(Vector4 obj) => obj.GetHashCode();
public int GetHashCode(Vector4 obj)
=> obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(ColorMatrix x, ColorMatrix y)
{
return
this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14)
=> this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14)
&& this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24)
&& this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34)
&& this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44)
&& this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54);
}
/// <inheritdoc/>
public int GetHashCode(ColorMatrix obj) => obj.GetHashCode();

Loading…
Cancel
Save