Browse Source

Merge remote-tracking branch 'origin/master' into bp/dofiltersse2

# Conflicts:
#	src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
pull/1871/head
Brian Popow 4 years ago
parent
commit
3cc321ca6e
  1. 9
      src/ImageSharp/Color/Color.cs
  2. 39
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs
  3. 118
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
  4. 123
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  5. 17
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  6. 1
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
  7. 82
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  8. 109
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  9. 13
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  10. 100
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  11. 5
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  12. 49
      src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
  13. 10
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
  14. 10
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
  15. 10
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
  16. 23
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs
  17. 0
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt
  18. 122
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  19. 2
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  20. 7
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  21. 6
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  22. 5
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs
  23. 361
      src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
  24. 79
      src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
  25. 6
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs
  26. 9
      src/ImageSharp/ImageSharp.csproj
  27. 6
      tests/ImageSharp.Tests.ProfilingSandbox/Program.cs
  28. 51
      tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs
  29. 50
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  30. 14
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs
  31. 129
      tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs
  32. 26
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  33. 8
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  34. 2
      tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs
  35. 4
      tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs
  36. 5
      tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs
  37. 90
      tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs
  38. 5
      tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs
  39. 2
      tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs

9
src/ImageSharp/Color/Color.cs

@ -270,8 +270,15 @@ namespace SixLabors.ImageSharp
return pixel;
}
if (this.boxedHighPrecisionPixel is null)
{
pixel = default;
pixel.FromRgba64(this.data);
return pixel;
}
pixel = default;
pixel.FromRgba64(this.data);
pixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return pixel;
}

39
src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs

@ -0,0 +1,39 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal unsafe partial struct Block8x8
{
[FieldOffset(0)]
public Vector128<short> V0;
[FieldOffset(16)]
public Vector128<short> V1;
[FieldOffset(32)]
public Vector128<short> V2;
[FieldOffset(48)]
public Vector128<short> V3;
[FieldOffset(64)]
public Vector128<short> V4;
[FieldOffset(80)]
public Vector128<short> V5;
[FieldOffset(96)]
public Vector128<short> V6;
[FieldOffset(112)]
public Vector128<short> V7;
[FieldOffset(0)]
public Vector256<short> V01;
[FieldOffset(32)]
public Vector256<short> V23;
[FieldOffset(64)]
public Vector256<short> V45;
[FieldOffset(96)]
public Vector256<short> V67;
}
}
#endif

118
src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
// ReSharper disable once InconsistentNaming
[StructLayout(LayoutKind.Explicit)]
internal unsafe struct Block8x8 : IEquatable<Block8x8>
internal unsafe partial struct Block8x8
{
/// <summary>
/// A number of scalar coefficients in a <see cref="Block8x8F"/>
@ -36,34 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
private fixed short data[Size];
#pragma warning restore IDE0051
#if SUPPORTS_RUNTIME_INTRINSICS
[FieldOffset(0)]
public Vector128<short> V0;
[FieldOffset(16)]
public Vector128<short> V1;
[FieldOffset(32)]
public Vector128<short> V2;
[FieldOffset(48)]
public Vector128<short> V3;
[FieldOffset(64)]
public Vector128<short> V4;
[FieldOffset(80)]
public Vector128<short> V5;
[FieldOffset(96)]
public Vector128<short> V6;
[FieldOffset(112)]
public Vector128<short> V7;
[FieldOffset(0)]
public Vector256<short> V01;
[FieldOffset(32)]
public Vector256<short> V23;
[FieldOffset(64)]
public Vector256<short> V45;
[FieldOffset(96)]
public Vector256<short> V67;
#endif
/// <summary>
/// Gets or sets a <see cref="short"/> value at the given index
/// </summary>
@ -102,74 +74,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
set => this[(y * 8) + x] = value;
}
public static bool operator ==(Block8x8 left, Block8x8 right) => left.Equals(right);
public static bool operator !=(Block8x8 left, Block8x8 right) => !left.Equals(right);
/// <summary>
/// Multiply all elements by a given <see cref="int"/>
/// </summary>
public static Block8x8 operator *(Block8x8 block, int value)
{
Block8x8 result = block;
for (int i = 0; i < Size; i++)
{
int val = result[i];
val *= value;
result[i] = (short)val;
}
return result;
}
/// <summary>
/// Divide all elements by a given <see cref="int"/>
/// </summary>
public static Block8x8 operator /(Block8x8 block, int value)
{
Block8x8 result = block;
for (int i = 0; i < Size; i++)
{
int val = result[i];
val /= value;
result[i] = (short)val;
}
return result;
}
/// <summary>
/// Add an <see cref="int"/> to all elements
/// </summary>
public static Block8x8 operator +(Block8x8 block, int value)
{
Block8x8 result = block;
for (int i = 0; i < Size; i++)
{
int val = result[i];
val += value;
result[i] = (short)val;
}
return result;
}
/// <summary>
/// Subtract an <see cref="int"/> from all elements
/// </summary>
public static Block8x8 operator -(Block8x8 block, int value)
{
Block8x8 result = block;
for (int i = 0; i < Size; i++)
{
int val = result[i];
val -= value;
result[i] = (short)val;
}
return result;
}
public static Block8x8 Load(Span<short> data)
{
Unsafe.SkipInit(out Block8x8 result);
@ -260,26 +164,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return sb.ToString();
}
/// <inheritdoc />
public bool Equals(Block8x8 other)
{
for (int i = 0; i < Size; i++)
{
if (this[i] != other[i])
{
return false;
}
}
return true;
}
/// <inheritdoc />
public override bool Equals(object obj) => obj is Block8x8 other && this.Equals(other);
/// <inheritdoc />
public override int GetHashCode() => (this[0] * 31) + this[1];
/// <summary>
/// Returns index of the last non-zero element in given matrix.
/// </summary>

