Browse Source

Merge branch 'master' into af/UniformUnmanagedMemoryPoolMemoryAllocator-02

af/UniformUnmanagedMemoryPoolMemoryAllocator-02-MemoryGuards
Anton Firszov 5 years ago
parent
commit
bc70d1072f
  1. 5
      ImageSharp.sln
  2. 11
      codecov.yml
  3. 2
      shared-infrastructure
  4. 150
      src/ImageSharp/Color/Color.Conversions.cs
  5. 105
      src/ImageSharp/Color/Color.cs
  6. 2
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  7. 1
      src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs
  8. 274
      src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
  9. 220
      src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
  10. 25
      src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs
  11. 8
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  12. 3
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs
  13. 1
      src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs
  14. 38
      src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs
  15. 17
      src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs
  16. 14
      src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs
  17. 24
      src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
  18. 31
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
  19. 2
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  20. 2
      src/ImageSharp/Formats/Webp/WebpEncoder.cs
  21. 4
      src/ImageSharp/IO/ChunkedMemoryStream.cs
  22. 47
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
  23. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  24. 49
      tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs
  25. 2
      tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs
  26. 101
      tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs
  27. 26
      tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs
  28. 122
      tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs
  29. 53
      tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs

5
ImageSharp.sln

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 17
VisualStudioVersion = 16.0.28902.138 VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1
ci-build.ps1 = ci-build.ps1 ci-build.ps1 = ci-build.ps1
ci-pack.ps1 = ci-pack.ps1 ci-pack.ps1 = ci-pack.ps1
ci-test.ps1 = ci-test.ps1 ci-test.ps1 = ci-test.ps1
codecov.yml = codecov.yml
Directory.Build.props = Directory.Build.props Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets Directory.Build.targets = Directory.Build.targets
LICENSE = LICENSE LICENSE = LICENSE

11
codecov.yml

@ -9,3 +9,14 @@ codecov:
# Avoid Report Expired # Avoid Report Expired
# https://docs.codecov.io/docs/codecov-yaml#section-expired-reports # https://docs.codecov.io/docs/codecov-yaml#section-expired-reports
max_report_age: off max_report_age: off
coverage:
# Use integer precision
# https://docs.codecov.com/docs/codecovyml-reference#coverageprecision
precision: 0
# Explicitly control coverage status checks
# https://docs.codecov.com/docs/commit-status#disabling-a-status
status:
project: on
patch: off

2
shared-infrastructure

@ -1 +1 @@
Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 Subproject commit 33cb12ca77f919b44de56f344d2627cc2a108c3a

150
src/ImageSharp/Color/Color.Conversions.cs

@ -17,56 +17,118 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Rgba64"/> containing the color information.</param> /// <param name="pixel">The <see cref="Rgba64"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgba64 pixel) => this.data = pixel; public Color(Rgba64 pixel)
{
this.data = pixel;
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Rgb48"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgb48 pixel)
{
this.data = new Rgba64(pixel.R, pixel.G, pixel.B, ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="La32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(La32 pixel)
{
this.data = new Rgba64(pixel.L, pixel.L, pixel.L, pixel.A);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="L16"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(L16 pixel)
{
this.data = new Rgba64(pixel.PackedValue, pixel.PackedValue, pixel.PackedValue, ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Rgba32"/> containing the color information.</param> /// <param name="pixel">The <see cref="Rgba32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgba32 pixel) => this.data = new Rgba64(pixel); public Color(Rgba32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Argb32"/> containing the color information.</param> /// <param name="pixel">The <see cref="Argb32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Argb32 pixel) => this.data = new Rgba64(pixel); public Color(Argb32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Bgra32"/> containing the color information.</param> /// <param name="pixel">The <see cref="Bgra32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Bgra32 pixel) => this.data = new Rgba64(pixel); public Color(Bgra32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Rgb24"/> containing the color information.</param> /// <param name="pixel">The <see cref="Rgb24"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgb24 pixel) => this.data = new Rgba64(pixel); public Color(Rgb24 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Bgr24"/> containing the color information.</param> /// <param name="pixel">The <see cref="Bgr24"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Bgr24 pixel) => this.data = new Rgba64(pixel); public Color(Bgr24 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="vector">The <see cref="Vector4"/> containing the color information.</param> /// <param name="vector">The <see cref="Vector4"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Vector4 vector) => this.data = new Rgba64(vector); public Color(Vector4 vector)
{
vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.boxedHighPrecisionPixel = new RgbaVector(vector.X, vector.Y, vector.Z, vector.W);
this.data = default;
}
/// <summary> /// <summary>
/// Converts a <see cref="Color"/> to <see cref="Vector4"/>. /// Converts a <see cref="Color"/> to <see cref="Vector4"/>.
/// </summary> /// </summary>
/// <param name="color">The <see cref="Color"/>.</param> /// <param name="color">The <see cref="Color"/>.</param>
/// <returns>The <see cref="Vector4"/>.</returns> /// <returns>The <see cref="Vector4"/>.</returns>
public static explicit operator Vector4(Color color) => color.data.ToVector4(); public static explicit operator Vector4(Color color) => color.ToVector4();
/// <summary> /// <summary>
/// Converts an <see cref="Vector4"/> to <see cref="Color"/>. /// Converts an <see cref="Vector4"/> to <see cref="Color"/>.
@ -74,24 +136,82 @@ namespace SixLabors.ImageSharp
/// <param name="source">The <see cref="Vector4"/>.</param> /// <param name="source">The <see cref="Vector4"/>.</param>
/// <returns>The <see cref="Color"/>.</returns> /// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static explicit operator Color(Vector4 source) => new Color(source); public static explicit operator Color(Vector4 source) => new(source);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Rgba32 ToRgba32() => this.data.ToRgba32(); internal Rgba32 ToRgba32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToRgba32();
}
Rgba32 value = default;
this.boxedHighPrecisionPixel.ToRgba32(ref value);
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Bgra32 ToBgra32() => this.data.ToBgra32(); internal Bgra32 ToBgra32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToBgra32();
}
Bgra32 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Argb32 ToArgb32() => this.data.ToArgb32(); internal Argb32 ToArgb32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToArgb32();
}
Argb32 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Rgb24 ToRgb24() => this.data.ToRgb24(); internal Rgb24 ToRgb24()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToRgb24();
}
Rgb24 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Bgr24 ToBgr24() => this.data.ToBgr24(); internal Bgr24 ToBgr24()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToBgr24();
}
Bgr24 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Vector4 ToVector4() => this.data.ToVector4(); internal Vector4 ToVector4()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToScaledVector4();
}
return this.boxedHighPrecisionPixel.ToScaledVector4();
}
} }
} }

105
src/ImageSharp/Color/Color.cs

@ -4,7 +4,6 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
@ -21,6 +20,7 @@ namespace SixLabors.ImageSharp
public readonly partial struct Color : IEquatable<Color> public readonly partial struct Color : IEquatable<Color>
{ {
private readonly Rgba64 data; private readonly Rgba64 data;
private readonly IPixel boxedHighPrecisionPixel;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private Color(byte r, byte g, byte b, byte a) private Color(byte r, byte g, byte b, byte a)
@ -30,6 +30,8 @@ namespace SixLabors.ImageSharp
ColorNumerics.UpscaleFrom8BitTo16Bit(g), ColorNumerics.UpscaleFrom8BitTo16Bit(g),
ColorNumerics.UpscaleFrom8BitTo16Bit(b), ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ColorNumerics.UpscaleFrom8BitTo16Bit(a)); ColorNumerics.UpscaleFrom8BitTo16Bit(a));
this.boxedHighPrecisionPixel = null;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -40,6 +42,15 @@ namespace SixLabors.ImageSharp
ColorNumerics.UpscaleFrom8BitTo16Bit(g), ColorNumerics.UpscaleFrom8BitTo16Bit(g),
ColorNumerics.UpscaleFrom8BitTo16Bit(b), ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ushort.MaxValue); ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
[MethodImpl(InliningOptions.ShortMethod)]
private Color(IPixel pixel)
{
this.boxedHighPrecisionPixel = pixel;
this.data = default;
} }
/// <summary> /// <summary>
@ -52,13 +63,10 @@ namespace SixLabors.ImageSharp
/// otherwise, false. /// otherwise, false.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static bool operator ==(Color left, Color right) public static bool operator ==(Color left, Color right) => left.Equals(right);
{
return left.Equals(right);
}
/// <summary> /// <summary>
/// Checks whether two <see cref="Color"/> structures are equal. /// Checks whether two <see cref="Color"/> structures are not equal.
/// </summary> /// </summary>
/// <param name="left">The left hand <see cref="Color"/> operand.</param> /// <param name="left">The left hand <see cref="Color"/> operand.</param>
/// <param name="right">The right hand <see cref="Color"/> operand.</param> /// <param name="right">The right hand <see cref="Color"/> operand.</param>
@ -67,10 +75,7 @@ namespace SixLabors.ImageSharp
/// otherwise, false. /// otherwise, false.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static bool operator !=(Color left, Color right) public static bool operator !=(Color left, Color right) => !left.Equals(right);
{
return !left.Equals(right);
}
/// <summary> /// <summary>
/// Creates a <see cref="Color"/> from RGBA bytes. /// Creates a <see cref="Color"/> from RGBA bytes.
@ -81,7 +86,7 @@ namespace SixLabors.ImageSharp
/// <param name="a">The alpha component (0-255).</param> /// <param name="a">The alpha component (0-255).</param>
/// <returns>The <see cref="Color"/>.</returns> /// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static Color FromRgba(byte r, byte g, byte b, byte a) => new Color(r, g, b, a); public static Color FromRgba(byte r, byte g, byte b, byte a) => new(r, g, b, a);
/// <summary> /// <summary>
/// Creates a <see cref="Color"/> from RGB bytes. /// Creates a <see cref="Color"/> from RGB bytes.
@ -91,7 +96,46 @@ namespace SixLabors.ImageSharp
/// <param name="b">The blue component (0-255).</param> /// <param name="b">The blue component (0-255).</param>
/// <returns>The <see cref="Color"/>.</returns> /// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b); public static Color FromRgb(byte r, byte g, byte b) => new(r, g, b);
/// <summary>
/// Creates a <see cref="Color"/> from the given <typeparamref name="TPixel"/>.
/// </summary>
/// <param name="pixel">The pixel to convert from.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Color FromPixel<TPixel>(TPixel pixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// Avoid boxing in case we can convert to Rgba64 safely and efficently
if (typeof(TPixel) == typeof(Rgba64))
{
return new((Rgba64)(object)pixel);
}
else if (typeof(TPixel) == typeof(Rgb48))
{
return new((Rgb48)(object)pixel);
}
else if (typeof(TPixel) == typeof(La32))
{
return new((La32)(object)pixel);
}
else if (typeof(TPixel) == typeof(L16))
{
return new((L16)(object)pixel);
}
else if (Unsafe.SizeOf<TPixel>() <= Unsafe.SizeOf<Rgba32>())
{
Rgba32 p = default;
pixel.ToRgba32(ref p);
return new(p);
}
else
{
return new(pixel);
}
}
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="Color"/> struct /// Creates a new instance of the <see cref="Color"/> struct
@ -213,7 +257,7 @@ namespace SixLabors.ImageSharp
public override string ToString() => this.ToHex(); public override string ToString() => this.ToHex();
/// <summary> /// <summary>
/// Converts the color instance to a specified <see cref="IPixel{TSelf}"/> type. /// Converts the color instance to a specified <typeparamref name="TPixel"/> type.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam> /// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <returns>The pixel value.</returns> /// <returns>The pixel value.</returns>
@ -221,13 +265,18 @@ namespace SixLabors.ImageSharp
public TPixel ToPixel<TPixel>() public TPixel ToPixel<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
TPixel pixel = default; if (this.boxedHighPrecisionPixel is TPixel pixel)
{
return pixel;
}
pixel = default;
pixel.FromRgba64(this.data); pixel.FromRgba64(this.data);
return pixel; return pixel;
} }
/// <summary> /// <summary>
/// Bulk converts a span of <see cref="Color"/> to a span of a specified <see cref="IPixel{TSelf}"/> type. /// Bulk converts a span of <see cref="Color"/> to a span of a specified <typeparamref name="TPixel"/> type.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam> /// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
@ -240,28 +289,38 @@ namespace SixLabors.ImageSharp
Span<TPixel> destination) Span<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
ReadOnlySpan<Rgba64> rgba64Span = MemoryMarshal.Cast<Color, Rgba64>(source); Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
PixelOperations<TPixel>.Instance.FromRgba64(configuration, rgba64Span, destination); for (int i = 0; i < source.Length; i++)
{
destination[i] = source[i].ToPixel<TPixel>();
}
} }
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(Color other) public bool Equals(Color other)
{ {
return this.data.PackedValue == other.data.PackedValue; if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null)
{
return this.data.PackedValue == other.data.PackedValue;
}
return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true;
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj) => obj is Color other && this.Equals(other);
{
return obj is Color other && this.Equals(other);
}
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode() public override int GetHashCode()
{ {
return this.data.PackedValue.GetHashCode(); if (this.boxedHighPrecisionPixel is null)
{
return this.data.PackedValue.GetHashCode();
}
return this.boxedHighPrecisionPixel.GetHashCode();
} }
} }
} }

