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> /// </remarks>
public static class SRgbCompanding public static class SRgbCompanding
{ {
private const int Length = Scale + 2; private const int Length = Scale + 2; // 256kb @ 16bit precision.
private const int Scale = (1 << 14) - 1; private const int Scale = (1 << 16) - 1;
private static readonly Lazy<float[]> LazyCompressTable = new Lazy<float[]>(() => private static readonly Lazy<float[]> LazyCompressTable = new Lazy<float[]>(() =>
{ {
@ -129,10 +129,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Expand(ref Vector4 vector) 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.X = Expand(vector.X);
vector.Y = Expand(vector.Y); vector.Y = Expand(vector.Y);
vector.Z = Expand(vector.Z); vector.Z = Expand(vector.Z);
vector.W = Expand(vector.W);
} }
/// <summary> /// <summary>
@ -142,10 +142,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(ref Vector4 vector) 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.X = Compress(vector.X);
vector.Y = Compress(vector.Y); vector.Y = Compress(vector.Y);
vector.Z = Compress(vector.Z); vector.Z = Compress(vector.Z);
vector.W = Compress(vector.W);
} }
/// <summary> /// <summary>
@ -192,7 +192,9 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
Vector256<float> low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float)); Vector256<float> low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float));
Vector256<float> high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), 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); vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
} }
} }
@ -216,17 +218,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
float f0 = multiplied.X; float f0 = multiplied.X;
float f1 = multiplied.Y; float f1 = multiplied.Y;
float f2 = multiplied.Z; float f2 = multiplied.Z;
float f3 = multiplied.W;
uint i0 = (uint)f0; uint i0 = (uint)f0;
uint i1 = (uint)f1; uint i1 = (uint)f1;
uint i2 = (uint)f2; 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.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0);
vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1); 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.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); 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 internal static class Numerics
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #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; private const int ShuffleAlphaControl = 0b_11_11_11_11;
#endif #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. // Licensed under the Apache License, Version 2.0.
using System; 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) => (s, d) =>
{ {
Span<TPixel> destPixels = d.GetSpan(); 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))] [MemberData(nameof(ArraySizesData))]
public void FromCompandedScaledVector4(int count) public void FromCompandedScaledVector4(int count)
{ {
void SourceAction(ref Vector4 v) void SourceAction(ref Vector4 v) => SRgbCompanding.Expand(ref v);
{
SRgbCompanding.Expand(ref v);
}
void ExpectedAction(ref Vector4 v) void ExpectedAction(ref Vector4 v) => SRgbCompanding.Compress(ref v);
{
SRgbCompanding.Compress(ref v);
}
Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v));
TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -219,7 +213,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected, expected,
(s, d) => (s, d) =>
{ {
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None; : PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers); this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers);
@ -254,7 +249,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected, expected,
(s, d) => (s, d) =>
{ {
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None; : PixelConversionModifiers.None;
this.Operations.FromVector4Destructive( this.Operations.FromVector4Destructive(
@ -297,7 +293,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected, expected,
(s, d) => (s, d) =>
{ {
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None; : PixelConversionModifiers.None;
this.Operations.FromVector4Destructive( this.Operations.FromVector4Destructive(
@ -343,7 +340,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
PixelConverterTests.ReferenceImplementations.To<TPixel, TDestPixel>(this.Configuration, source, expected); 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] [Theory]
@ -356,11 +353,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
TestOperation( TestOperation(
source, source,
expected, expected,
(s, d) => (s, d) => this.Operations.ToVector4(
{ this.Configuration,
Span<Vector4> destVectors = d.GetSpan(); s,
this.Operations.ToVector4(this.Configuration, (ReadOnlySpan<TPixel>)s, destVectors, PixelConversionModifiers.Scale); d.GetSpan(),
}); PixelConversionModifiers.Scale));
} }
[Theory] [Theory]
@ -369,13 +366,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{ {
void SourceAction(ref Vector4 v) void SourceAction(ref Vector4 v)
{ {
SRgbCompanding.Compress(ref v);
} }
void ExpectedAction(ref Vector4 v) void ExpectedAction(ref Vector4 v) => SRgbCompanding.Expand(ref v);
{
SRgbCompanding.Expand(ref v);
}
TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(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) void SourceAction(ref Vector4 v)
{ {
Numerics.UnPremultiply(ref v);
} }
void ExpectedAction(ref Vector4 v) void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
{
Numerics.Premultiply(ref v);
}
TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(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) void SourceAction(ref Vector4 v)
{ {
Numerics.UnPremultiply(ref v);
} }
void ExpectedAction(ref Vector4 v) void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
{
Numerics.Premultiply(ref v);
}
TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(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) void SourceAction(ref Vector4 v)
{ {
Numerics.UnPremultiply(ref v);
SRgbCompanding.Compress(ref v);
} }
void ExpectedAction(ref Vector4 v) void ExpectedAction(ref Vector4 v)
@ -1006,15 +989,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
[Theory] [Theory]
[MemberData(nameof(ArraySizesData))] [MemberData(nameof(ArraySizesData))]
public void PackFromRgbPlanes(int count) public void PackFromRgbPlanes(int count)
{ => SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
count, 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); 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++) for (int i = 0; i < result.Length; i++)
{ {
Vector4 v = GetVector(rnd); Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v); vectorModifier?.Invoke(ref v);
result[i] = v; result[i] = v;
@ -1088,7 +1065,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++) for (int i = 0; i < result.Length; i++)
{ {
Vector4 v = GetVector(rnd); Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v); vectorModifier?.Invoke(ref v);
@ -1106,7 +1083,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++) for (int i = 0; i < result.Length; i++)
{ {
Vector4 v = GetVector(rnd); Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v); vectorModifier?.Invoke(ref v);
@ -1129,10 +1106,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
return result; return result;
} }
internal static Vector4 GetVector(Random rnd) internal static Vector4 GetScaledVector(Random rnd)
{ => new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble());
return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble());
}
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct OctetBytes 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> expected = MemoryMarshal.Cast<TDest, Vector4>(this.ExpectedDestBuffer.AsSpan());
Span<Vector4> actual = MemoryMarshal.Cast<TDest, Vector4>(this.ActualDestBuffer.GetSpan()); 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++) for (int i = 0; i < count; i++)
{ {
// ReSharper disable PossibleNullReferenceException
Assert.Equal(expected[i], actual[i], comparer); 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 else

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

@ -3,6 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests
{ {
@ -12,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests
internal readonly struct ApproximateFloatComparer : internal readonly struct ApproximateFloatComparer :
IEqualityComparer<float>, IEqualityComparer<float>,
IEqualityComparer<Vector2>, IEqualityComparer<Vector2>,
IEqualityComparer<IPixel>,
IEqualityComparer<Vector4>, IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix> IEqualityComparer<ColorMatrix>
{ {
@ -32,30 +34,42 @@ namespace SixLabors.ImageSharp.Tests
} }
/// <inheritdoc/> /// <inheritdoc/>
public int GetHashCode(float obj) => obj.GetHashCode(); public int GetHashCode(float obj)
=> obj.GetHashCode();
/// <inheritdoc/> /// <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/> /// <inheritdoc/>
public int GetHashCode(Vector2 obj) => obj.GetHashCode(); public int GetHashCode(Vector2 obj)
=> obj.GetHashCode();
/// <inheritdoc/> /// <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/> /// <inheritdoc/>
public int GetHashCode(Vector4 obj) => obj.GetHashCode(); public int GetHashCode(Vector4 obj)
=> obj.GetHashCode();
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(ColorMatrix x, ColorMatrix y) public bool Equals(ColorMatrix x, ColorMatrix y)
{ => this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14)
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.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24) && 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.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.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); && 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/> /// <inheritdoc/>
public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); public int GetHashCode(ColorMatrix obj) => obj.GetHashCode();

Loading…
Cancel
Save