123
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -98,58 +97,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
set => this[(y * 8) + x] = value;
}
public static Block8x8F operator *(Block8x8F block, float value)
{
Block8x8F result = block;
for (int i = 0; i < Size; i++)
{
float val = result[i];
val *= value;
result[i] = val;
}
return result;
}
public static Block8x8F operator /(Block8x8F block, float value)
{
Block8x8F result = block;
for (int i = 0; i < Size; i++)
{
float val = result[i];
val /= value;
result[i] = val;
}
return result;
}
public static Block8x8F operator +(Block8x8F block, float value)
{
Block8x8F result = block;
for (int i = 0; i < Size; i++)
{
float val = result[i];
val += value;
result[i] = val;
}
return result;
}
public static Block8x8F operator -(Block8x8F block, float value)
{
Block8x8F result = block;
for (int i = 0; i < Size; i++)
{
float val = result[i];
val -= value;
result[i] = val;
}
return result;
}
public static Block8x8F Load(Span<float> data)
{
Block8x8F result = default;
@ -157,13 +104,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return result;
}
public static Block8x8F Load(Span<int> data)
{
Block8x8F result = default;
result.LoadFrom(data);
return result;
}
/// <summary>
/// Load raw 32bit floating point data from source.
/// </summary>
@ -177,15 +117,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float));
}
/// <summary>
/// Load raw 32bit floating point data from source.
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="source">Source</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void LoadFrom(Block8x8F* blockPtr, Span<float> source)
=> blockPtr->LoadFrom(source);
/// <summary>
/// Load raw 32bit floating point data from source
/// </summary>
@ -202,44 +133,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
}
/// <summary>
/// Copy raw 32bit floating point data to dest,
/// </summary>
/// <param name="dest">Destination</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyTo(Span<float> dest)
{
ref byte d = ref Unsafe.As<float, byte>(ref MemoryMarshal.GetReference(dest));
ref byte s = ref Unsafe.As<Block8x8F, byte>(ref this);
Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float));
}
/// <summary>
/// Convert scalars to byte-s and copy to dest,
/// </summary>
/// <param name="blockPtr">Pointer to block</param>
/// <param name="dest">Destination</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span<byte> dest)
{
float* fPtr = (float*)blockPtr;
for (int i = 0; i < Size; i++)
{
dest[i] = (byte)*fPtr;
fPtr++;
}
}
/// <summary>
/// Copy raw 32bit floating point data to dest.
/// </summary>
/// <param name="blockPtr">The block pointer.</param>
/// <param name="dest">The destination.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span<float> dest)
=> blockPtr->ScaledCopyTo(dest);
/// <summary>
/// Copy raw 32bit floating point data to dest
/// </summary>
@ -253,22 +146,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
}
/// <summary>
/// Copy raw 32bit floating point data to dest
/// </summary>
/// <param name="dest">Destination</param>
public unsafe void ScaledCopyTo(Span<int> dest)
{
fixed (Vector4* ptr = &this.V0L)
{
var fp = (float*)ptr;
for (int i = 0; i < Size; i++)
{
dest[i] = (int)fp[i];
}
}
}
public float[] ToArray()
{
float[] result = new float[Size];

17
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -197,6 +197,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// </summary>
public readonly Span<float> Component3;
/// <summary>
/// Initializes a new instance of the <see cref="ComponentValues"/> struct.
/// </summary>
/// <param name="componentProcessors">The 1-4 sized list of component post processors.</param>
/// <param name="row">The row to convert</param>
public ComponentValues(IReadOnlyList<JpegComponentPostProcessor> componentProcessors, int row)
{
this.ComponentCount = componentProcessors.Count;
this.Component0 = componentProcessors[0].GetColorBufferRowSpan(row);
// In case of grayscale, Component1 and Component2 point to Component0 memory area
this.Component1 = this.ComponentCount > 1 ? componentProcessors[1].GetColorBufferRowSpan(row) : this.Component0;
this.Component2 = this.ComponentCount > 2 ? componentProcessors[2].GetColorBufferRowSpan(row) : this.Component0;
this.Component3 = this.ComponentCount > 3 ? componentProcessors[3].GetColorBufferRowSpan(row) : Span<float>.Empty;
}
/// <summary>
/// Initializes a new instance of the <see cref="ComponentValues"/> struct.
/// </summary>

1
src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs

@ -5,7 +5,6 @@ using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <inheritdoc />
/// <summary>
/// Represents decompressed, unprocessed jpeg data with spectral space <see cref="IJpegComponent" />-s.
/// </summary>

82
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs

@ -1,82 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Encapsulates the implementation of processing "raw" jpeg buffers into Jpeg image channels.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct JpegBlockPostProcessor
{
/// <summary>
/// Source block
/// </summary>
public Block8x8F SourceBlock;
/// <summary>
/// The quantization table as <see cref="Block8x8F"/>.
/// </summary>
public Block8x8F DequantiazationTable;
/// <summary>
/// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block.
/// </summary>
private Size subSamplingDivisors;
/// <summary>
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
/// </summary>
/// <param name="decoder">The raw jpeg data.</param>
/// <param name="component">The raw component.</param>
public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component)
{
int qtIndex = component.QuantizationTableIndex;
this.DequantiazationTable = decoder.QuantizationTables[qtIndex];
this.subSamplingDivisors = component.SubSamplingDivisors;
this.SourceBlock = default;
}
/// <summary>
/// Processes 'sourceBlock' producing Jpeg color channel values from spectral values:
/// - Dequantize
/// - Applying IDCT
/// - Level shift by +maximumValue/2, clip to [0, maximumValue]
/// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in <see cref="subSamplingDivisors"/>.
/// </summary>
/// <param name="sourceBlock">The source block.</param>
/// <param name="destAreaOrigin">Reference to the origin of the destination pixel area.</param>
/// <param name="destAreaStride">The width of the destination pixel buffer.</param>
/// <param name="maximumValue">The maximum value derived from the bitdepth.</param>
public void ProcessBlockColorsInto(
ref Block8x8 sourceBlock,
ref float destAreaOrigin,
int destAreaStride,
float maximumValue)
{
ref Block8x8F block = ref this.SourceBlock;
block.LoadFrom(ref sourceBlock);
// Dequantize:
block.MultiplyInPlace(ref this.DequantiazationTable);
FastFloatingPointDCT.TransformIDCT(ref block);
// To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations.
// To be "more accurate", we need to emulate this by rounding!
block.NormalizeColorsAndRoundInPlace(maximumValue);
block.ScaledCopyTo(
ref destAreaOrigin,
destAreaStride,
this.subSamplingDivisors.Width,
this.subSamplingDivisors.Height);
}
}
}

109
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs

@ -11,11 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
internal class JpegComponentPostProcessor : IDisposable
{
/// <summary>
/// Points to the current row in <see cref="Component"/>.
/// </summary>
private int currentComponentRowInBlocks;
/// <summary>
/// The size of the area in <see cref="ColorBuffer"/> corresponding to one 8x8 Jpeg block
/// </summary>
@ -26,6 +21,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
private readonly JpegFrame frame;
/// <summary>
/// Gets the maximal number of block rows being processed in one step.
/// </summary>
private readonly int blockRowsPerStep;
/// <summary>
/// Gets the <see cref="IJpegComponent"/> component containing decoding meta information.
/// </summary>
private readonly IJpegComponent component;
/// <summary>
/// Gets the <see cref="IRawJpegData"/> instance containing decoding meta information.
/// </summary>
private readonly IRawJpegData rawJpeg;
/// <summary>
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class.
/// </summary>
@ -33,98 +43,87 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.frame = frame;
this.Component = component;
this.RawJpeg = rawJpeg;
this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
this.component = component;
this.rawJpeg = rawJpeg;
this.blockAreaSize = this.component.SubSamplingDivisors * 8;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
postProcessorBufferSize.Width,
postProcessorBufferSize.Height,
this.blockAreaSize.Height);
this.BlockRowsPerStep = postProcessorBufferSize.Height / 8 / this.Component.SubSamplingDivisors.Height;
this.blockRowsPerStep = postProcessorBufferSize.Height / 8 / this.component.SubSamplingDivisors.Height;
}
public IRawJpegData RawJpeg { get; }
/// <summary>
/// Gets the <see cref="Component"/>
/// </summary>
public IJpegComponent Component { get; }
/// <summary>
/// Gets the temporary working buffer of color values.
/// </summary>
public Buffer2D<float> ColorBuffer { get; }
/// <summary>
/// Gets <see cref="IJpegComponent.SizeInBlocks"/>
/// </summary>
public Size SizeInBlocks => this.Component.SizeInBlocks;
/// <summary>
/// Gets the maximal number of block rows being processed in one step.
/// </summary>
public int BlockRowsPerStep { get; }
/// <inheritdoc />
public void Dispose() => this.ColorBuffer.Dispose();
/// <summary>
/// Invoke <see cref="JpegBlockPostProcessor"/> for <see cref="BlockRowsPerStep"/> block rows, copy the result into <see cref="ColorBuffer"/>.
/// Convert raw spectral DCT data to color data and copy it to the color buffer <see cref="ColorBuffer"/>.
/// </summary>
public void CopyBlocksToColorBuffer(int step)
public void CopyBlocksToColorBuffer(int spectralStep)
{
Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;
var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component);
Buffer2D<Block8x8> spectralBuffer = this.component.SpectralBlocks;
float maximumValue = this.frame.MaxColorChannelValue;
int destAreaStride = this.ColorBuffer.Width;
int yBlockStart = step * this.BlockRowsPerStep;
int yBlockStart = spectralStep * this.blockRowsPerStep;
for (int y = 0; y < this.BlockRowsPerStep; y++)
{
int yBlock = yBlockStart + y;
Size subSamplingDivisors = this.component.SubSamplingDivisors;
if (yBlock >= spectralBuffer.Height)
{
break;
}
Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.component.QuantizationTableIndex];
Block8x8F workspaceBlock = default;
for (int y = 0; y < this.blockRowsPerStep; y++)
{
int yBuffer = y * this.blockAreaSize.Height;
Span<float> colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer);
Span<Block8x8> blockRow = spectralBuffer.GetRowSpan(yBlock);
Span<Block8x8> blockRow = spectralBuffer.GetRowSpan(yBlockStart + y);
// see: https://github.com/SixLabors/ImageSharp/issues/824
int widthInBlocks = Math.Min(spectralBuffer.Width, this.SizeInBlocks.Width);
for (int xBlock = 0; xBlock < widthInBlocks; xBlock++)
for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
{
ref Block8x8 block = ref blockRow[xBlock];
int xBuffer = xBlock * this.blockAreaSize.Width;
ref float destAreaOrigin = ref colorBufferRow[xBuffer];
blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue);
// Integer to float
workspaceBlock.LoadFrom(ref blockRow[xBlock]);
// Dequantize
workspaceBlock.MultiplyInPlace(ref dequantTable);
// Convert from spectral to color
FastFloatingPointDCT.TransformIDCT(ref workspaceBlock);
// To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations.
// To be "more accurate", we need to emulate this by rounding!
workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue);
// Write to color buffer acording to sampling factors
int xColorBufferStart = xBlock * this.blockAreaSize.Width;
workspaceBlock.ScaledCopyTo(
ref colorBufferRow[xColorBufferStart],
destAreaStride,
subSamplingDivisors.Width,
subSamplingDivisors.Height);
}
}
}
public void ClearSpectralBuffers()
{
Buffer2D<Block8x8> spectralBlocks = this.Component.SpectralBlocks;
Buffer2D<Block8x8> spectralBlocks = this.component.SpectralBlocks;
for (int i = 0; i < spectralBlocks.Height; i++)
{
spectralBlocks.GetRowSpan(i).Clear();
}
}
public void CopyBlocksToColorBuffer()
{
this.CopyBlocksToColorBuffer(this.currentComponentRowInBlocks);
this.currentComponentRowInBlocks += this.BlockRowsPerStep;
}
public Span<float> GetColorBufferRowSpan(int row) =>
this.ColorBuffer.GetRowSpan(row);
}
}