2
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -1072,7 +1072,7 @@ namespace SixLabors.ImageSharp.Formats.Png
int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length); int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
while (bytesRead != 0) while (bytesRead != 0)
{ {
uncompressedBytes.AddRange(this.buffer.AsSpan().Slice(0, bytesRead).ToArray()); uncompressedBytes.AddRange(this.buffer.AsSpan(0, bytesRead).ToArray());
bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length); bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
} }

1
src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs

@ -35,6 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <summary> /// <summary>
/// Gets the number of entropy-analysis passes (in [1..10]). /// Gets the number of entropy-analysis passes (in [1..10]).
/// Defaults to 1.
/// </summary> /// </summary>
int EntropyPasses { get; } int EntropyPasses { get; }

274
src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs

@ -4,23 +4,78 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Webp.Lossy namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
internal static class LossyUtils internal static class LossyUtils
{ {
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector128<byte> Mean16x4Mask = Vector128.Create((short)0x00ff).AsByte();
#endif
// Note: method name in libwebp reference implementation is called VP8SSE16x16.
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static int Vp8Sse16X16(Span<byte> a, Span<byte> b) => GetSse(a, b, 16, 16); public static int Vp8_Sse16X16(Span<byte> a, Span<byte> b) => Vp8_SseNxN(a, b, 16, 16);
// Note: method name in libwebp reference implementation is called VP8SSE16x8.
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static int Vp8Sse16X8(Span<byte> a, Span<byte> b) => GetSse(a, b, 16, 8); public static int Vp8_Sse16X8(Span<byte> a, Span<byte> b) => Vp8_SseNxN(a, b, 16, 8);
// Note: method name in libwebp reference implementation is called VP8SSE4x4.
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static int Vp8Sse4X4(Span<byte> a, Span<byte> b) => GetSse(a, b, 4, 4); public static int Vp8_Sse4X4(Span<byte> a, Span<byte> b)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
// Load values.
ref byte aRef = ref MemoryMarshal.GetReference(a);
Vector128<byte> a0 = Unsafe.As<byte, Vector128<byte>>(ref aRef);
Vector128<byte> a1 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref aRef, WebpConstants.Bps));
Vector128<byte> a2 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2));
Vector128<byte> a3 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3));
ref byte bRef = ref MemoryMarshal.GetReference(b);
Vector128<byte> b0 = Unsafe.As<byte, Vector128<byte>>(ref bRef);
Vector128<byte> b1 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref bRef, WebpConstants.Bps));
Vector128<byte> b2 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2));
Vector128<byte> b3 = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 3));
// Combine pair of lines.
Vector128<int> a01 = Sse2.UnpackLow(a0.AsInt32(), a1.AsInt32());
Vector128<int> a23 = Sse2.UnpackLow(a2.AsInt32(), a3.AsInt32());
Vector128<int> b01 = Sse2.UnpackLow(b0.AsInt32(), b1.AsInt32());
Vector128<int> b23 = Sse2.UnpackLow(b2.AsInt32(), b3.AsInt32());
// Convert to 16b.
Vector128<byte> a01s = Sse2.UnpackLow(a01.AsByte(), Vector128<byte>.Zero);
Vector128<byte> a23s = Sse2.UnpackLow(a23.AsByte(), Vector128<byte>.Zero);
Vector128<byte> b01s = Sse2.UnpackLow(b01.AsByte(), Vector128<byte>.Zero);
Vector128<byte> b23s = Sse2.UnpackLow(b23.AsByte(), Vector128<byte>.Zero);
// subtract, square and accumulate.
Vector128<byte> d0 = Sse2.SubtractSaturate(a01s, b01s);
Vector128<byte> d1 = Sse2.SubtractSaturate(a23s, b23s);
Vector128<int> e0 = Sse2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16());
Vector128<int> e1 = Sse2.MultiplyAddAdjacent(d1.AsInt16(), d1.AsInt16());
Vector128<int> sum = Sse2.Add(e0, e1);
return Numerics.ReduceSum(sum);
}
else
#endif
{
return Vp8_SseNxN(a, b, 4, 4);
}
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static int GetSse(Span<byte> a, Span<byte> b, int w, int h) public static int Vp8_SseNxN(Span<byte> a, Span<byte> b, int w, int h)
{ {
int count = 0; int count = 0;
int aOffset = 0; int aOffset = 0;
@ -61,11 +116,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public static int Vp8Disto16X16(Span<byte> a, Span<byte> b, Span<ushort> w, Span<int> scratch) public static int Vp8Disto16X16(Span<byte> a, Span<byte> b, Span<ushort> w, Span<int> scratch)
{ {
int d = 0; int d = 0;
int dataSize = (4 * WebpConstants.Bps) - 16;
for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps) for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps)
{ {
for (int x = 0; x < 16; x += 4) for (int x = 0; x < 16; x += 4)
{ {
d += Vp8Disto4X4(a.Slice(x + y), b.Slice(x + y), w, scratch); d += Vp8Disto4X4(a.Slice(x + y, dataSize), b.Slice(x + y, dataSize), w, scratch);
} }
} }
@ -75,9 +131,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static int Vp8Disto4X4(Span<byte> a, Span<byte> b, Span<ushort> w, Span<int> scratch) public static int Vp8Disto4X4(Span<byte> a, Span<byte> b, Span<ushort> w, Span<int> scratch)
{ {
int sum1 = TTransform(a, w, scratch); #if SUPPORTS_RUNTIME_INTRINSICS
int sum2 = TTransform(b, w, scratch); if (Sse41.IsSupported)
return Math.Abs(sum2 - sum1) >> 5; {
int diffSum = TTransformSse41(a, b, w);
return Math.Abs(diffSum) >> 5;
}
else
#endif
{
int sum1 = TTransform(a, w, scratch);
int sum2 = TTransform(b, w, scratch);
return Math.Abs(sum2 - sum1) >> 5;
}
} }
public static void DC16(Span<byte> dst, Span<byte> yuv, int offset) public static void DC16(Span<byte> dst, Span<byte> yuv, int offset)
@ -589,6 +655,122 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return sum; return sum;
} }
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// Hadamard transform
/// Returns the weighted sum of the absolute value of transformed coefficients.
/// w[] contains a row-major 4 by 4 symmetric matrix.
/// </summary>
public static int TTransformSse41(Span<byte> inputA, Span<byte> inputB, Span<ushort> w)
{
// Load and combine inputs.
Vector128<byte> ina0 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(inputA));
Vector128<byte> ina1 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps, 16)));
Vector128<byte> ina2 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps * 2, 16)));
Vector128<long> ina3 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps * 3, 16))).AsInt64();
Vector128<byte> inb0 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(inputB));
Vector128<byte> inb1 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps, 16)));
Vector128<byte> inb2 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps * 2, 16)));
Vector128<long> inb3 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps * 3, 16))).AsInt64();
// Combine inA and inB (we'll do two transforms in parallel).
Vector128<int> inab0 = Sse2.UnpackLow(ina0.AsInt32(), inb0.AsInt32());
Vector128<int> inab1 = Sse2.UnpackLow(ina1.AsInt32(), inb1.AsInt32());
Vector128<int> inab2 = Sse2.UnpackLow(ina2.AsInt32(), inb2.AsInt32());
Vector128<int> inab3 = Sse2.UnpackLow(ina3.AsInt32(), inb3.AsInt32());
Vector128<short> tmp0 = Sse41.ConvertToVector128Int16(inab0.AsByte());
Vector128<short> tmp1 = Sse41.ConvertToVector128Int16(inab1.AsByte());
Vector128<short> tmp2 = Sse41.ConvertToVector128Int16(inab2.AsByte());
Vector128<short> tmp3 = Sse41.ConvertToVector128Int16(inab3.AsByte());
// a00 a01 a02 a03 b00 b01 b02 b03
// a10 a11 a12 a13 b10 b11 b12 b13
// a20 a21 a22 a23 b20 b21 b22 b23
// a30 a31 a32 a33 b30 b31 b32 b33
// Vertical pass first to avoid a transpose (vertical and horizontal passes
// are commutative because w/kWeightY is symmetric) and subsequent transpose.
// Calculate a and b (two 4x4 at once).
Vector128<short> a0 = Sse2.Add(tmp0, tmp2);
Vector128<short> a1 = Sse2.Add(tmp1, tmp3);
Vector128<short> a2 = Sse2.Subtract(tmp1, tmp3);
Vector128<short> a3 = Sse2.Subtract(tmp0, tmp2);
Vector128<short> b0 = Sse2.Add(a0, a1);
Vector128<short> b1 = Sse2.Add(a3, a2);
Vector128<short> b2 = Sse2.Subtract(a3, a2);
Vector128<short> b3 = Sse2.Subtract(a0, a1);
// a00 a01 a02 a03 b00 b01 b02 b03
// a10 a11 a12 a13 b10 b11 b12 b13
// a20 a21 a22 a23 b20 b21 b22 b23
// a30 a31 a32 a33 b30 b31 b32 b33
// Transpose the two 4x4.
Vector128<short> transpose00 = Sse2.UnpackLow(b0, b1);
Vector128<short> transpose01 = Sse2.UnpackLow(b2, b3);
Vector128<short> transpose02 = Sse2.UnpackHigh(b0, b1);
Vector128<short> transpose03 = Sse2.UnpackHigh(b2, b3);
// a00 a10 a01 a11 a02 a12 a03 a13
// a20 a30 a21 a31 a22 a32 a23 a33
// b00 b10 b01 b11 b02 b12 b03 b13
// b20 b30 b21 b31 b22 b32 b23 b33
Vector128<int> transpose10 = Sse2.UnpackLow(transpose00.AsInt32(), transpose01.AsInt32());
Vector128<int> transpose11 = Sse2.UnpackLow(transpose02.AsInt32(), transpose03.AsInt32());
Vector128<int> transpose12 = Sse2.UnpackHigh(transpose00.AsInt32(), transpose01.AsInt32());
Vector128<int> transpose13 = Sse2.UnpackHigh(transpose02.AsInt32(), transpose03.AsInt32());
// a00 a10 a20 a30 a01 a11 a21 a31
// b00 b10 b20 b30 b01 b11 b21 b31
// a02 a12 a22 a32 a03 a13 a23 a33
// b02 b12 a22 b32 b03 b13 b23 b33
Vector128<long> output0 = Sse2.UnpackLow(transpose10.AsInt64(), transpose11.AsInt64());
Vector128<long> output1 = Sse2.UnpackHigh(transpose10.AsInt64(), transpose11.AsInt64());
Vector128<long> output2 = Sse2.UnpackLow(transpose12.AsInt64(), transpose13.AsInt64());
Vector128<long> output3 = Sse2.UnpackHigh(transpose12.AsInt64(), transpose13.AsInt64());
// a00 a10 a20 a30 b00 b10 b20 b30
// a01 a11 a21 a31 b01 b11 b21 b31
// a02 a12 a22 a32 b02 b12 b22 b32
// a03 a13 a23 a33 b03 b13 b23 b33
// Horizontal pass and difference of weighted sums.
Vector128<ushort> w0 = Unsafe.As<ushort, Vector128<ushort>>(ref MemoryMarshal.GetReference(w));
Vector128<ushort> w8 = Unsafe.As<ushort, Vector128<ushort>>(ref MemoryMarshal.GetReference(w.Slice(8, 8)));
// Calculate a and b (two 4x4 at once).
a0 = Sse2.Add(output0.AsInt16(), output2.AsInt16());
a1 = Sse2.Add(output1.AsInt16(), output3.AsInt16());
a2 = Sse2.Subtract(output1.AsInt16(), output3.AsInt16());
a3 = Sse2.Subtract(output0.AsInt16(), output2.AsInt16());
b0 = Sse2.Add(a0, a1);
b1 = Sse2.Add(a3, a2);
b2 = Sse2.Subtract(a3, a2);
b3 = Sse2.Subtract(a0, a1);
// Separate the transforms of inA and inB.
Vector128<long> ab0 = Sse2.UnpackLow(b0.AsInt64(), b1.AsInt64());
Vector128<long> ab2 = Sse2.UnpackLow(b2.AsInt64(), b3.AsInt64());
Vector128<long> bb0 = Sse2.UnpackHigh(b0.AsInt64(), b1.AsInt64());
Vector128<long> bb2 = Sse2.UnpackHigh(b2.AsInt64(), b3.AsInt64());
Vector128<ushort> ab0Abs = Ssse3.Abs(ab0.AsInt16());
Vector128<ushort> ab2Abs = Ssse3.Abs(ab2.AsInt16());
Vector128<ushort> b0Abs = Ssse3.Abs(bb0.AsInt16());
Vector128<ushort> bb2Abs = Ssse3.Abs(bb2.AsInt16());
// weighted sums.
Vector128<int> ab0mulw0 = Sse2.MultiplyAddAdjacent(ab0Abs.AsInt16(), w0.AsInt16());
Vector128<int> ab2mulw8 = Sse2.MultiplyAddAdjacent(ab2Abs.AsInt16(), w8.AsInt16());
Vector128<int> b0mulw0 = Sse2.MultiplyAddAdjacent(b0Abs.AsInt16(), w0.AsInt16());
Vector128<int> bb2mulw8 = Sse2.MultiplyAddAdjacent(bb2Abs.AsInt16(), w8.AsInt16());
Vector128<int> ab0ab2Sum = Sse2.Add(ab0mulw0, ab2mulw8);
Vector128<int> b0w0bb2w8Sum = Sse2.Add(b0mulw0, bb2mulw8);
// difference of weighted sums.
Vector128<int> result = Sse2.Subtract(ab0ab2Sum.AsInt32(), b0w0bb2w8Sum.AsInt32());
return Numerics.ReduceSum(result);
}
#endif
public static void TransformTwo(Span<short> src, Span<byte> dst, Span<int> scratch) public static void TransformTwo(Span<short> src, Span<byte> dst, Span<int> scratch)
{ {
TransformOne(src, dst, scratch); TransformOne(src, dst, scratch);
@ -598,7 +780,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public static void TransformOne(Span<short> src, Span<byte> dst, Span<int> scratch) public static void TransformOne(Span<short> src, Span<byte> dst, Span<int> scratch)
{ {
Span<int> tmp = scratch.Slice(0, 16); Span<int> tmp = scratch.Slice(0, 16);
tmp.Clear();
int tmpOffset = 0; int tmpOffset = 0;
for (int srcOffset = 0; srcOffset < 4; srcOffset++) for (int srcOffset = 0; srcOffset < 4; srcOffset++)
{ {
@ -801,26 +982,55 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh);
} }
[MethodImpl(InliningOptions.ShortMethod)] public static void Mean16x4(Span<byte> input, Span<uint> dc)
public static uint LoadUv(byte u, byte v) =>
(uint)(u | (v << 16)); // We process u and v together stashed into 32bit(16bit each).
[MethodImpl(InliningOptions.ShortMethod)]
public static void YuvToBgr(int y, int u, int v, Span<byte> bgr)
{ {
bgr[0] = (byte)YuvToB(y, u); #if SUPPORTS_RUNTIME_INTRINSICS
bgr[1] = (byte)YuvToG(y, u, v); if (Ssse3.IsSupported)
bgr[2] = (byte)YuvToR(y, v); {
} Vector128<byte> a0 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input));
Vector128<byte> a1 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps, 16)));
[MethodImpl(InliningOptions.ShortMethod)] Vector128<byte> a2 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 2, 16)));
public static int YuvToB(int y, int u) => Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); Vector128<byte> a3 = Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 3, 16)));
Vector128<short> b0 = Sse2.ShiftRightLogical(a0.AsInt16(), 8); // hi byte
[MethodImpl(InliningOptions.ShortMethod)] Vector128<short> b1 = Sse2.ShiftRightLogical(a1.AsInt16(), 8);
public static int YuvToG(int y, int u, int v) => Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); Vector128<short> b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8);
Vector128<short> b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8);
Vector128<byte> c0 = Sse2.And(a0, Mean16x4Mask); // lo byte
Vector128<byte> c1 = Sse2.And(a1, Mean16x4Mask);
Vector128<byte> c2 = Sse2.And(a2, Mean16x4Mask);
Vector128<byte> c3 = Sse2.And(a3, Mean16x4Mask);
Vector128<int> d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32());
Vector128<int> d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32());
Vector128<int> d2 = Sse2.Add(b2.AsInt32(), c2.AsInt32());
Vector128<int> d3 = Sse2.Add(b3.AsInt32(), c3.AsInt32());
Vector128<int> e0 = Sse2.Add(d0, d1);
Vector128<int> e1 = Sse2.Add(d2, d3);
Vector128<int> f0 = Sse2.Add(e0, e1);
Vector128<short> hadd = Ssse3.HorizontalAdd(f0.AsInt16(), f0.AsInt16());
Vector128<uint> wide = Sse2.UnpackLow(hadd, Vector128<short>.Zero).AsUInt32();
ref uint outputRef = ref MemoryMarshal.GetReference(dc);
Unsafe.As<uint, Vector128<uint>>(ref outputRef) = wide;
}
else
#endif
{
for (int k = 0; k < 4; k++)
{
uint avg = 0;
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
avg += input[x + (y * WebpConstants.Bps)];
}
}
[MethodImpl(InliningOptions.ShortMethod)] dc[k] = avg;
public static int YuvToR(int y, int v) => Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); input = input.Slice(4); // go to next 4x4 block.
}
}
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static byte Avg2(byte a, byte b) => (byte)((a + b + 1) >> 1); public static byte Avg2(byte a, byte b) => (byte)((a + b + 1) >> 1);
@ -1026,9 +1236,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return WebpLookupTables.Abs0(p1 - p0) > thresh || WebpLookupTables.Abs0(q1 - q0) > thresh; return WebpLookupTables.Abs0(p1 - p0) > thresh || WebpLookupTables.Abs0(q1 - q0) > thresh;
} }
[MethodImpl(InliningOptions.ShortMethod)]
private static int MultHi(int v, int coeff) => (v * coeff) >> 8;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static void Store(Span<byte> dst, int x, int y, int v) private static void Store(Span<byte> dst, int x, int y, int v)
{ {
@ -1051,13 +1258,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int Mul2(int a) => (a * 35468) >> 16; private static int Mul2(int a) => (a * 35468) >> 16;
[MethodImpl(InliningOptions.ShortMethod)]
private static byte Clip8(int v)
{
int yuvMask = (256 << 6) - 1;
return (byte)((v & ~yuvMask) == 0 ? v >> 6 : v < 0 ? 0 : 255);
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static void Put8x8uv(byte value, Span<byte> dst) private static void Put8x8uv(byte value, Span<byte> dst)
{ {

220
src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs

@ -3,13 +3,18 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Webp.Lossy namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
/// <summary> /// <summary>
/// Quantization methods. /// Quantization methods.
/// </summary> /// </summary>
internal static class QuantEnc internal static unsafe class QuantEnc
{ {
private static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; private static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 };
@ -17,6 +22,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private const int MaxLevel = 2047; private const int MaxLevel = 2047;
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector128<short> MaxCoeff2047 = Vector128.Create((short)MaxLevel);
private static readonly Vector128<byte> CstLo = Vector128.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13);
private static readonly Vector128<byte> Cst7 = Vector128.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255);
private static readonly Vector128<byte> CstHi = Vector128.Create(2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15);
private static readonly Vector128<byte> Cst8 = Vector128.Create(254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255);
#endif
// Diffusion weights. We under-correct a bit (15/16th of the error is actually // Diffusion weights. We under-correct a bit (15/16th of the error is actually
// diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. // diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0.
private const int C1 = 7; // fraction of error sent to the 4x4 block below private const int C1 = 7; // fraction of error sent to the 4x4 block below
@ -49,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
rdCur.Nz = (uint)ReconstructIntra16(it, dqm, rdCur, tmpDst, mode); rdCur.Nz = (uint)ReconstructIntra16(it, dqm, rdCur, tmpDst, mode);
// Measure RD-score. // Measure RD-score.
rdCur.D = LossyUtils.Vp8Sse16X16(src, tmpDst); rdCur.D = LossyUtils.Vp8_Sse16X16(src, tmpDst);
rdCur.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto16X16(src, tmpDst, WeightY, scratch)) : 0; rdCur.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto16X16(src, tmpDst, WeightY, scratch)) : 0;
rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; rdCur.H = WebpConstants.Vp8FixedCostsI16[mode];
rdCur.R = it.GetCostLuma16(rdCur, proba, res); rdCur.R = it.GetCostLuma16(rdCur, proba, res);
@ -143,7 +160,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
rdTmp.Nz = (uint)ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode); rdTmp.Nz = (uint)ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode);
// Compute RD-score. // Compute RD-score.
rdTmp.D = LossyUtils.Vp8Sse4X4(src, tmpDst); rdTmp.D = LossyUtils.Vp8_Sse4X4(src, tmpDst);
rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto4X4(src, tmpDst, WeightY, scratch)) : 0; rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto4X4(src, tmpDst, WeightY, scratch)) : 0;
rdTmp.H = modeCosts[mode]; rdTmp.H = modeCosts[mode];
@ -234,7 +251,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
rdUv.Nz = (uint)ReconstructUv(it, dqm, rdUv, tmpDst, mode); rdUv.Nz = (uint)ReconstructUv(it, dqm, rdUv, tmpDst, mode);
// Compute RD-score // Compute RD-score
rdUv.D = LossyUtils.Vp8Sse16X8(src, tmpDst); rdUv.D = LossyUtils.Vp8_Sse16X8(src, tmpDst);
rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas. rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas.
rdUv.H = WebpConstants.Vp8FixedCostsUv[mode]; rdUv.H = WebpConstants.Vp8FixedCostsUv[mode];
rdUv.R = it.GetCostUv(rdUv, proba, res); rdUv.R = it.GetCostUv(rdUv, proba, res);
@ -298,14 +315,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
Vp8Encoding.FTransformWht(tmp, dcTmp, scratch); Vp8Encoding.FTransformWht(tmp, dcTmp, scratch);
nz |= QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24; nz |= QuantizeBlock(dcTmp, rd.YDcLevels, ref dqm.Y2) << 24;
for (n = 0; n < 16; n += 2) for (n = 0; n < 16; n += 2)
{ {
// Zero-out the first coeff, so that: a) nz is correct below, and // Zero-out the first coeff, so that: a) nz is correct below, and
// b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified.
tmp[n * 16] = tmp[(n + 1) * 16] = 0; tmp[n * 16] = tmp[(n + 1) * 16] = 0;
nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n; nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), ref dqm.Y1) << n;
} }
// Transform back. // Transform back.
@ -323,10 +340,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]);
Span<short> tmp = it.Scratch2.AsSpan(0, 16); Span<short> tmp = it.Scratch2.AsSpan(0, 16);
Span<int> scratch = it.Scratch3.AsSpan(0, 16); Span<int> scratch = it.Scratch3.AsSpan(0, 16);
tmp.Clear();
scratch.Clear();
Vp8Encoding.FTransform(src, reference, tmp, scratch); Vp8Encoding.FTransform(src, reference, tmp, scratch);
int nz = QuantizeBlock(tmp, levels, dqm.Y1); int nz = QuantizeBlock(tmp, levels, ref dqm.Y1);
Vp8Encoding.ITransform(reference, tmp, yuvOut, false, scratch); Vp8Encoding.ITransform(reference, tmp, yuvOut, false, scratch);
return nz; return nz;
@ -340,8 +355,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int n; int n;
Span<short> tmp = it.Scratch2.AsSpan(0, 8 * 16); Span<short> tmp = it.Scratch2.AsSpan(0, 8 * 16);
Span<int> scratch = it.Scratch3.AsSpan(0, 16); Span<int> scratch = it.Scratch3.AsSpan(0, 16);
tmp.Clear();
scratch.Clear();
for (n = 0; n < 8; n += 2) for (n = 0; n < 8; n += 2)
{ {
@ -353,11 +366,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
scratch); scratch);
} }
CorrectDcValues(it, dqm.Uv, tmp, rd); CorrectDcValues(it, ref dqm.Uv, tmp, rd);
for (n = 0; n < 8; n += 2) for (n = 0; n < 8; n += 2)
{ {
nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n; nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), ref dqm.Uv) << n;
} }
for (n = 0; n < 8; n += 2) for (n = 0; n < 8; n += 2)
@ -394,7 +407,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) for (mode = 0; mode < WebpConstants.NumPredModes; ++mode)
{ {
Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]);
long score = (LossyUtils.Vp8Sse16X16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16); long score = (LossyUtils.Vp8_Sse16X16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16);
if (mode > 0 && WebpConstants.Vp8FixedCostsI16[mode] > bitLimit) if (mode > 0 && WebpConstants.Vp8FixedCostsI16[mode] > bitLimit)
{ {
@ -441,7 +454,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
for (mode = 0; mode < WebpConstants.NumBModes; ++mode) for (mode = 0; mode < WebpConstants.NumBModes; ++mode)
{ {
Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]);
long score = (LossyUtils.Vp8Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); long score = (LossyUtils.Vp8_Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4);
if (score < bestI4Score) if (score < bestI4Score)
{ {
bestI4Mode = mode; bestI4Mode = mode;
@ -490,7 +503,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) for (mode = 0; mode < WebpConstants.NumPredModes; ++mode)
{ {
Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); Span<byte> reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]);
long score = (LossyUtils.Vp8Sse16X8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv); long score = (LossyUtils.Vp8_Sse16X8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv);
if (score < bestUvScore) if (score < bestUvScore)
{ {
bestMode = mode; bestMode = mode;
@ -508,58 +521,155 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static int Quantize2Blocks(Span<short> input, Span<short> output, Vp8Matrix mtx) public static int Quantize2Blocks(Span<short> input, Span<short> output, ref Vp8Matrix mtx)
{ {
int nz = QuantizeBlock(input, output, mtx) << 0; int nz = QuantizeBlock(input.Slice(0, 16), output.Slice(0, 16), ref mtx) << 0;
nz |= QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1; nz |= QuantizeBlock(input.Slice(1 * 16, 16), output.Slice(1 * 16, 16), ref mtx) << 1;
return nz; return nz;
} }
public static int QuantizeBlock(Span<short> input, Span<short> output, Vp8Matrix mtx) public static int QuantizeBlock(Span<short> input, Span<short> output, ref Vp8Matrix mtx)
{ {
int last = -1; #if SUPPORTS_RUNTIME_INTRINSICS
int n; if (Sse41.IsSupported)
for (n = 0; n < 16; ++n) {
// Load all inputs.
Vector128<short> input0 = Unsafe.As<short, Vector128<short>>(ref MemoryMarshal.GetReference(input));
Vector128<short> input8 = Unsafe.As<short, Vector128<short>>(ref MemoryMarshal.GetReference(input.Slice(8, 8)));
Vector128<ushort> iq0 = Unsafe.As<ushort, Vector128<ushort>>(ref mtx.IQ[0]);
Vector128<ushort> iq8 = Unsafe.As<ushort, Vector128<ushort>>(ref mtx.IQ[8]);
Vector128<ushort> q0 = Unsafe.As<ushort, Vector128<ushort>>(ref mtx.Q[0]);
Vector128<ushort> q8 = Unsafe.As<ushort, Vector128<ushort>>(ref mtx.Q[8]);
// coeff = abs(in)
Vector128<ushort> coeff0 = Ssse3.Abs(input0);
Vector128<ushort> coeff8 = Ssse3.Abs(input8);
// coeff = abs(in) + sharpen
Vector128<short> sharpen0 = Unsafe.As<short, Vector128<short>>(ref mtx.Sharpen[0]);
Vector128<short> sharpen8 = Unsafe.As<short, Vector128<short>>(ref mtx.Sharpen[8]);
Sse2.Add(coeff0.AsInt16(), sharpen0);
Sse2.Add(coeff8.AsInt16(), sharpen8);
// out = (coeff * iQ + B) >> QFIX
// doing calculations with 32b precision (QFIX=17)
// out = (coeff * iQ)
Vector128<ushort> coeffiQ0H = Sse2.MultiplyHigh(coeff0, iq0);
Vector128<ushort> coeffiQ0L = Sse2.MultiplyLow(coeff0, iq0);
Vector128<ushort> coeffiQ8H = Sse2.MultiplyHigh(coeff8, iq8);
Vector128<ushort> coeffiQ8L = Sse2.MultiplyLow(coeff8, iq8);
Vector128<ushort> out00 = Sse2.UnpackLow(coeffiQ0L, coeffiQ0H);
Vector128<ushort> out04 = Sse2.UnpackHigh(coeffiQ0L, coeffiQ0H);
Vector128<ushort> out08 = Sse2.UnpackLow(coeffiQ8L, coeffiQ8H);
Vector128<ushort> out12 = Sse2.UnpackHigh(coeffiQ8L, coeffiQ8H);
// out = (coeff * iQ + B)
Vector128<uint> bias00 = Unsafe.As<uint, Vector128<uint>>(ref mtx.Bias[0]);
Vector128<uint> bias04 = Unsafe.As<uint, Vector128<uint>>(ref mtx.Bias[4]);
Vector128<uint> bias08 = Unsafe.As<uint, Vector128<uint>>(ref mtx.Bias[8]);
Vector128<uint> bias12 = Unsafe.As<uint, Vector128<uint>>(ref mtx.Bias[12]);
out00 = Sse2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16();
out04 = Sse2.Add(out04.AsInt32(), bias04.AsInt32()).AsUInt16();
out08 = Sse2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16();
out12 = Sse2.Add(out12.AsInt32(), bias12.AsInt32()).AsUInt16();
// out = QUANTDIV(coeff, iQ, B, QFIX)
out00 = Sse2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16();
out04 = Sse2.ShiftRightArithmetic(out04.AsInt32(), WebpConstants.QFix).AsUInt16();
out08 = Sse2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16();
out12 = Sse2.ShiftRightArithmetic(out12.AsInt32(), WebpConstants.QFix).AsUInt16();
// pack result as 16b
Vector128<short> out0 = Sse2.PackSignedSaturate(out00.AsInt32(), out04.AsInt32());
Vector128<short> out8 = Sse2.PackSignedSaturate(out08.AsInt32(), out12.AsInt32());
// if (coeff > 2047) coeff = 2047
out0 = Sse2.Min(out0, MaxCoeff2047);
out8 = Sse2.Min(out8, MaxCoeff2047);
// put sign back
out0 = Ssse3.Sign(out0, input0);
out8 = Ssse3.Sign(out8, input8);
// in = out * Q
input0 = Sse2.MultiplyLow(out0, q0.AsInt16());
input8 = Sse2.MultiplyLow(out8, q8.AsInt16());
// in = out * Q
ref short inputRef = ref MemoryMarshal.GetReference(input);
Unsafe.As<short, Vector128<short>>(ref inputRef) = input0;
Unsafe.As<short, Vector128<short>>(ref Unsafe.Add(ref inputRef, 8)) = input8;
// zigzag the output before storing it. The re-ordering is:
// 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15
// -> 0 1 4[8]5 2 3 6 | 9 12 13 10 [7]11 14 15
// There's only two misplaced entries ([8] and [7]) that are crossing the
// reg's boundaries.
// We use pshufb instead of pshuflo/pshufhi.
Vector128<byte> tmpLo = Ssse3.Shuffle(out0.AsByte(), CstLo);
Vector128<byte> tmp7 = Ssse3.Shuffle(out0.AsByte(), Cst7); // extract #7
Vector128<byte> tmpHi = Ssse3.Shuffle(out8.AsByte(), CstHi);
Vector128<byte> tmp8 = Ssse3.Shuffle(out8.AsByte(), Cst8); // extract #8
Vector128<byte> outZ0 = Sse2.Or(tmpLo, tmp8);
Vector128<byte> outZ8 = Sse2.Or(tmpHi, tmp7);
ref short outputRef = ref MemoryMarshal.GetReference(output);
Unsafe.As<short, Vector128<short>>(ref outputRef) = outZ0.AsInt16();
Unsafe.As<short, Vector128<short>>(ref Unsafe.Add(ref outputRef, 8)) = outZ8.AsInt16();
Vector128<sbyte> packedOutput = Sse2.PackSignedSaturate(outZ0.AsInt16(), outZ8.AsInt16());
// Detect if all 'out' values are zeros or not.
Vector128<sbyte> cmpeq = Sse2.CompareEqual(packedOutput, Vector128<sbyte>.Zero);
return Sse2.MoveMask(cmpeq) != 0xffff ? 1 : 0;
}
else
#endif
{ {
int j = Zigzag[n]; int last = -1;
bool sign = input[j] < 0; int n;
uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]); for (n = 0; n < 16; ++n)
if (coeff > mtx.ZThresh[j])
{ {
uint q = mtx.Q[j]; int j = Zigzag[n];
uint iQ = mtx.IQ[j]; bool sign = input[j] < 0;
uint b = mtx.Bias[j]; uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]);
int level = QuantDiv(coeff, iQ, b); if (coeff > mtx.ZThresh[j])
if (level > MaxLevel)
{ {
level = MaxLevel; uint q = mtx.Q[j];
} uint iQ = mtx.IQ[j];
uint b = mtx.Bias[j];
int level = QuantDiv(coeff, iQ, b);
if (level > MaxLevel)
{
level = MaxLevel;
}
if (sign) if (sign)
{ {
level = -level; level = -level;
} }
input[j] = (short)(level * (int)q); input[j] = (short)(level * (int)q);
output[n] = (short)level; output[n] = (short)level;
if (level != 0) if (level != 0)
{
last = n;
}
}
else
{ {
last = n; output[n] = 0;
input[j] = 0;
} }
} }
else
{
output[n] = 0;
input[j] = 0;
}
}
return last >= 0 ? 1 : 0; return last >= 0 ? 1 : 0;
}
} }
// Quantize as usual, but also compute and return the quantization error. // Quantize as usual, but also compute and return the quantization error.
// Error is already divided by DSHIFT. // Error is already divided by DSHIFT.
public static int QuantizeSingle(Span<short> v, Vp8Matrix mtx) public static int QuantizeSingle(Span<short> v, ref Vp8Matrix mtx)
{ {
int v0 = v[0]; int v0 = v[0];
bool sign = v0 < 0; bool sign = v0 < 0;
@ -580,7 +690,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return (sign ? -v0 : v0) >> DSCALE; return (sign ? -v0 : v0) >> DSCALE;
} }
public static void CorrectDcValues(Vp8EncIterator it, Vp8Matrix mtx, Span<short> tmp, Vp8ModeScore rd) public static void CorrectDcValues(Vp8EncIterator it, ref Vp8Matrix mtx, Span<short> tmp, Vp8ModeScore rd)
{ {
#pragma warning disable SA1005 // Single line comments should begin with single space #pragma warning disable SA1005 // Single line comments should begin with single space
// | top[0] | top[1] // | top[0] | top[1]
@ -597,13 +707,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span<sbyte> left = it.LeftDerr.AsSpan(ch, 2); Span<sbyte> left = it.LeftDerr.AsSpan(ch, 2);
Span<short> c = tmp.Slice(ch * 4 * 16, 4 * 16); Span<short> c = tmp.Slice(ch * 4 * 16, 4 * 16);
c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE));
int err0 = QuantizeSingle(c, mtx); int err0 = QuantizeSingle(c, ref mtx);
c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE));
int err1 = QuantizeSingle(c.Slice(1 * 16), mtx); int err1 = QuantizeSingle(c.Slice(1 * 16), ref mtx);
c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE));
int err2 = QuantizeSingle(c.Slice(2 * 16), mtx); int err2 = QuantizeSingle(c.Slice(2 * 16), ref mtx);
c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE));
int err3 = QuantizeSingle(c.Slice(3 * 16), mtx); int err3 = QuantizeSingle(c.Slice(3 * 16), ref mtx);
rd.Derr[ch, 0] = err1; rd.Derr[ch, 0] = err1;
rd.Derr[ch, 1] = err2; rd.Derr[ch, 1] = err2;

