Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix alpha companding.
  • Loading branch information
JimBobSquarePants committed Dec 18, 2020
commit 69562563bccc01b7a21396dfd4ad85becbbfc755
16 changes: 8 additions & 8 deletions src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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[]>(() =>
{
Expand Down Expand Up @@ -129,10 +129,10 @@ public static unsafe void Compress(Span<Vector4> vectors)
[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>
Expand All @@ -142,10 +142,10 @@ public static void Expand(ref Vector4 vector)
[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>
Expand Down Expand Up @@ -192,7 +192,9 @@ private static unsafe void CompandAvx2(Span<Vector4> vectors, float[] table)
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);
}
}
Expand All @@ -216,17 +218,15 @@ private static unsafe void CompandScalar(Span<Vector4> vectors, float[] table)
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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp/Common/Helpers/Numerics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
Expand Down Expand Up @@ -45,4 +45,4 @@ internal static void ApplyBackwardConversionModifiers(Span<Vector4> vectors, Pix
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,23 +160,17 @@ public void FromScaledVector4(int count)
(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);
});
}

[Theory]
[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));
Expand Down Expand Up @@ -219,7 +213,8 @@ void ExpectedAction(ref Vector4 v)
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);
Expand Down Expand Up @@ -254,7 +249,8 @@ void ExpectedAction(ref Vector4 v)
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;

this.Operations.FromVector4Destructive(
Expand Down Expand Up @@ -297,7 +293,8 @@ void ExpectedAction(ref Vector4 v)
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;

this.Operations.FromVector4Destructive(
Expand Down Expand Up @@ -343,7 +340,7 @@ public void Generic_To<TDestPixel>(TestPixel<TDestPixel> dummy)

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]
Expand All @@ -356,11 +353,11 @@ public void ToScaledVector4(int count)
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]
Expand All @@ -369,13 +366,9 @@ public void ToCompandedScaledVector4(int count)
{
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));
Expand All @@ -396,13 +389,9 @@ public void ToPremultipliedVector4(int count)
{
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));
Expand All @@ -419,13 +408,9 @@ public void ToPremultipliedScaledVector4(int count)
{
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));
Expand All @@ -446,8 +431,6 @@ public void ToCompandedPremultipliedScaledVector4(int count)
{
void SourceAction(ref Vector4 v)
{
Numerics.UnPremultiply(ref v);
SRgbCompanding.Compress(ref v);
}

void ExpectedAction(ref Vector4 v)
Expand Down Expand Up @@ -1006,15 +989,9 @@ public void ToRgba64Bytes(int count)
[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);

Expand Down Expand Up @@ -1071,7 +1048,7 @@ internal static Vector4[] CreateVector4TestData(int length, RefAction<Vector4> v

for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v);

result[i] = v;
Expand All @@ -1088,7 +1065,7 @@ internal static TPixel[] CreatePixelTestData(int length, RefAction<Vector4> vect

for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);

vectorModifier?.Invoke(ref v);

Expand All @@ -1106,7 +1083,7 @@ internal static TPixel[] CreateScaledPixelTestData(int length, RefAction<Vector4

for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);

vectorModifier?.Invoke(ref v);

Expand All @@ -1129,10 +1106,8 @@ internal static byte[] CreateByteTestData(int length, int seed = 42)
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
Expand Down Expand Up @@ -1177,14 +1152,22 @@ public void Verify()
{
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
Expand Down
32 changes: 23 additions & 9 deletions tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;

namespace SixLabors.ImageSharp.Tests
{
Expand All @@ -12,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests
internal readonly struct ApproximateFloatComparer :
IEqualityComparer<float>,
IEqualityComparer<Vector2>,
IEqualityComparer<IPixel>,
IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix>
{
Expand All @@ -32,30 +34,42 @@ public bool Equals(float x, float y)
}

/// <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();
Expand Down