13
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs

@ -6,11 +6,8 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Converter used to convert jpeg spectral data.
/// Converter used to convert jpeg spectral data to color pixels.
/// </summary>
/// <remarks>
/// This is tightly coupled with <see cref="HuffmanScanDecoder"/> and <see cref="JpegDecoderCore"/>.
/// </remarks>
internal abstract class SpectralConverter
{
/// <summary>
@ -30,12 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData);
/// <summary>
/// Called once per spectral stride for each component in <see cref="HuffmanScanDecoder"/>.
/// This is called only for baseline interleaved jpegs.
/// Converts single spectral jpeg stride to color stride in baseline
/// decoding mode.
/// </summary>
/// <remarks>
/// Called once per decoded spectral stride in <see cref="HuffmanScanDecoder"/>
/// only for baseline interleaved jpeg images.
/// Spectral 'stride' doesn't particularly mean 'single stride'.
/// Actual stride height depends on the subsampling factor of the given component.
/// Actual stride height depends on the subsampling factor of the given image.
/// </remarks>
public abstract void ConvertStrideBaseline();

100
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

@ -4,7 +4,6 @@
using System;
using System.Buffers;
using System.Linq;
using System.Numerics;
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Memory;
@ -12,35 +11,78 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <inheritdoc/>
/// <remarks>
/// Color decoding scheme:
/// <list type = "bullet|number|table" >
/// <listheader>
/// <item>1. Decode spectral data to Jpeg color space</item>
/// <item>2. Convert from Jpeg color space to RGB</item>
/// <item>3. Convert from RGB to target pixel space</item>
/// </listheader>
/// </list>
/// </remarks>
internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// <see cref="Configuration"/> instance associated with current
/// decoding routine.
/// </summary>
private readonly Configuration configuration;
private readonly CancellationToken cancellationToken;
/// <summary>
/// Jpeg component converters from decompressed spectral to color data.
/// </summary>
private JpegComponentPostProcessor[] componentProcessors;
/// <summary>
/// Color converter from jpeg color space to target pixel color space.
/// </summary>
private JpegColorConverter colorConverter;
// private IMemoryOwner<Vector4> rgbaBuffer;
/// <summary>
/// Intermediate buffer of RGB components used in color conversion.
/// </summary>
private IMemoryOwner<byte> rgbBuffer;
/// <summary>
/// Proxy buffer used in packing from RGB to target TPixel pixels.
/// </summary>
private IMemoryOwner<TPixel> paddedProxyPixelRow;
/// <summary>
/// Resulting 2D pixel buffer.
/// </summary>
private Buffer2D<TPixel> pixelBuffer;
/// <summary>
/// How many pixel rows are processed in one 'stride'.
/// </summary>
private int pixelRowsPerStep;
/// <summary>
/// How many pixel rows were processed.
/// </summary>
private int pixelRowCounter;
public SpectralConverter(Configuration configuration, CancellationToken cancellationToken)
{
/// <summary>
/// Initializes a new instance of the <see cref="SpectralConverter{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
public SpectralConverter(Configuration configuration) =>
this.configuration = configuration;
this.cancellationToken = cancellationToken;
}
public Buffer2D<TPixel> GetPixelBuffer()
/// <summary>
/// Gets converted pixel buffer.
/// </summary>
/// <remarks>
/// For non-baseline interleaved jpeg this method does a 'lazy' spectral
/// conversion from spectral to color.
/// </remarks>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Pixel buffer.</returns>
public Buffer2D<TPixel> GetPixelBuffer(CancellationToken cancellationToken)
{
if (!this.Converted)
{
@ -48,7 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int step = 0; step < steps; step++)
{
this.cancellationToken.ThrowIfCancellationRequested();
cancellationToken.ThrowIfCancellationRequested();
this.ConvertStride(step);
}
}
@ -102,30 +144,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
/// <inheritdoc/>
public void Dispose()
{
if (this.componentProcessors != null)
{
foreach (JpegComponentPostProcessor cpp in this.componentProcessors)
{
cpp.Dispose();
}
}
this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose();
}
/// <summary>
/// Converts single spectral jpeg stride to color stride.
/// </summary>
/// <param name="spectralStep">Spectral stride index.</param>
private void ConvertStride(int spectralStep)
{
int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep);
var buffers = new Buffer2D<float>[this.componentProcessors.Length];
for (int i = 0; i < this.componentProcessors.Length; i++)
{
this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep);
buffers[i] = this.componentProcessors[i].ColorBuffer;
}
int width = this.pixelBuffer.Width;
@ -134,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
int y = yy - this.pixelRowCounter;
var values = new JpegColorConverter.ComponentValues(buffers, y);
var values = new JpegColorConverter.ComponentValues(this.componentProcessors, y);
this.colorConverter.ConvertToRgbInplace(values);
values = values.Slice(0, width); // slice away Jpeg padding
@ -164,5 +193,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.pixelRowCounter += this.pixelRowsPerStep;
}
/// <inheritdoc/>
public void Dispose()
{
if (this.componentProcessors != null)
{
foreach (JpegComponentPostProcessor cpp in this.componentProcessors)
{
cpp.Dispose();
}
}
this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose();
}
}
}

5
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -278,11 +278,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// ReSharper disable once InconsistentNaming
int prevDCY = 0;
var pixelConverter = LuminanceForwardConverter<TPixel>.Create();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
var pixelConverter = new LuminanceForwardConverter<TPixel>(frame);
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
@ -290,7 +291,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(frame, x, y, ref currentRows);
pixelConverter.Convert(x, y, ref currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,

49
src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -15,39 +16,63 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
internal ref struct LuminanceForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
/// </summary>
private const int PixelsPerSample = 8 * 8;
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;
/// <summary>
/// Temporal 8x8 block to hold TPixel data
/// Temporal 64-pixel span to hold unconverted TPixel data.
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted <see cref="L8"/> data.
/// </summary>
private readonly Span<L8> l8Span;
/// <summary>
/// Sampled pixel buffer size.
/// </summary>
private GenericBlock8x8<TPixel> pixelBlock;
private readonly Size samplingAreaSize;
/// <summary>
/// Temporal RGB block
/// <see cref="Configuration"/> for internal operations.
/// </summary>
private GenericBlock8x8<L8> l8Block;
private readonly Configuration config;
public static LuminanceForwardConverter<TPixel> Create()
public LuminanceForwardConverter(ImageFrame<TPixel> frame)
{
var result = default(LuminanceForwardConverter<TPixel>);
return result;
this.Y = default;
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.l8Span = new L8[PixelsPerSample].AsSpan();
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(8, 8);
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>)
/// </summary>
public void Convert(ImageFrame<TPixel> frame, int x, int y, ref RowOctet<TPixel> currentRows)
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows)
{
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows);
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
Span<L8> l8Span = this.l8Block.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToL8(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), l8Span);
PixelOperations<TPixel>.Instance.ToL8(this.config, this.pixelSpan, this.l8Span);
ref Block8x8F yBlock = ref this.Y;
ref L8 l8Start = ref l8Span[0];
ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span);
for (int i = 0; i < Block8x8F.Size; i++)
{

10
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs

@ -26,11 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer.
/// </summary>
private static readonly Size SampleSize = new Size(8, 8);
/// <summary>
/// The Red component.
/// </summary>
@ -81,6 +76,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.config = frame.GetConfiguration();
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(8, 8);
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24.
/// </summary>

10
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs

@ -25,11 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer
/// </summary>
private static readonly Size SampleSize = new Size(16, 8);
/// <summary>
/// The left Y component
/// </summary>
@ -102,6 +97,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(16, 8);
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows, int idx)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);