25
src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs

@ -357,15 +357,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int q = quality; int q = quality;
int kThreshold = 8 + ((17 - 8) * q / 100); int kThreshold = 8 + ((17 - 8) * q / 100);
int k; int k;
uint[] dc = new uint[16]; Span<uint> dc = stackalloc uint[16];
Span<ushort> tmp = stackalloc ushort[16];
uint m; uint m;
uint m2; uint m2;
for (k = 0; k < 16; k += 4) for (k = 0; k < 16; k += 4)
{ {
this.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.AsSpan(k)); LossyUtils.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.Slice(k, 4));
} }
for (m = 0, m2 = 0, k = 0; k < 16; ++k) for (m = 0, m2 = 0, k = 0; k < 16; k++)
{ {
m += dc[k]; m += dc[k];
m2 += dc[k] * dc[k]; m2 += dc[k] * dc[k];
@ -823,24 +824,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.Nz[this.nzIdx] = nz; this.Nz[this.nzIdx] = nz;
} }
private void Mean16x4(Span<byte> input, Span<uint> dc)
{
for (int k = 0; k < 4; k++)
{
uint avg = 0;
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
avg += input[x + (y * WebpConstants.Bps)];
}
}
dc[k] = avg;
input = input.Slice(4); // go to next 4x4 block.
}
}
private void ImportBlock(Span<byte> src, int srcStride, Span<byte> dst, int w, int h, int size) private void ImportBlock(Span<byte> src, int srcStride, Span<byte> dst, int w, int h, int size)
{ {
int dstIdx = 0; int dstIdx = 0;

8
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -502,7 +502,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.ResetStats(); this.ResetStats();
} }
private void AdjustFilterStrength() private unsafe void AdjustFilterStrength()
{ {
if (this.filterStrength > 0) if (this.filterStrength > 0)
{ {
@ -806,7 +806,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
proba.NbSkip = 0; proba.NbSkip = 0;
} }
private void SetupMatrices(Vp8SegmentInfo[] dqm) private unsafe void SetupMatrices(Vp8SegmentInfo[] dqm)
{ {
int tlambdaScale = this.method >= WebpEncodingMethod.Default ? this.spatialNoiseShaping : 0; int tlambdaScale = this.method >= WebpEncodingMethod.Default ? this.spatialNoiseShaping : 0;
for (int i = 0; i < dqm.Length; i++) for (int i = 0; i < dqm.Length; i++)
@ -814,10 +814,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vp8SegmentInfo m = dqm[i]; Vp8SegmentInfo m = dqm[i];
int q = m.Quant; int q = m.Quant;
m.Y1 = new Vp8Matrix();
m.Y2 = new Vp8Matrix();
m.Uv = new Vp8Matrix();
m.Y1.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY1Dc, 0, 127)]; m.Y1.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY1Dc, 0, 127)];
m.Y1.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q, 0, 127)]; m.Y1.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q, 0, 127)];

