|
|
|
@ -1,26 +1,27 @@ |
|
|
|
using System; |
|
|
|
using System.Numerics; |
|
|
|
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
|
|
|
{ |
|
|
|
using System; |
|
|
|
using System.Numerics; |
|
|
|
|
|
|
|
using SixLabors.ImageSharp.ColorSpaces; |
|
|
|
using SixLabors.ImageSharp.ColorSpaces.Conversion; |
|
|
|
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; |
|
|
|
using SixLabors.ImageSharp.Memory; |
|
|
|
using SixLabors.ImageSharp.ColorSpaces; |
|
|
|
using SixLabors.ImageSharp.ColorSpaces.Conversion; |
|
|
|
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; |
|
|
|
using SixLabors.ImageSharp.Memory; |
|
|
|
|
|
|
|
using Xunit; |
|
|
|
using Xunit.Abstractions; |
|
|
|
using Xunit; |
|
|
|
using Xunit.Abstractions; |
|
|
|
|
|
|
|
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
|
|
|
{ |
|
|
|
public class JpegColorConverterTests |
|
|
|
{ |
|
|
|
private const float Precision = 0.1f; |
|
|
|
private const float Precision = 0.01f; |
|
|
|
|
|
|
|
private const int InputBufferLength = 42; |
|
|
|
|
|
|
|
// The result buffer could be shorter
|
|
|
|
private const int ResultBufferLength = 40; |
|
|
|
|
|
|
|
private readonly Vector4[] result = new Vector4[ResultBufferLength]; |
|
|
|
public static readonly TheoryData<int, int, int> CommonConversionData = |
|
|
|
new TheoryData<int, int, int> |
|
|
|
{ |
|
|
|
{ 40, 40, 1 }, |
|
|
|
{ 42, 40, 2 }, |
|
|
|
{ 42, 39, 3 } |
|
|
|
}; |
|
|
|
|
|
|
|
private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); |
|
|
|
|
|
|
|
@ -31,161 +32,193 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
|
|
|
|
|
|
|
private ITestOutputHelper Output { get; } |
|
|
|
|
|
|
|
private static JpegColorConverter.ComponentValues CreateRandomValues(int componentCount, float maxVal = 255f) |
|
|
|
[Theory] |
|
|
|
[MemberData(nameof(CommonConversionData))] |
|
|
|
public void ConvertFromYCbCr(int inputBufferLength, int resultBufferLength, int seed) |
|
|
|
{ |
|
|
|
var rnd = new Random(42); |
|
|
|
Buffer2D<float>[] buffers = new Buffer2D<float>[componentCount]; |
|
|
|
for (int i = 0; i < componentCount; i++) |
|
|
|
{ |
|
|
|
float[] values = new float[InputBufferLength]; |
|
|
|
|
|
|
|
for (int j = 0; j < InputBufferLength; j++) |
|
|
|
{ |
|
|
|
values[j] = (float)rnd.NextDouble() * maxVal; |
|
|
|
} |
|
|
|
|
|
|
|
// no need to dispose when buffer is not array owner
|
|
|
|
buffers[i] = new Buffer2D<float>(values, values.Length, 1); |
|
|
|
} |
|
|
|
return new JpegColorConverter.ComponentValues(buffers, 0); |
|
|
|
ValidateConversion( |
|
|
|
JpegColorSpace.YCbCr, |
|
|
|
3, |
|
|
|
inputBufferLength, |
|
|
|
resultBufferLength, |
|
|
|
seed, |
|
|
|
(values, result, i) => |
|
|
|
{ |
|
|
|
float y = values.Component0[i]; |
|
|
|
float cb = values.Component1[i]; |
|
|
|
float cr = values.Component2[i]; |
|
|
|
var ycbcr = new YCbCr(y, cb, cr); |
|
|
|
|
|
|
|
Vector4 rgba = result[i]; |
|
|
|
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); |
|
|
|
var expected = ColorSpaceConverter.ToRgb(ycbcr); |
|
|
|
|
|
|
|
Assert.True(actual.AlmostEquals(expected, Precision)); |
|
|
|
Assert.Equal(1, rgba.W); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void ConvertFromYCbCr() |
|
|
|
[Theory] |
|
|
|
[MemberData(nameof(CommonConversionData))] |
|
|
|
public void ConvertFromCmyk(int inputBufferLength, int resultBufferLength, int seed) |
|
|
|
{ |
|
|
|
var converter = JpegColorConverter.GetConverter(JpegColorSpace.YCbCr); |
|
|
|
|
|
|
|
JpegColorConverter.ComponentValues values = CreateRandomValues(3); |
|
|
|
|
|
|
|
converter.ConvertToRGBA(values, this.result); |
|
|
|
|
|
|
|
for (int i = 0; i < ResultBufferLength; i++) |
|
|
|
{ |
|
|
|
float y = values.Component0[i]; |
|
|
|
float cb = values.Component1[i]; |
|
|
|
float cr = values.Component2[i]; |
|
|
|
var ycbcr = new YCbCr(y, cb, cr); |
|
|
|
|
|
|
|
Vector4 rgba = this.result[i]; |
|
|
|
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); |
|
|
|
var expected = ColorSpaceConverter.ToRgb(ycbcr); |
|
|
|
|
|
|
|
Assert.True(actual.AlmostEquals(expected, Precision)); |
|
|
|
Assert.Equal(1, rgba.W); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void ConvertFromCmyk() |
|
|
|
{ |
|
|
|
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk); |
|
|
|
|
|
|
|
JpegColorConverter.ComponentValues values = CreateRandomValues(4); |
|
|
|
|
|
|
|
converter.ConvertToRGBA(values, this.result); |
|
|
|
|
|
|
|
var v = new Vector4(0, 0, 0, 1F); |
|
|
|
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); |
|
|
|
|
|
|
|
for (int i = 0; i < ResultBufferLength; i++) |
|
|
|
{ |
|
|
|
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; |
|
|
|
|
|
|
|
Vector4 rgba = this.result[i]; |
|
|
|
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); |
|
|
|
var expected = new Rgb(v.X, v.Y, v.Z); |
|
|
|
|
|
|
|
Assert.True(actual.AlmostEquals(expected, Precision)); |
|
|
|
Assert.Equal(1, rgba.W); |
|
|
|
} |
|
|
|
ValidateConversion( |
|
|
|
JpegColorSpace.Cmyk, |
|
|
|
4, |
|
|
|
inputBufferLength, |
|
|
|
resultBufferLength, |
|
|
|
seed, |
|
|
|
(values, result, i) => |
|
|
|
{ |
|
|
|
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; |
|
|
|
|
|
|
|
Vector4 rgba = result[i]; |
|
|
|
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); |
|
|
|
var expected = new Rgb(v.X, v.Y, v.Z); |
|
|
|
|
|
|
|
Assert.True(actual.AlmostEquals(expected, Precision)); |
|
|
|
Assert.Equal(1, rgba.W); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void ConvertFromYcck() |
|
|
|
[Theory] |
|
|
|
[MemberData(nameof(CommonConversionData))] |
|
|
|
public void ConvertFromGrayScale(int inputBufferLength, int resultBufferLength, int seed) |
|
|
|
{ |
|
|
|
var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck); |
|
|
|
|
|
|
|
JpegColorConverter.ComponentValues values = CreateRandomValues(4); |
|
|
|
ValidateConversion( |
|
|
|
JpegColorSpace.GrayScale, |
|
|
|
1, |
|
|
|
inputBufferLength, |
|
|
|
resultBufferLength, |
|
|
|
seed, |
|
|
|
(values, result, i) => |
|
|
|
{ |
|
|
|
float y = values.Component0[i]; |
|
|
|
Vector4 rgba = result[i]; |
|
|
|
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); |
|
|
|
var expected = new Rgb(y / 255F, y / 255F, y / 255F); |
|
|
|
|
|
|
|
Assert.True(actual.AlmostEquals(expected, Precision)); |
|
|
|
Assert.Equal(1, rgba.W); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
converter.ConvertToRGBA(values, this.result); |
|
|
|
[Theory] |
|
|
|
[MemberData(nameof(CommonConversionData))] |
|
|
|
public void ConvertFromRgb(int inputBufferLength, int resultBufferLength, int seed) |
|
|
|
{ |
|
|
|
ValidateConversion( |
|
|
|
JpegColorSpace.RGB, |
|
|
|
3, |
|
|
|
inputBufferLength, |
|
|
|
resultBufferLength, |
|
|
|
seed, |
|
|
|
(values, result, i) => |
|
|
|
{ |
|
|
|
float r = values.Component0[i]; |
|
|
|
float g = values.Component1[i]; |
|
|
|
float b = values.Component2[i]; |
|
|
|
Vector4 rgba = result[i]; |
|
|
|
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); |
|
|
|
var expected = new Rgb(r / 255F, g / 255F, b / 255F); |
|
|
|
|
|
|
|
Assert.True(actual.AlmostEquals(expected, Precision)); |
|
|
|
Assert.Equal(1, rgba.W); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
[Theory] |
|
|
|
[MemberData(nameof(CommonConversionData))] |
|
|
|
public void ConvertFromYcck(int inputBufferLength, int resultBufferLength, int seed) |
|
|
|
{ |
|
|
|
var v = new Vector4(0, 0, 0, 1F); |
|
|
|
var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); |
|
|
|
|
|
|
|
for (int i = 0; i < ResultBufferLength; i++) |
|
|
|
{ |
|
|
|
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 - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; |
|
|
|
v.Y = (255F - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; |
|
|
|
v.Z = (255F - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; |
|
|
|
v.W = 1F; |
|
|
|
|
|
|
|
v *= scale; |
|
|
|
|
|
|
|
Vector4 rgba = this.result[i]; |
|
|
|
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); |
|
|
|
var expected = new Rgb(v.X, v.Y, v.Z); |
|
|
|
|
|
|
|
Assert.True(actual.AlmostEquals(expected, Precision)); |
|
|
|
Assert.Equal(1, rgba.W); |
|
|
|
} |
|
|
|
ValidateConversion( |
|
|
|
JpegColorSpace.Ycck, |
|
|
|
4, |
|
|
|
inputBufferLength, |
|
|
|
resultBufferLength, |
|
|
|
seed, |
|
|
|
(values, result, i) => |
|
|
|
{ |
|
|
|
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 - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; |
|
|
|
v.Y = (255F - MathF.Round( |
|
|
|
y - (0.344136F * cb) - (0.714136F * cr), |
|
|
|
MidpointRounding.AwayFromZero)) * k; |
|
|
|
v.Z = (255F - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; |
|
|
|
v.W = 1F; |
|
|
|
|
|
|
|
v *= scale; |
|
|
|
|
|
|
|
Vector4 rgba = result[i]; |
|
|
|
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); |
|
|
|
var expected = new Rgb(v.X, v.Y, v.Z); |
|
|
|
|
|
|
|
Assert.True(actual.AlmostEquals(expected, Precision)); |
|
|
|
Assert.Equal(1, rgba.W); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void ConvertFromGrayScale() |
|
|
|
private static JpegColorConverter.ComponentValues CreateRandomValues( |
|
|
|
int componentCount, |
|
|
|
int inputBufferLength, |
|
|
|
int seed, |
|
|
|
float maxVal = 255f) |
|
|
|
{ |
|
|
|
var converter = JpegColorConverter.GetConverter(JpegColorSpace.GrayScale); |
|
|
|
|
|
|
|
JpegColorConverter.ComponentValues values = CreateRandomValues(1); |
|
|
|
|
|
|
|
converter.ConvertToRGBA(values, this.result); |
|
|
|
|
|
|
|
for (int i = 0; i < ResultBufferLength; i++) |
|
|
|
var rnd = new Random(seed); |
|
|
|
Buffer2D<float>[] buffers = new Buffer2D<float>[componentCount]; |
|
|
|
for (int i = 0; i < componentCount; i++) |
|
|
|
{ |
|
|
|
float y = values.Component0[i]; |
|
|
|
Vector4 rgba = this.result[i]; |
|
|
|
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); |
|
|
|
var expected = new Rgb(y / 255F, y / 255F, y / 255F); |
|
|
|
float[] values = new float[inputBufferLength]; |
|
|
|
|
|
|
|
Assert.True(actual.AlmostEquals(expected, Precision)); |
|
|
|
Assert.Equal(1, rgba.W); |
|
|
|
for (int j = 0; j < inputBufferLength; j++) |
|
|
|
{ |
|
|
|
values[j] = (float)rnd.NextDouble() * maxVal; |
|
|
|
} |
|
|
|
|
|
|
|
// no need to dispose when buffer is not array owner
|
|
|
|
buffers[i] = new Buffer2D<float>(values, values.Length, 1); |
|
|
|
} |
|
|
|
return new JpegColorConverter.ComponentValues(buffers, 0); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void ConvertFromRgb() |
|
|
|
private static void ValidateConversion( |
|
|
|
JpegColorSpace colorSpace, |
|
|
|
int componentCount, |
|
|
|
int inputBufferLength, |
|
|
|
int resultBufferLength, |
|
|
|
int seed, |
|
|
|
Action<JpegColorConverter.ComponentValues, Span<Vector4>, int> doValidate) |
|
|
|
{ |
|
|
|
var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB); |
|
|
|
var converter = JpegColorConverter.GetConverter(colorSpace); |
|
|
|
|
|
|
|
JpegColorConverter.ComponentValues values = CreateRandomValues(3); |
|
|
|
JpegColorConverter.ComponentValues values = CreateRandomValues(componentCount, inputBufferLength, seed); |
|
|
|
Vector4[] result = new Vector4[resultBufferLength]; |
|
|
|
|
|
|
|
converter.ConvertToRGBA(values, this.result); |
|
|
|
converter.ConvertToRGBA(values, result); |
|
|
|
|
|
|
|
for (int i = 0; i < ResultBufferLength; i++) |
|
|
|
for (int i = 0; i < resultBufferLength; i++) |
|
|
|
{ |
|
|
|
float r = values.Component0[i]; |
|
|
|
float g = values.Component1[i]; |
|
|
|
float b = values.Component2[i]; |
|
|
|
Vector4 rgba = this.result[i]; |
|
|
|
var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); |
|
|
|
var expected = new Rgb(r / 255F, g / 255F, b / 255F); |
|
|
|
|
|
|
|
Assert.True(actual.AlmostEquals(expected, Precision)); |
|
|
|
Assert.Equal(1, rgba.W); |
|
|
|
doValidate(values, result, i); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|