10
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs

@ -25,11 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer
/// </summary>
private static readonly Size SampleSize = new Size(8, 8);
/// <summary>
/// The Y component
/// </summary>
@ -96,6 +91,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
}
/// <summary>
/// Gets size of sampling area from given frame pixel buffer.
/// </summary>
private static Size SampleSize => new(8, 8);
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>

23
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal unsafe partial struct GenericBlock8x8<T>
{
#pragma warning disable 169
// It's not allowed use fix-sized buffers with generics, need to place all the fields manually:
private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7;
private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7;
private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7;
private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7;
private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7;
private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7;
private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7;
private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7;
#pragma warning restore 169
}
}

0
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt

122
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs

@ -1,122 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct GenericBlock8x8<T>
where T : unmanaged
{
public const int Size = 64;
/// <summary>
/// FOR TESTING ONLY!
/// Gets or sets a <see cref="Rgb24"/> value at the given index
/// </summary>
/// <param name="idx">The index</param>
/// <returns>The value</returns>
public T this[int idx]
{
get
{
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this);
return Unsafe.Add(ref selfRef, idx);
}
set
{
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this);
Unsafe.Add(ref selfRef, idx) = value;
}
}
/// <summary>
/// FOR TESTING ONLY!
/// Gets or sets a value in a row+column of the 8x8 block
/// </summary>
/// <param name="x">The x position index in the row</param>
/// <param name="y">The column index</param>
/// <returns>The value</returns>
public T this[int x, int y]
{
get => this[(y * 8) + x];
set => this[(y * 8) + x] = value;
}
/// <summary>
/// Load a 8x8 region of an image into the block.
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
/// </summary>
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY, ref RowOctet<T> currentRows)
{
int width = Math.Min(8, source.Width - sourceX);
int height = Math.Min(8, source.Height - sourceY);
if (width <= 0 || height <= 0)
{
return;
}
uint byteWidth = (uint)width * (uint)Unsafe.SizeOf<T>();
int remainderXCount = 8 - width;
ref byte blockStart = ref Unsafe.As<GenericBlock8x8<T>, byte>(ref this);
int blockRowSizeInBytes = 8 * Unsafe.SizeOf<T>();
for (int y = 0; y < height; y++)
{
Span<T> row = currentRows[y];
ref byte s = ref Unsafe.As<T, byte>(ref row[sourceX]);
ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes);
Unsafe.CopyBlock(ref d, ref s, byteWidth);
ref T last = ref Unsafe.Add(ref Unsafe.As<byte, T>(ref d), width - 1);
for (int x = 1; x <= remainderXCount; x++)
{
Unsafe.Add(ref last, x) = last;
}
}
int remainderYCount = 8 - height;
if (remainderYCount == 0)
{
return;
}
ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes);
for (int y = 1; y <= remainderYCount; y++)
{
ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y);
Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes);
}
}
/// <summary>
/// Only for on-stack instances!
/// </summary>
public Span<T> AsSpanUnsafe()
{
#if SUPPORTS_CREATESPAN
Span<GenericBlock8x8<T>> s = MemoryMarshal.CreateSpan(ref this, 1);
return MemoryMarshal.Cast<GenericBlock8x8<T>, T>(s);
#else
return new Span<T>(Unsafe.AsPointer(ref this), Size);
#endif
}
}
}

2
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
=> await this.DecodeAsync<Rgb24>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>

7
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration, cancellationToken);
using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
@ -185,7 +185,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.InitIptcProfile();
this.InitDerivedMetadataProperties();
return new Image<TPixel>(this.Configuration, spectralConverter.GetPixelBuffer(), this.Metadata);
return new Image<TPixel>(
this.Configuration,
spectralConverter.GetPixelBuffer(cancellationToken),
this.Metadata);
}
/// <inheritdoc/>

6
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