3
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs

@ -81,7 +81,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
int i; int i;
Span<int> tmp = scratch.Slice(0, 16); Span<int> tmp = scratch.Slice(0, 16);
tmp.Clear();
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
{ {
// vertical pass. // vertical pass.
@ -124,7 +123,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
int i; int i;
Span<int> tmp = scratch.Slice(0, 16); Span<int> tmp = scratch.Slice(0, 16);
tmp.Clear();
int srcIdx = 0; int srcIdx = 0;
int refIdx = 0; int refIdx = 0;
@ -163,7 +161,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public static void FTransformWht(Span<short> input, Span<short> output, Span<int> scratch) public static void FTransformWht(Span<short> input, Span<short> output, Span<int> scratch)
{ {
Span<int> tmp = scratch.Slice(0, 16); Span<int> tmp = scratch.Slice(0, 16);
tmp.Clear();
int i; int i;
int inputIdx = 0; int inputIdx = 0;

1
src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs

@ -49,7 +49,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.distribution.AsSpan().Clear(); this.distribution.AsSpan().Clear();
for (j = startBlock; j < endBlock; j++) for (j = startBlock; j < endBlock; j++)
{ {
this.output.AsSpan().Clear();
this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), this.output); this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), this.output);
// Convert coefficients to bin. // Convert coefficients to bin.

38
src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs

@ -3,7 +3,7 @@
namespace SixLabors.ImageSharp.Formats.Webp.Lossy namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
internal class Vp8Matrix internal unsafe struct Vp8Matrix
{ {
private static readonly int[][] BiasMatrices = private static readonly int[][] BiasMatrices =
{ {
@ -23,41 +23,29 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private const int SharpenBits = 11; private const int SharpenBits = 11;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Vp8Matrix"/> class. /// The quantizer steps.
/// </summary> /// </summary>
public Vp8Matrix() public fixed ushort Q[16];
{
this.Q = new ushort[16];
this.IQ = new ushort[16];
this.Bias = new uint[16];
this.ZThresh = new uint[16];
this.Sharpen = new short[16];
}
/// <summary>
/// Gets the quantizer steps.
/// </summary>
public ushort[] Q { get; }
/// <summary> /// <summary>
/// Gets the reciprocals, fixed point. /// The reciprocals, fixed point.
/// </summary> /// </summary>
public ushort[] IQ { get; } public fixed ushort IQ[16];
/// <summary> /// <summary>
/// Gets the rounding bias. /// The rounding bias.
/// </summary> /// </summary>
public uint[] Bias { get; } public fixed uint Bias[16];
/// <summary> /// <summary>
/// Gets the value below which a coefficient is zeroed. /// The value below which a coefficient is zeroed.
/// </summary> /// </summary>
public uint[] ZThresh { get; } public fixed uint ZThresh[16];
/// <summary> /// <summary>
/// Gets the frequency boosters for slight sharpening. /// The frequency boosters for slight sharpening.
/// </summary> /// </summary>
public short[] Sharpen { get; } public fixed short Sharpen[16];
/// <summary> /// <summary>
/// Returns the average quantizer. /// Returns the average quantizer.
@ -72,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int isAcCoeff = i > 0 ? 1 : 0; int isAcCoeff = i > 0 ? 1 : 0;
int bias = BiasMatrices[type][isAcCoeff]; int bias = BiasMatrices[type][isAcCoeff];
this.IQ[i] = (ushort)((1 << WebpConstants.QFix) / this.Q[i]); this.IQ[i] = (ushort)((1 << WebpConstants.QFix) / this.Q[i]);
this.Bias[i] = (uint)this.BIAS(bias); this.Bias[i] = (uint)BIAS(bias);
// zthresh is the exact value such that QUANTDIV(coeff, iQ, B) is: // zthresh is the exact value such that QUANTDIV(coeff, iQ, B) is:
// * zero if coeff <= zthresh // * zero if coeff <= zthresh
@ -106,6 +94,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return (sum + 8) >> 4; return (sum + 8) >> 4;
} }
private int BIAS(int b) => b << (WebpConstants.QFix - 8); private static int BIAS(int b) => b << (WebpConstants.QFix - 8);
} }
} }

