Browse Source

Merge branch 'master' into webp

pull/1552/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
569ad469d1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 38
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  2. 8
      src/ImageSharp/Formats/Png/PngEncoderOptions.cs
  3. 12
      src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
  4. 90
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  5. 1
      tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs

38
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -268,35 +268,27 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.use16Bit) if (this.use16Bit)
{ {
// 16 bit grayscale + alpha // 16 bit grayscale + alpha
// TODO: Should we consider in the future a GrayAlpha32 type. using IMemoryOwner<La32> laBuffer = this.memoryAllocator.Allocate<La32>(rowSpan.Length);
using (IMemoryOwner<Rgba64> rgbaBuffer = this.memoryAllocator.Allocate<Rgba64>(rowSpan.Length)) Span<La32> laSpan = laBuffer.GetSpan();
{ ref La32 laRef = ref MemoryMarshal.GetReference(laSpan);
Span<Rgba64> rgbaSpan = rgbaBuffer.GetSpan(); PixelOperations<TPixel>.Instance.ToLa32(this.configuration, rowSpan, laSpan);
ref Rgba64 rgbaRef = ref MemoryMarshal.GetReference(rgbaSpan);
PixelOperations<TPixel>.Instance.ToRgba64(this.configuration, rowSpan, rgbaSpan);
// Can't map directly to byte array as it's big endian. // Can't map directly to byte array as it's big endian.
for (int x = 0, o = 0; x < rgbaSpan.Length; x++, o += 4) for (int x = 0, o = 0; x < laSpan.Length; x++, o += 4)
{ {
Rgba64 rgba = Unsafe.Add(ref rgbaRef, x); La32 la = Unsafe.Add(ref laRef, x);
ushort luminance = ColorNumerics.Get16BitBT709Luminance(rgba.R, rgba.G, rgba.B); BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), la.L);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance); BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), la.A);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.A);
}
} }
} }
else else
{ {
// 8 bit grayscale + alpha // 8 bit grayscale + alpha
// TODO: Should we consider in the future a GrayAlpha16 type. PixelOperations<TPixel>.Instance.ToLa16Bytes(
Rgba32 rgba = default; this.configuration,
for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 2) rowSpan,
{ rawScanlineSpan,
Unsafe.Add(ref rowSpanRef, x).ToRgba32(ref rgba); rowSpan.Length);
Unsafe.Add(ref rawScanlineSpanRef, o) =
ColorNumerics.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
Unsafe.Add(ref rawScanlineSpanRef, o + 1) = rgba.A;
}
} }
} }
} }

8
src/ImageSharp/Formats/Png/PngEncoderOptions.cs