@ -55,17 +55,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
using var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder());
// TODO: Should we pass through the CancellationToken from the tiff decoder?
// If the PhotometricInterpretation is YCbCr we explicitly assume the JPEG data is in RGB color space.
// There seems no other way to determine that the JPEG data is RGB colorspace (no APP14 marker, componentId's are not RGB).
using SpectralConverter<Rgb24> spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ?
new RgbJpegSpectralConverter<Rgb24>(this.configuration, CancellationToken.None) : new SpectralConverter<Rgb24>(this.configuration, CancellationToken.None);
new RgbJpegSpectralConverter<Rgb24>(this.configuration) : new SpectralConverter<Rgb24>(this.configuration);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None);
jpegDecoder.LoadTables(this.jpegTables, scanDecoder);
scanDecoder.ResetInterval = 0;
jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None);
CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer());
// TODO: Should we pass through the CancellationToken from the tiff decoder?
CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer(CancellationToken.None));
}
else
{

5
src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs

@ -21,9 +21,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// This Spectral converter will always convert the pixel data to RGB color.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public RgbJpegSpectralConverter(Configuration configuration, CancellationToken cancellationToken)
: base(configuration, cancellationToken)
public RgbJpegSpectralConverter(Configuration configuration)
: base(configuration)
{
}

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

@ -29,6 +29,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private static readonly Vector128<sbyte> SixtyThree = Vector128.Create((short)63).AsSByte();
private static readonly Vector128<sbyte> SixtyFour = Vector128.Create((byte)64).AsSByte();
private static readonly Vector128<short> K1 = Vector128.Create((short)20091);
private static readonly Vector128<short> K2 = Vector128.Create((short)-30068);
#endif
// Note: method name in libwebp reference implementation is called VP8SSE16x16.
@ -832,57 +837,325 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
#endif
// Transforms (Paragraph 14.4).
// Does two transforms.
public static void TransformTwo(Span<short> src, Span<byte> dst, Span<int> scratch)
{
TransformOne(src, dst, scratch);
TransformOne(src.Slice(16), dst.Slice(4), scratch);
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
// This implementation makes use of 16-bit fixed point versions of two
// multiply constants:
// K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16
// K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16
//
// To be able to use signed 16-bit integers, we use the following trick to
// have constants within range:
// - Associated constants are obtained by subtracting the 16-bit fixed point
// version of one:
// k = K - (1 << 16) => K = k + (1 << 16)
// K1 = 85267 => k1 = 20091
// K2 = 35468 => k2 = -30068
// - The multiplication of a variable by a constant become the sum of the
// variable and the multiplication of that variable by the associated
// constant:
// (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x
// Load and concatenate the transform coefficients (we'll do two transforms
// in parallel).
ref short srcRef = ref MemoryMarshal.GetReference(src);
var in0 = Vector128.Create(Unsafe.As<short, long>(ref srcRef), 0);
var in1 = Vector128.Create(Unsafe.As<short, long>(ref Unsafe.Add(ref srcRef, 4)), 0);
var in2 = Vector128.Create(Unsafe.As<short, long>(ref Unsafe.Add(ref srcRef, 8)), 0);
var in3 = Vector128.Create(Unsafe.As<short, long>(ref Unsafe.Add(ref srcRef, 12)), 0);
// a00 a10 a20 a30 x x x x
// a01 a11 a21 a31 x x x x
// a02 a12 a22 a32 x x x x
// a03 a13 a23 a33 x x x x
var inb0 = Vector128.Create(Unsafe.As<short, long>(ref Unsafe.Add(ref srcRef, 16)), 0);
var inb1 = Vector128.Create(Unsafe.As<short, long>(ref Unsafe.Add(ref srcRef, 20)), 0);
var inb2 = Vector128.Create(Unsafe.As<short, long>(ref Unsafe.Add(ref srcRef, 24)), 0);
var inb3 = Vector128.Create(Unsafe.As<short, long>(ref Unsafe.Add(ref srcRef, 28)), 0);
in0 = Sse2.UnpackLow(in0, inb0);
in1 = Sse2.UnpackLow(in1, inb1);
in2 = Sse2.UnpackLow(in2, inb2);
in3 = Sse2.UnpackLow(in3, inb3);
// 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
// Vertical pass and subsequent transpose.
// First pass, c and d calculations are longer because of the "trick" multiplications.
Vector128<short> a = Sse2.Add(in0.AsInt16(), in2.AsInt16());
Vector128<short> b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16());
// c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3
Vector128<short> c1 = Sse2.MultiplyHigh(in1.AsInt16(), K2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1);
Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16());
Vector128<short> c4 = Sse2.Subtract(c1, c2);
Vector128<short> c = Sse2.Add(c3.AsInt16(), c4);
// d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3
Vector128<short> d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2);
Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16());
Vector128<short> d4 = Sse2.Add(d1, d2);
Vector128<short> d = Sse2.Add(d3, d4);
// Second pass.
Vector128<short> tmp0 = Sse2.Add(a.AsInt16(), d);
Vector128<short> tmp1 = Sse2.Add(b.AsInt16(), c);
Vector128<short> tmp2 = Sse2.Subtract(b.AsInt16(), c);
Vector128<short> tmp3 = Sse2.Subtract(a.AsInt16(), d);
// Transpose the two 4x4.
Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128<long> t0, out Vector128<long> t1, out Vector128<long> t2, out Vector128<long> t3);
// Horizontal pass and subsequent transpose.
// First pass, c and d calculations are longer because of the "trick" multiplications.
Vector128<short> dc = Sse2.Add(t0.AsInt16(), Four.AsInt16());
a = Sse2.Add(dc, t2.AsInt16());
b = Sse2.Subtract(dc, t2.AsInt16());
// c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3
c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2);
c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1);
c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16());
c4 = Sse2.Subtract(c1, c2);
c = Sse2.Add(c3, c4);
// d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3
d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1);
d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2);
d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16());
d4 = Sse2.Add(d1, d2);
d = Sse2.Add(d3, d4);
// Second pass.
tmp0 = Sse2.Add(a, d);
tmp1 = Sse2.Add(b, c);
tmp2 = Sse2.Subtract(b, c);
tmp3 = Sse2.Subtract(a, d);
Vector128<short> shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3);
Vector128<short> shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3);
Vector128<short> shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3);
Vector128<short> shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3);
// Transpose the two 4x4.
Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3);
// Add inverse transform to 'dst' and store.
// Load the reference(s).
// Load eight bytes/pixels per line.
ref byte dstRef = ref MemoryMarshal.GetReference(dst);
Vector128<byte> dst0 = Vector128.Create(Unsafe.As<byte, long>(ref dstRef), 0).AsByte();
Vector128<byte> dst1 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref dstRef, WebpConstants.Bps)), 0).AsByte();
Vector128<byte> dst2 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2)), 0).AsByte();
Vector128<byte> dst3 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3)), 0).AsByte();
// Convert to 16b.
dst0 = Sse2.UnpackLow(dst0, Vector128<byte>.Zero);
dst1 = Sse2.UnpackLow(dst1, Vector128<byte>.Zero);
dst2 = Sse2.UnpackLow(dst2, Vector128<byte>.Zero);
dst3 = Sse2.UnpackLow(dst3, Vector128<byte>.Zero);
// Add the inverse transform(s).
dst0 = Sse2.Add(dst0.AsInt16(), t0.AsInt16()).AsByte();
dst1 = Sse2.Add(dst1.AsInt16(), t1.AsInt16()).AsByte();
dst2 = Sse2.Add(dst2.AsInt16(), t2.AsInt16()).AsByte();
dst3 = Sse2.Add(dst3.AsInt16(), t3.AsInt16()).AsByte();
// Unsigned saturate to 8b.
dst0 = Sse2.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16());
dst1 = Sse2.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16());
dst2 = Sse2.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16());
dst3 = Sse2.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16());
// Store the results.
// Store eight bytes/pixels per line.
ref byte outputRef = ref MemoryMarshal.GetReference(dst);
Unsafe.As<byte, Vector64<byte>>(ref outputRef) = dst0.GetLower();
Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = dst1.GetLower();
Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = dst2.GetLower();
Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = dst3.GetLower();
}
else
#endif
{
TransformOne(src, dst, scratch);
TransformOne(src.Slice(16), dst.Slice(4), scratch);
}
}
public static void TransformOne(Span<short> src, Span<byte> dst, Span<int> scratch)
{
Span<int> tmp = scratch.Slice(0, 16);
int tmpOffset = 0;
for (int srcOffset = 0; srcOffset < 4; srcOffset++)
{
// vertical pass
int srcOffsetPlus4 = srcOffset + 4;
int srcOffsetPlus8 = srcOffset + 8;
int srcOffsetPlus12 = srcOffset + 12;
int a = src[srcOffset] + src[srcOffsetPlus8];
int b = src[srcOffset] - src[srcOffsetPlus8];
int c = Mul2(src[srcOffsetPlus4]) - Mul1(src[srcOffsetPlus12]);
int d = Mul1(src[srcOffsetPlus4]) + Mul2(src[srcOffsetPlus12]);
tmp[tmpOffset++] = a + d;
tmp[tmpOffset++] = b + c;
tmp[tmpOffset++] = b - c;
tmp[tmpOffset++] = a - d;
}
// Each pass is expanding the dynamic range by ~3.85 (upper bound).
// The exact value is (2. + (20091 + 35468) / 65536).
// After the second pass, maximum interval is [-3794, 3794], assuming
// an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range.
// In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968].
tmpOffset = 0;
int dstOffset = 0;
for (int i = 0; i < 4; i++)
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
{
// Load and concatenate the transform coefficients.
ref short srcRef = ref MemoryMarshal.GetReference(src);
var in0 = Vector128.Create(Unsafe.As<short, long>(ref srcRef), 0);
var in1 = Vector128.Create(Unsafe.As<short, long>(ref Unsafe.Add(ref srcRef, 4)), 0);
var in2 = Vector128.Create(Unsafe.As<short, long>(ref Unsafe.Add(ref srcRef, 8)), 0);
var in3 = Vector128.Create(Unsafe.As<short, long>(ref Unsafe.Add(ref srcRef, 12)), 0);
// a00 a10 a20 a30 x x x x
// a01 a11 a21 a31 x x x x
// a02 a12 a22 a32 x x x x
// a03 a13 a23 a33 x x x x
// Vertical pass and subsequent transpose.
// First pass, c and d calculations are longer because of the "trick" multiplications.
Vector128<short> a = Sse2.Add(in0.AsInt16(), in2.AsInt16());
Vector128<short> b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16());
// c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3
Vector128<short> c1 = Sse2.MultiplyHigh(in1.AsInt16(), K2);
Vector128<short> c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1);
Vector128<short> c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16());
Vector128<short> c4 = Sse2.Subtract(c1, c2);
Vector128<short> c = Sse2.Add(c3.AsInt16(), c4);
// d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3
Vector128<short> d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1);
Vector128<short> d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2);
Vector128<short> d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16());
Vector128<short> d4 = Sse2.Add(d1, d2);
Vector128<short> d = Sse2.Add(d3, d4);
// Second pass.
Vector128<short> tmp0 = Sse2.Add(a.AsInt16(), d);
Vector128<short> tmp1 = Sse2.Add(b.AsInt16(), c);
Vector128<short> tmp2 = Sse2.Subtract(b.AsInt16(), c);
Vector128<short> tmp3 = Sse2.Subtract(a.AsInt16(), d);
// Transpose the two 4x4.
Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128<long> t0, out Vector128<long> t1, out Vector128<long> t2, out Vector128<long> t3);
// Horizontal pass and subsequent transpose.
// First pass, c and d calculations are longer because of the "trick" multiplications.
Vector128<short> dc = Sse2.Add(t0.AsInt16(), Four.AsInt16());
a = Sse2.Add(dc, t2.AsInt16());
b = Sse2.Subtract(dc, t2.AsInt16());
// c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3
c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2);
c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1);
c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16());
c4 = Sse2.Subtract(c1, c2);
c = Sse2.Add(c3, c4);
// d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3
d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1);
d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2);
d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16());
d4 = Sse2.Add(d1, d2);
d = Sse2.Add(d3, d4);
// Second pass.
tmp0 = Sse2.Add(a, d);
tmp1 = Sse2.Add(b, c);
tmp2 = Sse2.Subtract(b, c);
tmp3 = Sse2.Subtract(a, d);
Vector128<short> shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3);
Vector128<short> shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3);
Vector128<short> shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3);
Vector128<short> shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3);
// Transpose the two 4x4.
Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3);
// Add inverse transform to 'dst' and store.
// Load the reference(s).
// Load four bytes/pixels per line.
ref byte dstRef = ref MemoryMarshal.GetReference(dst);
Vector128<byte> dst0 = Sse2.ConvertScalarToVector128Int32(Unsafe.As<byte, int>(ref dstRef)).AsByte();
Vector128<byte> dst1 = Sse2.ConvertScalarToVector128Int32(Unsafe.As<byte, int>(ref Unsafe.Add(ref dstRef, WebpConstants.Bps))).AsByte();
Vector128<byte> dst2 = Sse2.ConvertScalarToVector128Int32(Unsafe.As<byte, int>(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2))).AsByte();
Vector128<byte> dst3 = Sse2.ConvertScalarToVector128Int32(Unsafe.As<byte, int>(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3))).AsByte();
// Convert to 16b.
dst0 = Sse2.UnpackLow(dst0, Vector128<byte>.Zero);
dst1 = Sse2.UnpackLow(dst1, Vector128<byte>.Zero);
dst2 = Sse2.UnpackLow(dst2, Vector128<byte>.Zero);
dst3 = Sse2.UnpackLow(dst3, Vector128<byte>.Zero);
// Add the inverse transform(s).
dst0 = Sse2.Add(dst0.AsInt16(), t0.AsInt16()).AsByte();
dst1 = Sse2.Add(dst1.AsInt16(), t1.AsInt16()).AsByte();
dst2 = Sse2.Add(dst2.AsInt16(), t2.AsInt16()).AsByte();
dst3 = Sse2.Add(dst3.AsInt16(), t3.AsInt16()).AsByte();
// Unsigned saturate to 8b.
dst0 = Sse2.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16());
dst1 = Sse2.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16());
dst2 = Sse2.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16());
dst3 = Sse2.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16());
// Store the results.
// Store four bytes/pixels per line.
ref byte outputRef = ref MemoryMarshal.GetReference(dst);
int output0 = Sse2.ConvertToInt32(dst0.AsInt32());
int output1 = Sse2.ConvertToInt32(dst1.AsInt32());
int output2 = Sse2.ConvertToInt32(dst2.AsInt32());
int output3 = Sse2.ConvertToInt32(dst3.AsInt32());
Unsafe.As<byte, int>(ref outputRef) = output0;
Unsafe.As<byte, int>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = output1;
Unsafe.As<byte, int>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = output2;
Unsafe.As<byte, int>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = output3;
}
else
#endif
{
// horizontal pass
int tmpOffsetPlus4 = tmpOffset + 4;
int tmpOffsetPlus8 = tmpOffset + 8;
int tmpOffsetPlus12 = tmpOffset + 12;
int dc = tmp[tmpOffset] + 4;
int a = dc + tmp[tmpOffsetPlus8];
int b = dc - tmp[tmpOffsetPlus8];
int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]);
int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]);
Store(dst.Slice(dstOffset), 0, 0, a + d);
Store(dst.Slice(dstOffset), 1, 0, b + c);
Store(dst.Slice(dstOffset), 2, 0, b - c);
Store(dst.Slice(dstOffset), 3, 0, a - d);
tmpOffset++;
dstOffset += WebpConstants.Bps;
Span<int> tmp = scratch.Slice(0, 16);
int tmpOffset = 0;
for (int srcOffset = 0; srcOffset < 4; srcOffset++)
{
// vertical pass
int srcOffsetPlus4 = srcOffset + 4;
int srcOffsetPlus8 = srcOffset + 8;
int srcOffsetPlus12 = srcOffset + 12;
int a = src[srcOffset] + src[srcOffsetPlus8];
int b = src[srcOffset] - src[srcOffsetPlus8];
int c = Mul2(src[srcOffsetPlus4]) - Mul1(src[srcOffsetPlus12]);
int d = Mul1(src[srcOffsetPlus4]) + Mul2(src[srcOffsetPlus12]);
tmp[tmpOffset++] = a + d;
tmp[tmpOffset++] = b + c;
tmp[tmpOffset++] = b - c;
tmp[tmpOffset++] = a - d;
}
// Each pass is expanding the dynamic range by ~3.85 (upper bound).
// The exact value is (2. + (20091 + 35468) / 65536).
// After the second pass, maximum interval is [-3794, 3794], assuming
// an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range.
// In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968].
tmpOffset = 0;
int dstOffset = 0;
for (int i = 0; i < 4; i++)
{
// horizontal pass
int tmpOffsetPlus4 = tmpOffset + 4;
int tmpOffsetPlus8 = tmpOffset + 8;
int tmpOffsetPlus12 = tmpOffset + 12;
int dc = tmp[tmpOffset] + 4;
int a = dc + tmp[tmpOffsetPlus8];
int b = dc - tmp[tmpOffsetPlus8];
int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]);
int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]);
Store(dst.Slice(dstOffset), 0, 0, a + d);
Store(dst.Slice(dstOffset), 1, 0, b + c);
Store(dst.Slice(dstOffset), 2, 0, b - c);
Store(dst.Slice(dstOffset), 3, 0, a - d);
tmpOffset++;
dstOffset += WebpConstants.Bps;
}
}
}

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