17
src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs

@ -97,18 +97,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public void Clear() public void Clear()
{ {
this.YDcLevels.AsSpan().Clear(); Array.Clear(this.YDcLevels, 0, this.YDcLevels.Length);
this.YAcLevels.AsSpan().Clear(); Array.Clear(this.YAcLevels, 0, this.YAcLevels.Length);
this.UvLevels.AsSpan().Clear(); Array.Clear(this.UvLevels, 0, this.UvLevels.Length);
this.ModesI4.AsSpan().Clear(); Array.Clear(this.ModesI4, 0, this.ModesI4.Length);
Array.Clear(this.Derr, 0, this.Derr.Length);
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
this.Derr[i, j] = 0;
}
}
} }
public void InitScore() public void InitScore()

14
src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs

@ -8,19 +8,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
internal class Vp8SegmentInfo internal class Vp8SegmentInfo
{ {
/// <summary> /// <summary>
/// Gets or sets the quantization matrix y1. /// Gets the quantization matrix y1.
/// </summary> /// </summary>
public Vp8Matrix Y1 { get; set; } #pragma warning disable SA1401 // Fields should be private
public Vp8Matrix Y1;
/// <summary> /// <summary>
/// Gets or sets the quantization matrix y2. /// Gets the quantization matrix y2.
/// </summary> /// </summary>
public Vp8Matrix Y2 { get; set; } public Vp8Matrix Y2;
/// <summary> /// <summary>
/// Gets or sets the quantization matrix uv. /// Gets the quantization matrix uv.
/// </summary> /// </summary>
public Vp8Matrix Uv { get; set; } public Vp8Matrix Uv;
#pragma warning restore SA1401 // Fields should be private
/// <summary> /// <summary>
/// Gets or sets the quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness. /// Gets or sets the quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness.

24
src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs

@ -747,21 +747,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{ {
int xStep = 3; int xStep = 3;
int lastPixelPair = (len - 1) >> 1; int lastPixelPair = (len - 1) >> 1;
uint tluv = LossyUtils.LoadUv(topU[0], topV[0]); // top-left sample uint tluv = YuvConversion.LoadUv(topU[0], topV[0]); // top-left sample
uint luv = LossyUtils.LoadUv(curU[0], curV[0]); // left-sample uint luv = YuvConversion.LoadUv(curU[0], curV[0]); // left-sample
uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2;
LossyUtils.YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); YuvConversion.YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst);
if (bottomY != null) if (bottomY != null)
{ {
uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2;
LossyUtils.YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); YuvConversion.YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst);
} }
for (int x = 1; x <= lastPixelPair; x++) for (int x = 1; x <= lastPixelPair; x++)
{ {
uint tuv = LossyUtils.LoadUv(topU[x], topV[x]); // top sample uint tuv = YuvConversion.LoadUv(topU[x], topV[x]); // top sample
uint uv = LossyUtils.LoadUv(curU[x], curV[x]); // sample uint uv = YuvConversion.LoadUv(curU[x], curV[x]); // sample
// Precompute invariant values associated with first and second diagonals. // Precompute invariant values associated with first and second diagonals.
uint avg = tluv + tuv + luv + uv + 0x00080008u; uint avg = tluv + tuv + luv + uv + 0x00080008u;
@ -770,15 +770,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
uv0 = (diag12 + tluv) >> 1; uv0 = (diag12 + tluv) >> 1;
uint uv1 = (diag03 + tuv) >> 1; uint uv1 = (diag03 + tuv) >> 1;
int xMul2 = x * 2; int xMul2 = x * 2;
LossyUtils.YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((xMul2 - 1) * xStep)); YuvConversion.YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((xMul2 - 1) * xStep));
LossyUtils.YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst.Slice((xMul2 - 0) * xStep)); YuvConversion.YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst.Slice((xMul2 - 0) * xStep));
if (bottomY != null) if (bottomY != null)
{ {
uv0 = (diag03 + luv) >> 1; uv0 = (diag03 + luv) >> 1;
uv1 = (diag12 + uv) >> 1; uv1 = (diag12 + uv) >> 1;
LossyUtils.YuvToBgr(bottomY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((xMul2 - 1) * xStep)); YuvConversion.YuvToBgr(bottomY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((xMul2 - 1) * xStep));
LossyUtils.YuvToBgr(bottomY[xMul2 + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst.Slice((xMul2 + 0) * xStep)); YuvConversion.YuvToBgr(bottomY[xMul2 + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst.Slice((xMul2 + 0) * xStep));
} }
tluv = tuv; tluv = tuv;
@ -788,11 +788,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
if ((len & 1) == 0) if ((len & 1) == 0)
{ {
uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2;
LossyUtils.YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((len - 1) * xStep)); YuvConversion.YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((len - 1) * xStep));
if (bottomY != null) if (bottomY != null)
{ {
uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2;
LossyUtils.YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((len - 1) * xStep)); YuvConversion.YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((len - 1) * xStep));
} }
} }
} }