@ -18,11 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
this.BitDepth = source.BitDepth; this.BitDepth = source.BitDepth;
this.ColorType = source.ColorType; this.ColorType = source.ColorType;
this.FilterMethod = source.FilterMethod;
// Specification recommends default filter method None for paletted images and Paeth for others.
this.FilterMethod = source.FilterMethod ?? (source.ColorType == PngColorType.Palette
? PngFilterMethod.None
: PngFilterMethod.Paeth);
this.CompressionLevel = source.CompressionLevel; this.CompressionLevel = source.CompressionLevel;
this.TextCompressionThreshold = source.TextCompressionThreshold; this.TextCompressionThreshold = source.TextCompressionThreshold;
this.Gamma = source.Gamma; this.Gamma = source.Gamma;
@ -41,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public PngColorType? ColorType { get; set; } public PngColorType? ColorType { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public PngFilterMethod? FilterMethod { get; } public PngFilterMethod? FilterMethod { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression; public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression;

12
src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs

@ -34,6 +34,18 @@ namespace SixLabors.ImageSharp.Formats.Png
// a sensible default based upon the pixel format. // a sensible default based upon the pixel format.
options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType<TPixel>(); options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType<TPixel>();
options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth<TPixel>(); options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth<TPixel>();
if (!options.FilterMethod.HasValue)
{
// Specification recommends default filter method None for paletted images and Paeth for others.
if (options.ColorType == PngColorType.Palette)
{
options.FilterMethod = PngFilterMethod.None;
}
else
{
options.FilterMethod = PngFilterMethod.Paeth;
}
}
// Ensure bit depth and color type are a supported combination. // Ensure bit depth and color type are a supported combination.
// Bit8 is the only bit depth supported by all color types. // Bit8 is the only bit depth supported by all color types.

90
tests/ImageSharp.Tests/Common/SimdUtilsTests.cs

@ -19,10 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Common
{ {
private ITestOutputHelper Output { get; } private ITestOutputHelper Output { get; }
public SimdUtilsTests(ITestOutputHelper output) public SimdUtilsTests(ITestOutputHelper output) => this.Output = output;
{
this.Output = output;
}
private static int R(float f) => (int)Math.Round(f, MidpointRounding.AwayFromZero); private static int R(float f) => (int)Math.Round(f, MidpointRounding.AwayFromZero);
@ -63,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Common
private static Vector<float> CreateRandomTestVector(int seed, float min, float max) private static Vector<float> CreateRandomTestVector(int seed, float min, float max)
{ {
var data = new float[Vector<float>.Count]; float[] data = new float[Vector<float>.Count];
var rnd = new Random(seed); var rnd = new Random(seed);
@ -154,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Common
float[] source = new Random(seed).GenerateRandomFloatArray(count, 0, 1f); float[] source = new Random(seed).GenerateRandomFloatArray(count, 0, 1f);
var dest = new byte[count]; byte[] dest = new byte[count];
SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByte(source, dest); SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByte(source, dest);
@ -163,25 +160,18 @@ namespace SixLabors.ImageSharp.Tests.Common
Assert.Equal(expected, dest); Assert.Equal(expected, dest);
} }
public static readonly TheoryData<int> ArraySizesDivisibleBy8 = new TheoryData<int> { 0, 8, 16, 1024 }; public static readonly TheoryData<int> ArraySizesDivisibleBy8 = new() { 0, 8, 16, 1024 };
public static readonly TheoryData<int> ArraySizesDivisibleBy4 = new TheoryData<int> { 0, 4, 8, 28, 1020 }; public static readonly TheoryData<int> ArraySizesDivisibleBy4 = new() { 0, 4, 8, 28, 1020 };
public static readonly TheoryData<int> ArraySizesDivisibleBy3 = new TheoryData<int> { 0, 3, 9, 36, 957 }; public static readonly TheoryData<int> ArraySizesDivisibleBy3 = new() { 0, 3, 9, 36, 957 };
public static readonly TheoryData<int> ArraySizesDivisibleBy32 = new TheoryData<int> { 0, 32, 512 }; public static readonly TheoryData<int> ArraySizesDivisibleBy32 = new() { 0, 32, 512 };
public static readonly TheoryData<int> ArbitraryArraySizes = public static readonly TheoryData<int> ArbitraryArraySizes = new() { 0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 63, 64, 255, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520 };
new TheoryData<int>
{
0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 63, 64, 255, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520,
};
[Theory] [Theory]
[MemberData(nameof(ArraySizesDivisibleBy4))] [MemberData(nameof(ArraySizesDivisibleBy4))]
public void FallbackIntrinsics128_BulkConvertByteToNormalizedFloat(int count) public void FallbackIntrinsics128_BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat(
{
TestImpl_BulkConvertByteToNormalizedFloat(
count, count,
(s, d) => SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(s.Span, d.Span)); (s, d) => SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(s.Span, d.Span));
}
[Theory] [Theory]
[MemberData(nameof(ArraySizesDivisibleBy8))] [MemberData(nameof(ArraySizesDivisibleBy8))]
@ -199,24 +189,23 @@ namespace SixLabors.ImageSharp.Tests.Common
[Theory] [Theory]
[MemberData(nameof(ArraySizesDivisibleBy32))] [MemberData(nameof(ArraySizesDivisibleBy32))]
public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat(int count) public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat(
{
TestImpl_BulkConvertByteToNormalizedFloat(
count, count,
(s, d) => SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); (s, d) => SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(s.Span, d.Span));
}
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
[Theory] [Theory]
[MemberData(nameof(ArraySizesDivisibleBy32))] [MemberData(nameof(ArraySizesDivisibleBy32))]
public void HwIntrinsics_BulkConvertByteToNormalizedFloat(int count) public void HwIntrinsics_BulkConvertByteToNormalizedFloat(int count)
{ {
static void RunTest(string serialized) if (!Sse2.IsSupported)
{ {
TestImpl_BulkConvertByteToNormalizedFloat( return;
}
static void RunTest(string serialized) => TestImpl_BulkConvertByteToNormalizedFloat(
FeatureTestRunner.Deserialize<int>(serialized), FeatureTestRunner.Deserialize<int>(serialized),
(s, d) => SimdUtils.HwIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); (s, d) => SimdUtils.HwIntrinsics.ByteToNormalizedFloat(s.Span, d.Span));
}
FeatureTestRunner.RunWithHwIntrinsicsFeature( FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest, RunTest,
@ -227,20 +216,17 @@ namespace SixLabors.ImageSharp.Tests.Common
[Theory] [Theory]
[MemberData(nameof(ArbitraryArraySizes))] [MemberData(nameof(ArbitraryArraySizes))]
public void BulkConvertByteToNormalizedFloat(int count) public void BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat(
{
TestImpl_BulkConvertByteToNormalizedFloat(
count, count,
(s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span)); (s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span));
}
private static void TestImpl_BulkConvertByteToNormalizedFloat( private static void TestImpl_BulkConvertByteToNormalizedFloat(
int count, int count,
Action<Memory<byte>, Memory<float>> convert) Action<Memory<byte>, Memory<float>> convert)
{ {
byte[] source = new Random(count).GenerateRandomByteArray(count); byte[] source = new Random(count).GenerateRandomByteArray(count);
var result = new float[count]; float[] result = new float[count];
float[] expected = source.Select(b => (float)b / 255f).ToArray(); float[] expected = source.Select(b => b / 255f).ToArray();
convert(source, result); convert(source, result);
@ -249,12 +235,9 @@ namespace SixLabors.ImageSharp.Tests.Common
[Theory] [Theory]
[MemberData(nameof(ArraySizesDivisibleBy4))] [MemberData(nameof(ArraySizesDivisibleBy4))]
public void FallbackIntrinsics128_BulkConvertNormalizedFloatToByteClampOverflows(int count) public void FallbackIntrinsics128_BulkConvertNormalizedFloatToByteClampOverflows(int count) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(
{
TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(
count, count,
(s, d) => SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(s.Span, d.Span)); (s, d) => SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(s.Span, d.Span));
}
[Theory] [Theory]
[MemberData(nameof(ArraySizesDivisibleBy8))] [MemberData(nameof(ArraySizesDivisibleBy8))]
@ -270,12 +253,9 @@ namespace SixLabors.ImageSharp.Tests.Common
[Theory] [Theory]
[MemberData(nameof(ArraySizesDivisibleBy32))] [MemberData(nameof(ArraySizesDivisibleBy32))]
public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(
{
TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(
count, count,
(s, d) => SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); (s, d) => SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span));
}
[Theory] [Theory]
[InlineData(1234)] [InlineData(1234)]
@ -304,12 +284,14 @@ namespace SixLabors.ImageSharp.Tests.Common
[MemberData(nameof(ArraySizesDivisibleBy32))] [MemberData(nameof(ArraySizesDivisibleBy32))]
public void HwIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) public void HwIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count)
{ {
static void RunTest(string serialized) if (!Sse2.IsSupported)
{ {
TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( return;
}
static void RunTest(string serialized) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(
FeatureTestRunner.Deserialize<int>(serialized), FeatureTestRunner.Deserialize<int>(serialized),
(s, d) => SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); (s, d) => SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span));
}
FeatureTestRunner.RunWithHwIntrinsicsFeature( FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest, RunTest,
@ -326,7 +308,7 @@ namespace SixLabors.ImageSharp.Tests.Common
TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span)); TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span));
// For small values, let's stress test the implementation a bit: // For small values, let's stress test the implementation a bit:
if (count > 0 && count < 10) if (count is > 0 and < 10)
{ {
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
{ {
@ -340,23 +322,17 @@ namespace SixLabors.ImageSharp.Tests.Common
[Theory] [Theory]
[MemberData(nameof(ArbitraryArraySizes))] [MemberData(nameof(ArbitraryArraySizes))]
public void PackFromRgbPlanes_Rgb24(int count) public void PackFromRgbPlanes_Rgb24(int count) => TestPackFromRgbPlanes<Rgb24>(
{
TestPackFromRgbPlanes<Rgb24>(
count, count,
(r, g, b, actual) => (r, g, b, actual) =>
SimdUtils.PackFromRgbPlanes(Configuration.Default, r, g, b, actual)); SimdUtils.PackFromRgbPlanes(Configuration.Default, r, g, b, actual));
}
[Theory] [Theory]
[MemberData(nameof(ArbitraryArraySizes))] [MemberData(nameof(ArbitraryArraySizes))]
public void PackFromRgbPlanes_Rgba32(int count) public void PackFromRgbPlanes_Rgba32(int count) => TestPackFromRgbPlanes<Rgba32>(
{
TestPackFromRgbPlanes<Rgba32>(
count, count,
(r, g, b, actual) => (r, g, b, actual) =>
SimdUtils.PackFromRgbPlanes(Configuration.Default, r, g, b, actual)); SimdUtils.PackFromRgbPlanes(Configuration.Default, r, g, b, actual));
}
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
[Fact] [Fact]
@ -371,7 +347,7 @@ namespace SixLabors.ImageSharp.Tests.Common
byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray();
byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray();
const int padding = 4; const int padding = 4;
Rgb24[] d = new Rgb24[32 + padding]; var d = new Rgb24[32 + padding];
ReadOnlySpan<byte> rr = r.AsSpan(); ReadOnlySpan<byte> rr = r.AsSpan();
ReadOnlySpan<byte> gg = g.AsSpan(); ReadOnlySpan<byte> gg = g.AsSpan();
@ -405,7 +381,7 @@ namespace SixLabors.ImageSharp.Tests.Common
byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray();
byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray();
Rgba32[] d = new Rgba32[32]; var d = new Rgba32[32];
ReadOnlySpan<byte> rr = r.AsSpan(); ReadOnlySpan<byte> rr = r.AsSpan();
ReadOnlySpan<byte> gg = g.AsSpan(); ReadOnlySpan<byte> gg = g.AsSpan();
@ -432,18 +408,18 @@ namespace SixLabors.ImageSharp.Tests.Common
internal static void TestPackFromRgbPlanes<TPixel>(int count, Action<byte[], byte[], byte[], TPixel[]> packMethod) internal static void TestPackFromRgbPlanes<TPixel>(int count, Action<byte[], byte[], byte[], TPixel[]> packMethod)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Random rnd = new Random(42); var rnd = new Random(42);
byte[] r = rnd.GenerateRandomByteArray(count); byte[] r = rnd.GenerateRandomByteArray(count);
byte[] g = rnd.GenerateRandomByteArray(count); byte[] g = rnd.GenerateRandomByteArray(count);
byte[] b = rnd.GenerateRandomByteArray(count); byte[] b = rnd.GenerateRandomByteArray(count);
TPixel[] expected = new TPixel[count]; var expected = new TPixel[count];
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
expected[i].FromRgb24(new Rgb24(r[i], g[i], b[i])); expected[i].FromRgb24(new Rgb24(r[i], g[i], b[i]));
} }
TPixel[] actual = new TPixel[count + 3]; // padding for Rgb24 AVX2 var actual = new TPixel[count + 3]; // padding for Rgb24 AVX2
packMethod(r, g, b, actual); packMethod(r, g, b, actual);
Assert.True(expected.AsSpan().SequenceEqual(actual.AsSpan().Slice(0, count))); Assert.True(expected.AsSpan().SequenceEqual(actual.AsSpan().Slice(0, count)));

1
tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs

@ -121,6 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
if (!Avx.IsSupported) if (!Avx.IsSupported)
{ {
this.Output.WriteLine("No AVX present, skipping test!"); this.Output.WriteLine("No AVX present, skipping test!");
return;
} }
Span<float> src = Create8x8RoundedRandomFloatData(-200, 200, seed); Span<float> src = Create8x8RoundedRandomFloatData(-200, 200, seed);

Loading…
Cancel
Save