@ -25,6 +25,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector128<short> MaxCoeff2047 = Vector128.Create((short)MaxLevel);
private static readonly Vector256<short> MaxCoeff2047Vec256 = Vector256.Create((short)MaxLevel);
private static readonly Vector256<byte> Cst256 = Vector256.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13, 2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15);
private static readonly Vector256<byte> Cst78 = Vector256.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255);
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);
@ -329,7 +335,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
LossyUtils.TransformWht(dcTmp, tmp, scratch);
for (n = 0; n < 16; n += 2)
{
Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), scratch);
Vp8Encoding.ITransformTwo(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), scratch);
}
return nz;
@ -375,7 +381,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
for (n = 0; n < 8; n += 2)
{
Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), scratch);
Vp8Encoding.ITransformTwo(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), scratch);
}
return nz << 16;
@ -531,7 +537,70 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public static int QuantizeBlock(Span<short> input, Span<short> output, ref Vp8Matrix mtx)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse41.IsSupported)
if (Avx2.IsSupported)
{
// Load all inputs.
Vector256<short> input0 = Unsafe.As<short, Vector256<short>>(ref MemoryMarshal.GetReference(input));
Vector256<ushort> iq0 = Unsafe.As<ushort, Vector256<ushort>>(ref mtx.IQ[0]);
Vector256<ushort> q0 = Unsafe.As<ushort, Vector256<ushort>>(ref mtx.Q[0]);
// coeff = abs(in)
Vector256<ushort> coeff0 = Avx2.Abs(input0);
// coeff = abs(in) + sharpen
Vector256<short> sharpen0 = Unsafe.As<short, Vector256<short>>(ref mtx.Sharpen[0]);
Avx2.Add(coeff0.AsInt16(), sharpen0);
// out = (coeff * iQ + B) >> QFIX
// doing calculations with 32b precision (QFIX=17)
// out = (coeff * iQ)
Vector256<ushort> coeffiQ0H = Avx2.MultiplyHigh(coeff0, iq0);
Vector256<ushort> coeffiQ0L = Avx2.MultiplyLow(coeff0, iq0);
Vector256<ushort> out00 = Avx2.UnpackLow(coeffiQ0L, coeffiQ0H);
Vector256<ushort> out08 = Avx2.UnpackHigh(coeffiQ0L, coeffiQ0H);
// out = (coeff * iQ + B)
Vector256<uint> bias00 = Unsafe.As<uint, Vector256<uint>>(ref mtx.Bias[0]);
Vector256<uint> bias08 = Unsafe.As<uint, Vector256<uint>>(ref mtx.Bias[8]);
out00 = Avx2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16();
out08 = Avx2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16();
// out = QUANTDIV(coeff, iQ, B, QFIX)
out00 = Avx2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16();
out08 = Avx2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16();
// Pack result as 16b.
Vector256<short> out0 = Avx2.PackSignedSaturate(out00.AsInt32(), out08.AsInt32());
// if (coeff > 2047) coeff = 2047
out0 = Avx2.Min(out0, MaxCoeff2047Vec256);
// Put the sign back.
out0 = Avx2.Sign(out0, input0);
// in = out * Q
input0 = Avx2.MultiplyLow(out0, q0.AsInt16());
ref short inputRef = ref MemoryMarshal.GetReference(input);
Unsafe.As<short, Vector256<short>>(ref inputRef) = input0;
// zigzag the output before storing it.
Vector256<byte> tmp256 = Avx2.Shuffle(out0.AsByte(), Cst256);
Vector256<byte> tmp78 = Avx2.Shuffle(out0.AsByte(), Cst78);
// Reverse the order of the 16-byte lanes.
Vector256<byte> tmp87 = Avx2.Permute2x128(tmp78, tmp78, 1);
Vector256<short> outZ = Avx2.Or(tmp256, tmp87).AsInt16();
ref short outputRef = ref MemoryMarshal.GetReference(output);
Unsafe.As<short, Vector256<short>>(ref outputRef) = outZ;
Vector256<sbyte> packedOutput = Avx2.PackSignedSaturate(outZ, outZ);
// Detect if all 'out' values are zeros or not.
Vector256<sbyte> cmpeq = Avx2.CompareEqual(packedOutput, Vector256<sbyte>.Zero);
return Avx2.MoveMask(cmpeq) != -1 ? 1 : 0;
}
else if (Sse41.IsSupported)
{
// Load all inputs.
Vector128<short> input0 = Unsafe.As<short, Vector128<short>>(ref MemoryMarshal.GetReference(input));
@ -579,7 +648,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
out08 = Sse2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16();
out12 = Sse2.ShiftRightArithmetic(out12.AsInt32(), WebpConstants.QFix).AsUInt16();
// pack result as 16b
// Pack result as 16b.
Vector128<short> out0 = Sse2.PackSignedSaturate(out00.AsInt32(), out04.AsInt32());
Vector128<short> out8 = Sse2.PackSignedSaturate(out08.AsInt32(), out12.AsInt32());
@ -587,7 +656,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
out0 = Sse2.Min(out0, MaxCoeff2047);
out8 = Sse2.Min(out8, MaxCoeff2047);
// put sign back
// Put the sign back.
out0 = Ssse3.Sign(out0, input0);
out8 = Ssse3.Sign(out8, input8);

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

@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
// Transforms (Paragraph 14.4)
// Does two inverse transforms.
public static void ITransform(Span<byte> reference, Span<short> input, Span<byte> dst, Span<int> scratch)
public static void ITransformTwo(Span<byte> reference, Span<short> input, Span<byte> dst, Span<int> scratch)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse2.IsSupported)
@ -208,10 +208,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded);
ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded);
// Unsigned saturate to 8b.
ref byte outputRef = ref MemoryMarshal.GetReference(dst);
// Store eight bytes/pixels per line.
ref byte outputRef = ref MemoryMarshal.GetReference(dst);
Unsafe.As<byte, Vector64<byte>>(ref outputRef) = ref0.GetLower();
Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = ref1.GetLower();
Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = ref2.GetLower();