31
src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs

@ -300,5 +300,36 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2);
return (uv & ~0xff) == 0 ? uv : uv < 0 ? 0 : 255; return (uv & ~0xff) == 0 ? uv : uv < 0 ? 0 : 255;
} }
[MethodImpl(InliningOptions.ShortMethod)]
public static uint LoadUv(byte u, byte v) =>
(uint)(u | (v << 16)); // We process u and v together stashed into 32bit(16bit each).
[MethodImpl(InliningOptions.ShortMethod)]
public static void YuvToBgr(int y, int u, int v, Span<byte> bgr)
{
bgr[2] = (byte)YuvToR(y, v);
bgr[1] = (byte)YuvToG(y, u, v);
bgr[0] = (byte)YuvToB(y, u);
}
[MethodImpl(InliningOptions.ShortMethod)]
public static int YuvToB(int y, int u) => Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685);
[MethodImpl(InliningOptions.ShortMethod)]
public static int YuvToG(int y, int u, int v) => Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708);
[MethodImpl(InliningOptions.ShortMethod)]
public static int YuvToR(int y, int v) => Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234);
[MethodImpl(InliningOptions.ShortMethod)]
private static int MultHi(int v, int coeff) => (v * coeff) >> 8;
[MethodImpl(InliningOptions.ShortMethod)]
private static byte Clip8(int v)
{
int yuvMask = (256 << 6) - 1;
return (byte)((v & ~yuvMask) == 0 ? v >> 6 : v < 0 ? 0 : 255);
}
} }
} }

2
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -306,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
// Check for VP8 magic bytes. // Check for VP8 magic bytes.
this.currentStream.Read(this.buffer, 0, 3); this.currentStream.Read(this.buffer, 0, 3);
if (!this.buffer.AsSpan().Slice(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes)) if (!this.buffer.AsSpan(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes))
{ {
WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found");
} }

2
src/ImageSharp/Formats/Webp/WebpEncoder.cs

@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
public bool UseAlphaCompression { get; set; } public bool UseAlphaCompression { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public int EntropyPasses { get; set; } public int EntropyPasses { get; set; } = 1;
/// <inheritdoc/> /// <inheritdoc/>
public int SpatialNoiseShaping { get; set; } = 50; public int SpatialNoiseShaping { get; set; } = 50;

4
src/ImageSharp/IO/ChunkedMemoryStream.cs

@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.IO
const string bufferMessage = "Offset subtracted from the buffer length is less than count."; const string bufferMessage = "Offset subtracted from the buffer length is less than count.";
Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage);
return this.ReadImpl(buffer.AsSpan().Slice(offset, count)); return this.ReadImpl(buffer.AsSpan(offset, count));
} }
#if SUPPORTS_SPAN_STREAM #if SUPPORTS_SPAN_STREAM
@ -359,7 +359,7 @@ namespace SixLabors.ImageSharp.IO
const string bufferMessage = "Offset subtracted from the buffer length is less than count."; const string bufferMessage = "Offset subtracted from the buffer length is less than count.";
Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage);
this.WriteImpl(buffer.AsSpan().Slice(offset, count)); this.WriteImpl(buffer.AsSpan(offset, count));
} }
#if SUPPORTS_SPAN_STREAM #if SUPPORTS_SPAN_STREAM

47
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

@ -417,19 +417,40 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
for (int r = 1; r < IndexCount; r++) for (int r = 1; r < IndexCount; r++)
{ {
// Currently, RyuJIT hoists the invariants of multi-level nested loop only to the
// immediate outer loop. See https://github.com/dotnet/runtime/issues/61420
// To ensure the calculation doesn't happen repeatedly, hoist some of the calculations
// in the form of ind1* manually.
int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) +
(r << (IndexBits + IndexAlphaBits + 1)) +
(r << (IndexBits * 2)) +
(r << (IndexBits + 1)) +
r;
volumeSpan.Clear(); volumeSpan.Clear();
for (int g = 1; g < IndexCount; g++) for (int g = 1; g < IndexCount; g++)
{ {
int ind1G = ind1R +
(g << (IndexBits + IndexAlphaBits)) +
(g << IndexBits) +
g;
int r_g = r + g;
areaSpan.Clear(); areaSpan.Clear();
for (int b = 1; b < IndexCount; b++) for (int b = 1; b < IndexCount; b++)
{ {
int ind1B = ind1G +
((r_g + b) << IndexAlphaBits) +
b;
Moment line = default; Moment line = default;
for (int a = 1; a < IndexAlphaCount; a++) for (int a = 1; a < IndexAlphaCount; a++)
{ {
int ind1 = GetPaletteIndex(r, g, b, a); int ind1 = ind1B + a;
line += momentSpan[ind1]; line += momentSpan[ind1];
areaSpan[a] += line; areaSpan[a] += line;
@ -628,13 +649,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
for (int r = cube.RMin + 1; r <= cube.RMax; r++) for (int r = cube.RMin + 1; r <= cube.RMax; r++)
{ {
// Currently, RyuJIT hoists the invariants of multi-level nested loop only to the
// immediate outer loop. See https://github.com/dotnet/runtime/issues/61420
// To ensure the calculation doesn't happen repeatedly, hoist some of the calculations
// in the form of ind1* manually.
int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) +
(r << (IndexBits + IndexAlphaBits + 1)) +
(r << (IndexBits * 2)) +
(r << (IndexBits + 1)) +
r;
for (int g = cube.GMin + 1; g <= cube.GMax; g++) for (int g = cube.GMin + 1; g <= cube.GMax; g++)
{ {
int ind1G = ind1R +
(g << (IndexBits + IndexAlphaBits)) +
(g << IndexBits) +
g;
int r_g = r + g;
for (int b = cube.BMin + 1; b <= cube.BMax; b++) for (int b = cube.BMin + 1; b <= cube.BMax; b++)
{ {
int ind1B = ind1G +
((r_g + b) << IndexAlphaBits) +
b;
for (int a = cube.AMin + 1; a <= cube.AMax; a++) for (int a = cube.AMin + 1; a <= cube.AMax; a++)
{ {
tagSpan[GetPaletteIndex(r, g, b, a)] = label; int index = ind1B + a;
tagSpan[index] = label;
} }
} }
} }

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right); ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right);
Span<double> kernelValues = this.tempValues.AsSpan().Slice(0, kernel.Length); Span<double> kernelValues = this.tempValues.AsSpan(0, kernel.Length);
double sum = 0; double sum = 0;
for (int j = left; j <= right; j++) for (int j = left; j <= right; j++)

49
tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs

@ -76,34 +76,29 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
return image.Height; return image.Height;
} }
/* Results 17.06.2021 /* Results 04.11.2021
* BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362 * BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1320 (21H1/May2021Update)
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.202 .NET SDK=6.0.100-rc.2.21505.57
[Host] : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT [Host] : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT
Job-AQFZAV : .NET Framework 4.8 (4.8.4180.0), X64 RyuJIT Job-WQLXJO : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT
Job-YCDAPQ : .NET Core 2.1.18 (CoreCLR 4.6.28801.04, CoreFX 4.6.28802.05), X64 RyuJIT Job-OJJAMD : .NET Core 3.1.20 (CoreCLR 4.700.21.47003, CoreFX 4.700.21.47101), X64 RyuJIT
Job-WMTYOZ : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT Job-OMFOAS : .NET Framework 4.8 (4.8.4420.0), X64 RyuJIT
IterationCount=3 LaunchCount=1 WarmupCount=3 | Method | Job | Runtime | Arguments | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
| Method | Job | Runtime | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | |--------------------------- |----------- |--------------------- |---------------------- |---------------------- |------------------------- |-----------:|----------:|--------:|---------:|------:|------:|----------:|
|--------------------------- |----------- |-------------- |---------------------- |------------------------- |-----------:|----------:|---------:|----------:|----------:|------:|------------:| | 'Magick Lossy Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 107.9 ms | 28.91 ms | 1.58 ms | - | - | - | 25 KB |
| 'Magick Lossy Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.8 ms | 6.28 ms | 0.34 ms | - | - | - | 17.65 KB | | 'ImageSharp Lossy Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 282.3 ms | 25.40 ms | 1.39 ms | 500.0000 | - | - | 2,428 KB |
| 'ImageSharp Lossy Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,145.0 ms | 110.82 ms | 6.07 ms | - | - | - | 2779.53 KB | | 'Magick Lossless Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.3 ms | 11.99 ms | 0.66 ms | - | - | - | 16 KB |
| 'Magick Lossless Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 145.9 ms | 8.55 ms | 0.47 ms | - | - | - | 18.05 KB | | 'ImageSharp Lossless Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 280.2 ms | 6.21 ms | 0.34 ms | - | - | - | 2,092 KB |
| 'ImageSharp Lossless Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,694.1 ms | 55.09 ms | 3.02 ms | 4000.0000 | 1000.0000 | - | 30556.87 KB | | 'Magick Lossy Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.2 ms | 9.32 ms | 0.51 ms | - | - | - | 15 KB |
| 'Magick Lossy Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.7 ms | 1.89 ms | 0.10 ms | - | - | - | 15.75 KB | | 'ImageSharp Lossy Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 295.8 ms | 21.25 ms | 1.16 ms | 500.0000 | - | - | 2,427 KB |
| 'ImageSharp Lossy Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 741.6 ms | 21.45 ms | 1.18 ms | - | - | - | 2767.85 KB | | 'Magick Lossless Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.5 ms | 4.07 ms | 0.22 ms | - | - | - | 15 KB |
| 'Magick Lossless Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 146.1 ms | 9.52 ms | 0.52 ms | - | - | - | 16.54 KB | | 'ImageSharp Lossless Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 464.0 ms | 55.70 ms | 3.05 ms | - | - | - | 2,090 KB |
| 'ImageSharp Lossless Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 522.5 ms | 21.15 ms | 1.16 ms | 4000.0000 | 1000.0000 | - | 22860.02 KB | | 'Magick Lossy Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 108.0 ms | 29.60 ms | 1.62 ms | - | - | - | 32 KB |
| 'Magick Lossy Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.9 ms | 5.34 ms | 0.29 ms | - | - | - | 15.45 KB | | 'ImageSharp Lossy Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 564.9 ms | 29.69 ms | 1.63 ms | - | - | - | 2,436 KB |
| 'ImageSharp Lossy Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 748.8 ms | 290.47 ms | 15.92 ms | - | - | - | 2767.84 KB | | 'Magick Lossless Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.2 ms | 4.74 ms | 0.26 ms | - | - | - | 18 KB |
| 'Magick Lossless Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 146.1 ms | 1.14 ms | 0.06 ms | - | - | - | 15.9 KB | | 'ImageSharp Lossless Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,767.5 ms | 106.33 ms | 5.83 ms | - | - | - | 9,729 KB |
| 'ImageSharp Lossless Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 480.7 ms | 25.25 ms | 1.38 ms | 4000.0000 | 1000.0000 | - | 22859.7 KB |
| 'Magick Lossy Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.7 ms | 4.71 ms | 0.26 ms | - | - | - | 15.48 KB |
| 'ImageSharp Lossy Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 645.7 ms | 61.00 ms | 3.34 ms | - | - | - | 2768.13 KB |
| 'Magick Lossless Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 146.5 ms | 18.63 ms | 1.02 ms | - | - | - | 15.8 KB |
| 'ImageSharp Lossless Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 306.7 ms | 32.31 ms | 1.77 ms | 4000.0000 | 1000.0000 | - | 22860.02 KB |
*/ */
} }
} }

2
tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs

@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void PngCoreWuNoDither() public void PngCoreWuNoDither()
{ {
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }), ColorType = PngColorType.Palette };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
} }

101
tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs

@ -4,6 +4,7 @@
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using ImageMagick; using ImageMagick;
using ImageMagick.Formats;
using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
@ -44,8 +45,22 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void MagickWebpLossy() public void MagickWebpLossy()
{ {
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "lossless", false);
this.webpMagick.Write(memoryStream, MagickFormat.WebP); var defines = new WebPWriteDefines
{
Lossless = false,
Method = 4,
AlphaCompression = WebPAlphaCompression.None,
FilterStrength = 60,
SnsStrength = 50,
Pass = 1,
// 100 means off.
NearLossless = 100
};
this.webpMagick.Quality = 75;
this.webpMagick.Write(memoryStream, defines);
} }
[Benchmark(Description = "ImageSharp Webp Lossy")] [Benchmark(Description = "ImageSharp Webp Lossy")]
@ -54,7 +69,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
this.webp.Save(memoryStream, new WebpEncoder() this.webp.Save(memoryStream, new WebpEncoder()
{ {
FileFormat = WebpFileFormatType.Lossy FileFormat = WebpFileFormatType.Lossy,
Method = WebpEncodingMethod.Level4,
UseAlphaCompression = false,
FilterStrength = 60,
SpatialNoiseShaping = 50,
EntropyPasses = 1
}); });
} }
@ -62,8 +82,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void MagickWebpLossless() public void MagickWebpLossless()
{ {
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "lossless", true); var defines = new WebPWriteDefines
this.webpMagick.Write(memoryStream, MagickFormat.WebP); {
Lossless = true,
Method = 4,
// 100 means off.
NearLossless = 100
};
this.webpMagick.Quality = 75;
this.webpMagick.Write(memoryStream, defines);
} }
[Benchmark(Description = "ImageSharp Webp Lossless")] [Benchmark(Description = "ImageSharp Webp Lossless")]
@ -72,41 +101,43 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
this.webp.Save(memoryStream, new WebpEncoder() this.webp.Save(memoryStream, new WebpEncoder()
{ {
FileFormat = WebpFileFormatType.Lossless FileFormat = WebpFileFormatType.Lossless,
Method = WebpEncodingMethod.Level4,
NearLossless = false,
// This is equal to exact = false in libwebp, which is the default.
TransparentColorMode = WebpTransparentColorMode.Clear
}); });
} }
/* Results 17.06.2021 /* Results 04.11.2021
* Summary * * Summary *
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.630 (2004/?/20H1) BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1320 (21H1/May2021Update)
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.100 .NET SDK=6.0.100-rc.2.21505.57
[Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT [Host] : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT
Job-OUUGWL : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT Job-WQLXJO : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT
Job-GAIITM : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT Job-OJJAMD : .NET Core 3.1.20 (CoreCLR 4.700.21.47003, CoreFX 4.700.21.47101), X64 RyuJIT
Job-HWOBSO : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT Job-OMFOAS : .NET Framework 4.8 (4.8.4420.0), X64 RyuJIT
| Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | IterationCount=3 LaunchCount=1 WarmupCount=3
|--------------------------- |----------- |-------------- |------------- |----------:|-----------:|----------:|------:|--------:|-----------:|----------:|----------:|-------------:|
| 'Magick Webp Lossy' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 23.30 ms | 0.869 ms | 0.048 ms | 0.14 | 0.00 | - | - | - | 68.19 KB | | Method | Job | Runtime | Arguments | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
| 'ImageSharp Webp Lossy' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 68.22 ms | 16.454 ms | 0.902 ms | 0.42 | 0.01 | 6125.0000 | 125.0000 | - | 26359.49 KB | |--------------------------- |----------- |--------------------- |---------------------- |------------- |----------:|----------:|---------:|------:|--------:|------------:|----------:|----------:|-----------:|
| 'Magick Webp Lossless' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 161.96 ms | 9.879 ms | 0.541 ms | 1.00 | 0.00 | - | - | - | 520.28 KB | | 'Magick Webp Lossy' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 23.33 ms | 1.491 ms | 0.082 ms | 0.15 | 0.00 | - | - | - | 67 KB |
| 'ImageSharp Webp Lossless' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 370.88 ms | 58.875 ms | 3.227 ms | 2.29 | 0.02 | 34000.0000 | 5000.0000 | 2000.0000 | 163177.15 KB | | 'ImageSharp Webp Lossy' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 245.80 ms | 24.288 ms | 1.331 ms | 1.53 | 0.01 | 135000.0000 | - | - | 552,713 KB |
| | | | | | | | | | | | | | | 'Magick Webp Lossless' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 160.36 ms | 11.131 ms | 0.610 ms | 1.00 | 0.00 | - | - | - | 518 KB |
| 'Magick Webp Lossy' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 23.35 ms | 0.428 ms | 0.023 ms | 0.14 | 0.00 | - | - | - | 67.76 KB | | 'ImageSharp Webp Lossless' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 313.93 ms | 45.605 ms | 2.500 ms | 1.96 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 161,670 KB |
| 'ImageSharp Webp Lossy' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 43.95 ms | 2.850 ms | 0.156 ms | 0.27 | 0.00 | 6250.0000 | 250.0000 | 83.3333 | 26284.72 KB | | | | | | | | | | | | | | | |
| 'Magick Webp Lossless' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 161.44 ms | 3.749 ms | 0.206 ms | 1.00 | 0.00 | - | - | - | 519.26 KB | | 'Magick Webp Lossy' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 23.36 ms | 2.289 ms | 0.125 ms | 0.15 | 0.00 | - | - | - | 67 KB |
| 'ImageSharp Webp Lossless' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 335.78 ms | 78.666 ms | 4.312 ms | 2.08 | 0.03 | 34000.0000 | 5000.0000 | 2000.0000 | 162727.56 KB | | 'ImageSharp Webp Lossy' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 254.64 ms | 19.620 ms | 1.075 ms | 1.59 | 0.00 | 135000.0000 | - | - | 552,713 KB |
| | | | | | | | | | | | | | | 'Magick Webp Lossless' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 160.30 ms | 9.549 ms | 0.523 ms | 1.00 | 0.00 | - | - | - | 518 KB |
| 'Magick Webp Lossy' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 23.48 ms | 4.325 ms | 0.237 ms | 0.15 | 0.00 | - | - | - | 67.66 KB | | 'ImageSharp Webp Lossless' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 320.35 ms | 22.924 ms | 1.257 ms | 2.00 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 161,669 KB |
| 'ImageSharp Webp Lossy' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 43.29 ms | 16.503 ms | 0.905 ms | 0.27 | 0.01 | 6272.7273 | 272.7273 | 90.9091 | 26284.86 KB | | | | | | | | | | | | | | | |
| 'Magick Webp Lossless' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 161.81 ms | 10.693 ms | 0.586 ms | 1.00 | 0.00 | - | - | - | 523.25 KB | | 'Magick Webp Lossy' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 23.37 ms | 0.908 ms | 0.050 ms | 0.15 | 0.00 | - | - | - | 68 KB |
| 'ImageSharp Webp Lossless' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 323.97 ms | 235.468 ms | 12.907 ms | 2.00 | 0.08 | 34000.0000 | 5000.0000 | 2000.0000 | 162724.84 KB | | 'ImageSharp Webp Lossy' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 378.67 ms | 25.540 ms | 1.400 ms | 2.36 | 0.01 | 135000.0000 | - | - | 554,351 KB |
| | | | | | | | | | | | | | | 'Magick Webp Lossless' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 160.13 ms | 5.115 ms | 0.280 ms | 1.00 | 0.00 | - | - | - | 520 KB |
| 'Magick Webp Lossy' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 23.36 ms | 0.448 ms | 0.025 ms | 0.14 | 0.00 | - | - | - | 67.66 KB | | 'ImageSharp Webp Lossless' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 379.01 ms | 71.192 ms | 3.902 ms | 2.37 | 0.02 | 34000.0000 | 5000.0000 | 2000.0000 | 162,119 KB |
| 'ImageSharp Webp Lossy' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 40.11 ms | 2.465 ms | 0.135 ms | 0.25 | 0.00 | 6307.6923 | 230.7692 | 76.9231 | 26284.71 KB |
| 'Magick Webp Lossless' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 161.55 ms | 6.662 ms | 0.365 ms | 1.00 | 0.00 | - | - | - | 518.84 KB |
| 'ImageSharp Webp Lossless' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 298.73 ms | 17.953 ms | 0.984 ms | 1.85 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 162725.13 KB |
*/ */
} }
} }

26
tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs

@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void Rgb24() public void Rgb24()
{ {
var source = new Rgb24(1, 22, 231); var source = new Rgb24(1, 22, 231);
// Act: // Act:
var color = new Color(source); var color = new Color(source);
@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void Bgr24() public void Bgr24()
{ {
var source = new Bgr24(1, 22, 231); var source = new Bgr24(1, 22, 231);
// Act: // Act:
var color = new Color(source); var color = new Color(source);
@ -88,6 +88,28 @@ namespace SixLabors.ImageSharp.Tests
Bgr24 data = color; Bgr24 data = color;
Assert.Equal(source, data); Assert.Equal(source, data);
} }
[Fact]
public void GenericPixel()
{
AssertGenericPixel(new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue));
AssertGenericPixel(new Rgba64(1, 2, ushort.MaxValue, ushort.MaxValue - 1));
AssertGenericPixel(new Rgb48(1, 2, ushort.MaxValue - 1));
AssertGenericPixel(new La32(1, ushort.MaxValue - 1));
AssertGenericPixel(new L16(ushort.MaxValue - 1));
AssertGenericPixel(new Rgba32(1, 2, 255, 254));
}
private static void AssertGenericPixel<TPixel>(TPixel source)
where TPixel : unmanaged, IPixel<TPixel>
{
// Act:
var color = Color.FromPixel(source);
// Assert:
TPixel actual = color.ToPixel<TPixel>();
Assert.Equal(source, actual);
}
} }
} }
} }

122
tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs

@ -0,0 +1,122 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Linq;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.WebP
{
[Trait("Format", "Webp")]
public class LossyUtilsTests
{
private static void RunVp8Sse4X4Test()
{
byte[] a =
{
27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129,
129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 29, 29, 28,
28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 26,
26, 26, 26, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128,
128, 128, 128, 128, 128, 128, 128, 28, 27, 27, 26, 26, 27, 27, 28, 27, 28, 28, 29, 29, 28, 28, 27,
129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128
};
byte[] b =
{
26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 204, 204, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 26, 26, 26,
26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204
};
int expected = 27;
int actual = LossyUtils.Vp8_Sse4X4(a, b);
Assert.Equal(expected, actual);
}
private static void RunMean16x4Test()
{
// arrange
byte[] input =
{
154, 145, 102, 115, 127, 129, 126, 125, 126, 120, 133, 152, 157, 153, 119, 94, 104, 116, 111, 113,
113, 109, 105, 124, 173, 175, 177, 170, 175, 172, 166, 164, 151, 141, 99, 114, 125, 126, 135, 150,
133, 115, 127, 149, 141, 168, 100, 54, 110, 117, 115, 116, 119, 115, 117, 130, 174, 174, 174, 157,
146, 171, 166, 158, 117, 140, 96, 111, 119, 119, 136, 171, 188, 134, 121, 126, 136, 119, 59, 77,
109, 115, 113, 120, 120, 117, 128, 115, 174, 173, 173, 161, 152, 148, 153, 162, 105, 140, 96, 114,
115, 122, 141, 173, 190, 190, 142, 106, 151, 78, 66, 141, 110, 117, 123, 136, 118, 124, 127, 114,
173, 175, 166, 155, 155, 159, 159, 158
};
uint[] dc = new uint[4];
uint[] expectedDc = { 1940, 2139, 2252, 1813 };
// act
LossyUtils.Mean16x4(input, dc);
// assert
Assert.True(dc.SequenceEqual(expectedDc));
}
private static void RunHadamardTransformTest()
{
byte[] a =
{
27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129,
129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 29, 29, 28,
28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 26,
26, 26, 26, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128,
128, 128, 128, 128, 128, 128, 128, 28, 27, 27, 26, 26, 27, 27, 28, 27, 28, 28, 29, 29, 28, 28, 27
};
byte[] b =
{
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28
};
ushort[] w = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 };
int expected = 2;
int actual = LossyUtils.Vp8Disto4X4(a, b, w, new int[16]);
Assert.Equal(expected, actual);
}
[Fact]
public void Vp8Sse4X4_Works() => RunVp8Sse4X4Test();
[Fact]
public void Mean16x4_Works() => RunMean16x4Test();
[Fact]
public void HadamardTransform_Works() => RunHadamardTransformTest();
#if SUPPORTS_RUNTIME_INTRINSICS
[Fact]
public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll);
[Fact]
public void Vp8Sse4X4_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableHWIntrinsic);
[Fact]
public void Mean16x4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.AllowAll);
[Fact]
public void Mean16x4_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.DisableHWIntrinsic);
[Fact]
public void HadamardTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.AllowAll);
[Fact]
public void HadamardTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableHWIntrinsic);
#endif
}
}

53
tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs

@ -0,0 +1,53 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Linq;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.WebP
{
[Trait("Format", "Webp")]
public class QuantEncTests
{
private static unsafe void RunQuantizeBlockTest()
{
// arrange
short[] input = { 378, 777, -851, 888, 259, 148, 0, -111, -185, -185, -74, -37, 148, 74, 111, 74 };
short[] output = new short[16];
ushort[] q = { 42, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37 };
ushort[] iq = { 3120, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542 };
uint[] bias = { 49152, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296 };
uint[] zthresh = { 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21 };
short[] expectedOutput = { 9, 21, 7, -5, 4, -23, 24, 0, -5, 4, 2, -2, -3, -1, 3, 2 };
int expectedResult = 1;
Vp8Matrix vp8Matrix = default;
for (int i = 0; i < 16; i++)
{
vp8Matrix.Q[i] = q[i];
vp8Matrix.IQ[i] = iq[i];
vp8Matrix.Bias[i] = bias[i];
vp8Matrix.ZThresh[i] = zthresh[i];
}
// act
int actualResult = QuantEnc.QuantizeBlock(input, output, ref vp8Matrix);
// assert
Assert.True(output.SequenceEqual(expectedOutput));
Assert.Equal(expectedResult, actualResult);
}
[Fact]
public void QuantizeBlock_Works() => RunQuantizeBlockTest();
#if SUPPORTS_RUNTIME_INTRINSICS
[Fact]
public void QuantizeBlock_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.AllowAll);
[Fact]
public void QuantizeBlock_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableHWIntrinsic);
#endif
}
}
Loading…
Cancel
Save