9
src/ImageSharp/ImageSharp.csproj

@ -70,11 +70,6 @@
<AutoGen>True</AutoGen>
<DependentUpon>Block8x8F.Generated.tt</DependentUpon>
</Compile>
<Compile Update="Formats\Jpeg\Components\GenericBlock8x8.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>GenericBlock8x8.Generated.tt</DependentUpon>
</Compile>
<Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@ -167,10 +162,6 @@
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Block8x8F.Generated.cs</LastGenOutput>
</None>
<None Update="Formats\Jpeg\Components\GenericBlock8x8.Generated.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>GenericBlock8x8.Generated.cs</LastGenOutput>
</None>
<None Update="Formats\Jpeg\Components\Block8x8F.Generated.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Block8x8F.Generated.cs</LastGenOutput>

6
tests/ImageSharp.Tests.ProfilingSandbox/Program.cs

@ -47,12 +47,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
benchmarks.EncodeJpeg_SingleMidSize();
}
private static void RunJpegColorProfilingTests()
{
new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(false);
new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(true);
}
private static void RunResizeProfilingTest()
{
var test = new ResizeProfilingBenchmarks(new ConsoleOutput());

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -90,17 +91,30 @@ namespace SixLabors.ImageSharp.Tests
}
[Fact]
public void GenericPixel()
public void Vector4Constructor()
{
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));
// Act:
Color color = new(Vector4.One);
// Assert:
Assert.Equal(new RgbaVector(1, 1, 1, 1), color.ToPixel<RgbaVector>());
Assert.Equal(new Rgba64(65535, 65535, 65535, 65535), color.ToPixel<Rgba64>());
Assert.Equal(new Rgba32(255, 255, 255, 255), color.ToPixel<Rgba32>());
Assert.Equal(new L8(255), color.ToPixel<L8>());
}
[Fact]
public void GenericPixelRoundTrip()
{
AssertGenericPixelRoundTrip(new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue));
AssertGenericPixelRoundTrip(new Rgba64(1, 2, ushort.MaxValue, ushort.MaxValue - 1));
AssertGenericPixelRoundTrip(new Rgb48(1, 2, ushort.MaxValue - 1));
AssertGenericPixelRoundTrip(new La32(1, ushort.MaxValue - 1));
AssertGenericPixelRoundTrip(new L16(ushort.MaxValue - 1));
AssertGenericPixelRoundTrip(new Rgba32(1, 2, 255, 254));
}
private static void AssertGenericPixel<TPixel>(TPixel source)
private static void AssertGenericPixelRoundTrip<TPixel>(TPixel source)
where TPixel : unmanaged, IPixel<TPixel>
{
// Act:
@ -110,6 +124,27 @@ namespace SixLabors.ImageSharp.Tests
TPixel actual = color.ToPixel<TPixel>();
Assert.Equal(source, actual);
}
[Fact]
public void GenericPixelDifferentPrecision()
{
AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba64(65535, 65535, 65535, 65535));
AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba32(255, 255, 255, 255));
AssertGenericPixelDifferentPrecision(new Rgba64(65535, 65535, 65535, 65535), new Rgba32(255, 255, 255, 255));
AssertGenericPixelDifferentPrecision(new Rgba32(255, 255, 255, 255), new L8(255));
}
private static void AssertGenericPixelDifferentPrecision<TPixel, TPixel2>(TPixel source, TPixel2 expected)
where TPixel : unmanaged, IPixel<TPixel>
where TPixel2 : unmanaged, IPixel<TPixel2>
{
// Act:
var color = Color.FromPixel(source);
// Assert:
TPixel2 actual = color.ToPixel<TPixel2>();
Assert.Equal(expected, actual);
}
}
}
}

50
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs

@ -114,56 +114,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// PrintLinearData((Span<float>)mirror);
}
[Fact]
public unsafe void Load_Store_FloatArray_Ptr()
{
float[] data = new float[Block8x8F.Size];
float[] mirror = new float[Block8x8F.Size];
for (int i = 0; i < Block8x8F.Size; i++)
{
data[i] = i;
}
this.Measure(
Times,
() =>
{
var b = default(Block8x8F);
Block8x8F.LoadFrom(&b, data);
Block8x8F.ScaledCopyTo(&b, mirror);
});
Assert.Equal(data, mirror);
// PrintLinearData((Span<float>)mirror);
}
[Fact]
public void Load_Store_IntArray()
{
int[] data = new int[Block8x8F.Size];
int[] mirror = new int[Block8x8F.Size];
for (int i = 0; i < Block8x8F.Size; i++)
{
data[i] = i;
}
this.Measure(
Times,
() =>
{
var v = default(Block8x8F);
v.LoadFrom(data);
v.ScaledCopyTo(mirror);
});
Assert.Equal(data, mirror);
// PrintLinearData((Span<int>)mirror);
}
[Fact]
public void TransposeInplace()
{

14
tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs

@ -70,20 +70,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(data, result);
}
[Fact]
public void Equality_WhenTrue()
{
short[] data = Create8x8ShortData();
var block1 = Block8x8.Load(data);
var block2 = Block8x8.Load(data);
block1[0] = 42;
block2[0] = 42;
Assert.Equal(block1, block2);
Assert.Equal(block1.GetHashCode(), block2.GetHashCode());
}
[Fact]
public void Equality_WhenFalse()
{

129
tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs

@ -1,129 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
[Trait("Format", "Jpg")]
public class GenericBlock8x8Tests
{
public static Image<TPixel> CreateTestImage<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
var image = new Image<TPixel>(10, 10);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
var rgba = new Rgba32((byte)(i + 1), (byte)(j + 1), 200, 255);
var color = default(TPixel);
color.FromRgba32(rgba);
pixels[i, j] = color;
}
}
return image;
}
[Theory]
[WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)]
public void LoadAndStretchCorners_FromOrigo<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> s = provider.GetImage())
{
var d = default(GenericBlock8x8<TPixel>);
RowOctet<TPixel> rowOctet = default;
rowOctet.Update(s.GetRootFramePixelBuffer(), 0);
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, ref rowOctet);
TPixel a = s.Frames.RootFrame[0, 0];
TPixel b = d[0, 0];
Assert.Equal(s[0, 0], d[0, 0]);
Assert.Equal(s[1, 0], d[1, 0]);
Assert.Equal(s[7, 0], d[7, 0]);
Assert.Equal(s[0, 1], d[0, 1]);
Assert.Equal(s[1, 1], d[1, 1]);
Assert.Equal(s[7, 0], d[7, 0]);
Assert.Equal(s[0, 7], d[0, 7]);
Assert.Equal(s[7, 7], d[7, 7]);
}
}
[Theory]
[WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)]
public void LoadAndStretchCorners_WithOffset<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> s = provider.GetImage())
{
var d = default(GenericBlock8x8<TPixel>);
RowOctet<TPixel> rowOctet = default;
rowOctet.Update(s.GetRootFramePixelBuffer(), 7);
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, ref rowOctet);
Assert.Equal(s[6, 7], d[0, 0]);
Assert.Equal(s[6, 8], d[0, 1]);
Assert.Equal(s[7, 8], d[1, 1]);
Assert.Equal(s[6, 9], d[0, 2]);
Assert.Equal(s[6, 9], d[0, 3]);
Assert.Equal(s[6, 9], d[0, 7]);
Assert.Equal(s[7, 9], d[1, 2]);
Assert.Equal(s[7, 9], d[1, 3]);
Assert.Equal(s[7, 9], d[1, 7]);
Assert.Equal(s[9, 9], d[3, 2]);
Assert.Equal(s[9, 9], d[3, 3]);
Assert.Equal(s[9, 9], d[3, 7]);
Assert.Equal(s[9, 7], d[3, 0]);
Assert.Equal(s[9, 7], d[4, 0]);
Assert.Equal(s[9, 7], d[7, 0]);
Assert.Equal(s[9, 9], d[3, 2]);
Assert.Equal(s[9, 9], d[4, 2]);
Assert.Equal(s[9, 9], d[7, 2]);
Assert.Equal(s[9, 9], d[4, 3]);
Assert.Equal(s[9, 9], d[7, 7]);
}
}
[Fact]
public void Indexer()
{
var block = default(GenericBlock8x8<Rgb24>);
Span<Rgb24> span = block.AsSpanUnsafe();
Assert.Equal(64, span.Length);
for (int i = 0; i < 64; i++)
{
span[i] = new Rgb24((byte)i, (byte)(2 * i), (byte)(3 * i));
}
var expected00 = new Rgb24(0, 0, 0);
var expected07 = new Rgb24(7, 14, 21);
var expected11 = new Rgb24(9, 18, 27);
var expected77 = new Rgb24(63, 126, 189);
var expected67 = new Rgb24(62, 124, 186);
Assert.Equal(expected00, block[0, 0]);
Assert.Equal(expected07, block[7, 0]);
Assert.Equal(expected11, block[1, 1]);
Assert.Equal(expected67, block[6, 7]);
Assert.Equal(expected77, block[7, 7]);
}
}
}

26
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -340,32 +340,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
seed);
}
// Benchmark, for local execution only
// [Theory]
// [InlineData(false)]
// [InlineData(true)]
public void BenchmarkYCbCr(bool simd)
{
int count = 2053;
int times = 50000;
JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1);
var result = new Vector4[count];
JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrVector4(8) : new JpegColorConverter.FromYCbCrBasic(8);
// Warm up:
converter.ConvertToRgbInplace(values);
using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}"))
{
for (int i = 0; i < times; i++)
{
converter.ConvertToRgbInplace(values);
}
}
}
private static JpegColorConverter.ComponentValues CreateRandomValues(
int componentCount,
int inputBufferLength,

8
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -93,6 +93,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.IsType<Image<Rgb24>>(image);
}
[Fact]
public async Task DecodeAsync_NonGeneric_CreatesRgb24Image()
{
string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small);
using Image image = await Image.LoadAsync(file);
Assert.IsType<Image<Rgb24>>(image);
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)]
public void JpegDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)

2
tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs

@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source);
Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformFDCT_UpscaleBy8(ref source);
actual /= 8;
actual.MultiplyInPlace(0.125f);
this.CompareBlocks(expected, actual, 1f);
}

4
tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs

@ -51,11 +51,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source);
source += 128;
source.AddInPlace(128f);
Block8x8 temp = source.RoundAsInt16Block();
Block8x8 actual8 = ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8(ref temp);
Block8x8F actual = actual8.AsFloatBlock();
actual /= 8;
actual.MultiplyInPlace(0.125f);
this.CompareBlocks(expected, actual, 1f);
}

5
tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs

@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.IO;
@ -43,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
// Decoding
using var converter = new SpectralConverter<TPixel>(Configuration.Default, cancellationToken: default);
using var converter = new SpectralConverter<TPixel>(Configuration.Default);
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default);
decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default);
@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName;
// Comparison
using (Image<TPixel> image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(), new ImageMetadata()))
using (Image<TPixel> image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata()))
using (Image<TPixel> referenceImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false))
{
ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image);

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

@ -11,8 +11,74 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
[Trait("Format", "Webp")]
public class LossyUtilsTests
{
private static void RunTransformTwoTest()
{
// arrange
short[] src = { 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 23, 0, 0, -23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
byte[] dst =
{
103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171,
171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103,
0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0,
103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169,
171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103,
103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171,
0, 0, 0, 0, 0, 0, 0, 0
};
byte[] expected =
{
105, 105, 105, 105, 105, 103, 100, 98, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171,
171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 108, 105, 102, 100, 0, 0, 0, 0, 169,
169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105,
105, 105, 111, 109, 106, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171,
171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 113, 111, 108, 106, 0, 0, 0, 0, 169, 169,
169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 0, 0, 0, 0
};
int[] scratch = new int[16];
// act
LossyUtils.TransformTwo(src, dst, scratch);
// assert
Assert.True(expected.SequenceEqual(dst));
}
private static void RunTransformOneTest()
{
// arrange
short[] src = { -176, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
byte[] dst =
{
128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128,
128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128,
0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0,
0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129,
128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128,
128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128,
0, 0, 0, 0, 0, 0, 0, 129
};
byte[] expected =
{
111, 111, 111, 111, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128,
128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 108, 108, 108, 108, 128, 128, 128, 128,
0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0,
0, 0, 0, 129, 104, 104, 104, 104, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129,
128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 101, 101, 101, 101,
128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128,
0, 0, 0, 0, 0, 0, 0, 129
};
int[] scratch = new int[16];
// act
LossyUtils.TransformOne(src, dst, scratch);
// assert
Assert.True(expected.SequenceEqual(dst));
}
private static void RunVp8Sse4X4Test()
{
// arrange
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,
@ -35,8 +101,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
int expected = 27;
// act
int actual = LossyUtils.Vp8_Sse4X4(a, b);
// assert
Assert.Equal(expected, actual);
}
@ -65,6 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
private static void RunHadamardTransformTest()
{
// arrange
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,
@ -86,10 +155,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
ushort[] w = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 };
int expected = 2;
// act
int actual = LossyUtils.Vp8Disto4X4(a, b, w, new int[16]);
// assert
Assert.Equal(expected, actual);
}
[Fact]
public void RunTransformTwo_Works() => RunTransformTwoTest();
[Fact]
public void RunTransformOne_Works() => RunTransformOneTest();
[Fact]
public void Vp8Sse4X4_Works() => RunVp8Sse4X4Test();
@ -100,6 +178,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void HadamardTransform_Works() => RunHadamardTransformTest();
#if SUPPORTS_RUNTIME_INTRINSICS
[Fact]
public void TransformTwo_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.AllowAll);
[Fact]
public void TransformTwo_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.DisableHWIntrinsic);
[Fact]
public void TransformOne_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.AllowAll);
[Fact]
public void TransformOne_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.DisableHWIntrinsic);
[Fact]
public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll);

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

@ -47,7 +47,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
public void QuantizeBlock_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.AllowAll);
[Fact]
public void QuantizeBlock_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableHWIntrinsic);
public void QuantizeBlock_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2);
[Fact]
public void QuantizeBlock_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableAVX2);
#endif
}
}

2
tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs

@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
int[] scratch = new int[16];
// act
Vp8Encoding.ITransform(reference, input, dst, scratch);
Vp8Encoding.ITransformTwo(reference, input, dst, scratch);
// assert
Assert.True(dst.SequenceEqual(expected));

Loading…
Cancel
Save