mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Common/Helpers - Add InterleaveLower and InterleaveUpper to Vector128_ and Vector256_ - Add unit test for InterleaveLower and InterleaveUpper (specifically for Vector256_) - Add Average to Numerics.cs Common - Add 32 and 33 to the InlineArray.tt text template Formats/Jxl/IO/Metadata - Remove unnecessary System.Runtime.CompilerServices using directive from JxlCustomTransformData and JxlOpsinInvreseMatrix Formats/Jxl/Processing/Decoder - Remove unncessary using SixLabors.ImageSharp.Formats.Jxl.IO Formats/Jxl/Processing/Encoder - Add partial Fast Lossless Encoder work (+enc_fast_lossless.cc; largest file in libjxl source) - Add linear algebra (+enc_linalg.cc, +enc_linalg.h) Formats/Jxl/Processing/Jpeg - Work that would later become JXL<->JPEG lossless coding mode Formats/Jxl/Processing/Modular/Encoding/ContextPrediction - Finish context prediction (+context_predict.h) Formats/Jxl/Processing/Modular/Transforms - Finish Reversible Color Transform (+rct.cc, +rct.h, +enc_rct.cc, +enc_rct.h) - Finish Palette/Indexed coding (+palette.cc, +palette.h, +enc_palette.cc, enc_palette.h) - Finish Squeeze transform (+squeeze.cc, +squeeze.h, +enc_squeeze.cc, +enc_squeeze.h) Formats/Jxl/Processing/RenderPipeline - Incomplete render pipeline abstractions with EPF (Edge Preserving Filter) 0 stage (+render_pipeline_stage.cc, +render_pipeline_stage.h, +stage_epf.cc, +stage_epf.h) Formats/Jxl/Processing/Splines - Remove unnecessary System.Runtime.CompilerServices using directive Formats/Jxl/Processing - Add dequantizer matrices - Remove JxlEndianness (prefer ByteOrder from ImageSharp/Common) - Add missing constant to JxlLoopFilter - Remove unnecessary using SixLabors.ImageSharp.Common.Helpers from JxlMath - Replace JxlPixelFormat to use ByteOrder - Update quantizers to use dequantizer matrices and quantizer weights - Add quantizer encoding and constants - Add SIMD utilities - Remove System.Runtime.CompilerServices using from JxlWeightsSeparable5 - Remove InlineArray3, InlineArray36 and InlineArray15 from InlineArrays (3 and 15 already exist in System.Runtime.CompilerServices; 36 already exists in InlineArray.tt from ImageSharp/Common) NEXT STEPS The current focus would be applying refactors and optimizations from reviews, followed by completing the JPEG XL modular.pull/3153/head
44 changed files with 5731 additions and 104 deletions
File diff suppressed because it is too large
@ -0,0 +1,52 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using Matrix2x2 = System.Runtime.CompilerServices.InlineArray2<System.Runtime.CompilerServices.InlineArray2<double>>; |
||||
|
using Vector2 = System.Runtime.CompilerServices.InlineArray2<double>; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Handles linear algebra for encoding.
|
||||
|
/// </summary>
|
||||
|
internal static class JxlLinearAlgebra |
||||
|
{ |
||||
|
public static void ConvertToDiagonal(Matrix2x2 a, Vector2 diag, Matrix2x2 u) |
||||
|
{ |
||||
|
DebugGuard.MustBeLessThan(Math.Abs(a[0][1] - a[1][0]), 1e-15, nameof(a)); |
||||
|
|
||||
|
double b = -(a[0][0] + a[1][1]); |
||||
|
double c = (a[0][0] * a[1][1]) - (a[0][1] * a[0][1]); |
||||
|
double d = (b * b) - (4.0 * c); |
||||
|
|
||||
|
if (Math.Abs(a[0][1]) < 1e-10 || d < 0) |
||||
|
{ |
||||
|
// Already diagonal.
|
||||
|
diag[0] = a[0][0]; |
||||
|
diag[1] = a[1][1]; |
||||
|
u[0][0] = u[1][1] = 1.0; |
||||
|
u[0][1] = u[1][0] = 0.0; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
double sqd = Math.Sqrt(d); |
||||
|
double l1 = (-b - sqd) * 0.5; |
||||
|
double l2 = (-b + sqd) * 0.5; |
||||
|
|
||||
|
Vector2 v1 = default; |
||||
|
v1[0] = a[0][0] - l1; |
||||
|
v1[1] = a[1][0]; |
||||
|
|
||||
|
double v1n = 1.0 / JxlMath.Hypot(v1[0], v1[1]); |
||||
|
v1[0] = v1[0] * v1n; |
||||
|
v1[1] = v1[1] * v1n; |
||||
|
|
||||
|
diag[0] = l1; |
||||
|
diag[1] = l2; |
||||
|
|
||||
|
u[0][0] = v1[1]; |
||||
|
u[0][1] = -v1[0]; |
||||
|
u[1][0] = v1[0]; |
||||
|
u[1][1] = v1[1]; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Jpeg; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies the kind of APP marker in a JPEG file.
|
||||
|
/// </summary>
|
||||
|
internal enum JpegAppMarkerType : byte |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Unknown APP marker
|
||||
|
/// </summary>
|
||||
|
Unknown, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains ICC profile metadata
|
||||
|
/// </summary>
|
||||
|
Icc, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains EXIF profile metadata
|
||||
|
/// </summary>
|
||||
|
Exif, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains XMP profile metadata
|
||||
|
/// </summary>
|
||||
|
Xmp |
||||
|
} |
||||
@ -0,0 +1,265 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Diagnostics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains matrices used to inverse quantize coefficients.
|
||||
|
/// </summary>
|
||||
|
internal sealed class JxlDequantMatrices |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Sum(DotProduct(RequiredSizeX, RequiredSizeY)).
|
||||
|
/// </summary>
|
||||
|
private const int SumRequiredXY = 2056; |
||||
|
|
||||
|
private const int TotalTableSize = SumRequiredXY * JxlFrameDimensions.DctBlockSize * 3; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains weights & multipliers for transforms used by the codec (e.g. DCT, identity, AFV).
|
||||
|
/// </summary>
|
||||
|
public static readonly JxlQuantizerEncoding[] Library = GetLibrary(); |
||||
|
|
||||
|
private uint computedMask; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Storage for quantization.
|
||||
|
/// </summary>
|
||||
|
private readonly Memory<byte> tableStorage; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains matrices for forward quantization.
|
||||
|
/// </summary>
|
||||
|
private readonly Memory<float> table; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains matrices for inverse quantization.
|
||||
|
/// </summary>
|
||||
|
private readonly Memory<float> inverseTable; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Quantization table for DC
|
||||
|
/// </summary>
|
||||
|
private InlineArray3<float> dcQuant; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Inverse quantization table for DC
|
||||
|
/// </summary>
|
||||
|
private InlineArray3<float> inverseDcQuant; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Table offsets.
|
||||
|
/// </summary>
|
||||
|
private readonly int[] tableOffsets = new int[JxlAcStrategy.NumberOfValidStrategies * 3]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Quantizer encodings. Multiple may be used depending on the kind of transform.
|
||||
|
/// </summary>
|
||||
|
private JxlQuantizerEncoding[] encodings = []; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="JxlDequantMatrices"/> class.
|
||||
|
/// </summary>
|
||||
|
public JxlDequantMatrices() |
||||
|
{ |
||||
|
// float dc_quant_[3] = {kDCQuant[0], kDCQuant[1], kDCQuant[2]};
|
||||
|
// float inv_dc_quant_[3] = {kInvDCQuant[0], kInvDCQuant[1], kInvDCQuant[2]};
|
||||
|
this.dcQuant[0] = JxlQuantizerConstants.DcQuant[0]; |
||||
|
this.dcQuant[1] = JxlQuantizerConstants.DcQuant[1]; |
||||
|
this.dcQuant[2] = JxlQuantizerConstants.DcQuant[2]; |
||||
|
|
||||
|
this.inverseDcQuant[0] = JxlQuantizerConstants.InverseDcQuant[0]; |
||||
|
this.inverseDcQuant[1] = JxlQuantizerConstants.InverseDcQuant[1]; |
||||
|
this.inverseDcQuant[2] = JxlQuantizerConstants.InverseDcQuant[2]; |
||||
|
|
||||
|
this.encodings = new JxlQuantizerEncoding[JxlQuantizerConstants.NumberOfQuantizerTables]; |
||||
|
for (int i = 0; i < this.encodings.Length; i++) |
||||
|
{ |
||||
|
this.encodings[i] = JxlQuantizerEncoding.Library(0); |
||||
|
} |
||||
|
|
||||
|
int pos = 0; |
||||
|
Span<int> offsets = stackalloc int[JxlQuantizerConstants.NumberOfQuantizerTables * 3]; |
||||
|
|
||||
|
for (int i = 0; i < JxlQuantizerConstants.NumberOfQuantizerTables; i++) |
||||
|
{ |
||||
|
int numBlocks = RequiredSizeX[i] * RequiredSizeY[i]; |
||||
|
int num = numBlocks * JxlFrameDimensions.DctBlockSize; |
||||
|
int i3 = 3 * i; |
||||
|
|
||||
|
for (int c = 0; c < 3; c++) |
||||
|
{ |
||||
|
offsets[i3 + c] = pos + (c * num); |
||||
|
} |
||||
|
|
||||
|
pos += 3 * num; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < JxlAcStrategy.NumberOfValidStrategies; i++) |
||||
|
{ |
||||
|
for (int c = 0; c < 3; c++) |
||||
|
{ |
||||
|
this.tableOffsets[(i * 3) + c] = offsets[((int)JxlQuantizerConstants.AcStrategyToQuantTableMap[i] * 3) + c]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a lookup which represents required widths for each quantizer.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<int> RequiredSizeX => [1, 1, 1, 1, 2, 4, 1, 1, 2, 1, 1, 8, 4, 16, 8, 32, 16]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a lookup which represents required heights for each quantizer.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<int> RequiredSizeY => [1, 1, 1, 1, 2, 4, 2, 4, 4, 1, 1, 8, 8, 16, 16, 32, 32]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the default library with quantizer encodings for all transforms
|
||||
|
/// used by the JPEG XL codec.
|
||||
|
/// </summary>
|
||||
|
/// <returns>Encodings for all kinds of transforms.</returns>
|
||||
|
/// <exception cref="InvalidOperationException">Used when quantization constants were partially updated.</exception>
|
||||
|
public static JxlQuantizerEncoding[] GetLibrary() |
||||
|
{ |
||||
|
if (JxlQuantizerConstants.NumberOfQuantizerTables != 17) |
||||
|
{ |
||||
|
throw new InvalidOperationException("This function should be updated when adding new quantization types"); |
||||
|
} |
||||
|
|
||||
|
if (JxlQuantWeights.NumPredefinedTables != 1) |
||||
|
{ |
||||
|
throw new InvalidOperationException("This function should be updated when adding new quantization matrices to the library"); |
||||
|
} |
||||
|
|
||||
|
Verify(0, JxlQuantTable.DCT); |
||||
|
Verify(1, JxlQuantTable.IDENTITY); |
||||
|
Verify(2, JxlQuantTable.DCT2X2); |
||||
|
Verify(3, JxlQuantTable.DCT4X4); |
||||
|
Verify(4, JxlQuantTable.DCT16X16); |
||||
|
Verify(5, JxlQuantTable.DCT32X32); |
||||
|
Verify(6, JxlQuantTable.DCT8X16); |
||||
|
Verify(7, JxlQuantTable.DCT8X32); |
||||
|
Verify(8, JxlQuantTable.DCT16X32); |
||||
|
Verify(9, JxlQuantTable.DCT4X8); |
||||
|
Verify(10, JxlQuantTable.AFV0); |
||||
|
Verify(11, JxlQuantTable.DCT64X64); |
||||
|
Verify(12, JxlQuantTable.DCT32X64); |
||||
|
Verify(13, JxlQuantTable.DCT128X128); |
||||
|
Verify(14, JxlQuantTable.DCT64X128); |
||||
|
Verify(15, JxlQuantTable.DCT256X256); |
||||
|
Verify(16, JxlQuantTable.DCT128X256); |
||||
|
|
||||
|
return |
||||
|
[ |
||||
|
JxlQuantWeights.Dct, |
||||
|
JxlQuantWeights.Identity, |
||||
|
JxlQuantWeights.Dct2x2, |
||||
|
JxlQuantWeights.Dct4x4, |
||||
|
JxlQuantWeights.Dct16x16, |
||||
|
JxlQuantWeights.Dct32x32, |
||||
|
JxlQuantWeights.Dct8x16, |
||||
|
JxlQuantWeights.Dct8x32, |
||||
|
JxlQuantWeights.Dct16x32, |
||||
|
JxlQuantWeights.Dct4x8, |
||||
|
JxlQuantWeights.Afv, |
||||
|
JxlQuantWeights.Dct64x64, |
||||
|
JxlQuantWeights.Dct32x32, |
||||
|
JxlQuantWeights.Dct128x128, |
||||
|
JxlQuantWeights.Dct64x128, |
||||
|
JxlQuantWeights.Dct256x256, |
||||
|
JxlQuantWeights.Dct128x256 |
||||
|
]; |
||||
|
|
||||
|
[Conditional("DEBUG")] |
||||
|
static void Verify(int expected, JxlQuantTable actual) |
||||
|
{ |
||||
|
if (expected != (byte)actual) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Quantizer modes were partially updated; this method needs to be updated too"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a matrix for the specified kind of quantizer and index.
|
||||
|
/// </summary>
|
||||
|
/// <param name="quantKind">Quantizer kind</param>
|
||||
|
/// <param name="c">Index</param>
|
||||
|
/// <returns>Matrix</returns>
|
||||
|
public Span<float> GetMatrix(JxlAcStrategyType quantKind, int c) |
||||
|
{ |
||||
|
DebugGuard.MustBeGreaterThan((1 << (int)quantKind) & this.computedMask, 0, nameof(quantKind)); |
||||
|
return this.table.Span[this.tableOffsets[((int)quantKind * 3) + c]..]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an inverse matrix for the specified kind of quantizer and index.
|
||||
|
/// </summary>
|
||||
|
/// <param name="quantKind">Quantizer kind</param>
|
||||
|
/// <param name="c">Index</param>
|
||||
|
/// <returns>Inverse matrix</returns>
|
||||
|
public Span<float> GetInverseMatrix(JxlAcStrategyType quantKind, int c) |
||||
|
{ |
||||
|
DebugGuard.MustBeGreaterThan((1 << (int)quantKind) & this.computedMask, 0, nameof(quantKind)); |
||||
|
return this.inverseTable.Span[this.tableOffsets[((int)quantKind * 3) + c]..]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a DC quant for index c.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c">The DC quantizer index.</param>
|
||||
|
/// <returns>DC quant for index <paramref name="c"/>.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public float GetDcQuant(int c) => this.dcQuant[c]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns all DC quantizers. See also <seealso cref="GetDcQuant(int)"/>.
|
||||
|
/// </summary>
|
||||
|
/// <returns>Span that covers all DC quantizers.</returns>
|
||||
|
public Span<float> GetDcQuants() => this.dcQuant; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns an inverse DC quant for index c.
|
||||
|
/// </summary>
|
||||
|
/// <param name="c">The inverse DC quantizer index.</param>
|
||||
|
/// <returns>Inverse DC quant for index <paramref name="c"/>.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public float GetInverseDcQuant(int c) => this.inverseDcQuant[c]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the specified DC quantizer.
|
||||
|
/// </summary>
|
||||
|
/// <param name="dc">DC quantizer to apply to the dequantization matrices.</param>
|
||||
|
public void SetDcQuant(InlineArray3<float> dc) |
||||
|
{ |
||||
|
for (int c = 0; c < 3; c++) |
||||
|
{ |
||||
|
this.dcQuant[c] = 1f / dc[c]; |
||||
|
this.inverseDcQuant[c] = dc[c]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets custom quantizer encodings for transform functions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="encodings">The encodings to identify required transform functions.</param>
|
||||
|
public void SetEncodings(JxlQuantizerEncoding[] encodings) |
||||
|
{ |
||||
|
this.encodings = encodings; |
||||
|
this.computedMask = 0; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns quantizer encodings for this dequant matrices instance.
|
||||
|
/// </summary>
|
||||
|
/// <returns>
|
||||
|
/// Encodings set by the <see cref="SetEncodings(JxlQuantizerEncoding[])"/> method.
|
||||
|
/// By default (when the aforementioned method wasn't invoked), the result
|
||||
|
/// is simply an empty span.
|
||||
|
/// </returns>
|
||||
|
public Span<JxlQuantizerEncoding> GetEncodings() => this.encodings; |
||||
|
} |
||||
@ -1,25 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Specifies the ordering of multi-byte data.
|
|
||||
/// </summary>
|
|
||||
internal enum JxlEndianness : byte |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Use endianness of the CPU/system.
|
|
||||
/// </summary>
|
|
||||
Native, |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Force little endian.
|
|
||||
/// </summary>
|
|
||||
Little, |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Force big endian.
|
|
||||
/// </summary>
|
|
||||
Big |
|
||||
} |
|
||||
@ -0,0 +1,46 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Shared constants used by the quantizer.
|
||||
|
/// </summary>
|
||||
|
internal static class JxlQuantizerConstants |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Total number of quantization tables.
|
||||
|
/// </summary>
|
||||
|
public const byte NumberOfQuantizerTables = (byte)(JxlQuantTable.DCT128X256 + 1); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the inverse DC quantization table.
|
||||
|
/// </summary>
|
||||
|
public static ReadOnlySpan<float> InverseDcQuant => [4096f, 512f, 256f]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the forward DC quantization table.
|
||||
|
/// </summary>
|
||||
|
public static ReadOnlySpan<float> DcQuant => [ |
||||
|
1f / 4096f, |
||||
|
1f / 512f, |
||||
|
1f / 256f]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a translation table for converting AC strategies to quant tables.
|
||||
|
/// Simply pass the index of the AC strategy enum and you'll get back the
|
||||
|
/// matching quant table.
|
||||
|
/// </summary>
|
||||
|
public static ReadOnlySpan<JxlQuantTable> AcStrategyToQuantTableMap => |
||||
|
[ |
||||
|
JxlQuantTable.DCT, JxlQuantTable.IDENTITY, JxlQuantTable.DCT2X2, |
||||
|
JxlQuantTable.DCT4X4, JxlQuantTable.DCT16X16, JxlQuantTable.DCT32X32, |
||||
|
JxlQuantTable.DCT8X16, JxlQuantTable.DCT8X16, JxlQuantTable.DCT8X32, |
||||
|
JxlQuantTable.DCT8X32, JxlQuantTable.DCT16X32, JxlQuantTable.DCT16X32, |
||||
|
JxlQuantTable.DCT4X8, JxlQuantTable.DCT4X8, JxlQuantTable.AFV0, |
||||
|
JxlQuantTable.AFV0, JxlQuantTable.AFV0, JxlQuantTable.AFV0, |
||||
|
JxlQuantTable.DCT64X64, JxlQuantTable.DCT32X64, JxlQuantTable.DCT32X64, |
||||
|
JxlQuantTable.DCT128X128, JxlQuantTable.DCT64X128, JxlQuantTable.DCT64X128, |
||||
|
JxlQuantTable.DCT256X256, JxlQuantTable.DCT128X256, JxlQuantTable.DCT128X256 |
||||
|
]; |
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
internal static partial class JxlSimdUtils |
||||
|
{ |
||||
|
public static void StoreInterleaved<T>(Vector<T> v1, Vector<T> v2, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector<T> v1, Vector<T> v2, Vector<T> v3, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector<T> v1, Vector<T> v2, Vector<T> v3, Vector<T> v4, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector<T> v1, Vector<T> v2, Vector<T> v3, Vector<T> v4, Vector<T> v5, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3)); |
||||
|
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector<T> v1, Vector<T> v2, Vector<T> v3, Vector<T> v4, Vector<T> v5, Vector<T> v6, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3)); |
||||
|
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4)); |
||||
|
v6.StoreUnsafe(ref Unsafe.Add(ref memory, 5)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector128<T> v1, Vector128<T> v2, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector128<T> v1, Vector128<T> v2, Vector128<T> v3, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector128<T> v1, Vector128<T> v2, Vector128<T> v3, Vector128<T> v4, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector128<T> v1, Vector128<T> v2, Vector128<T> v3, Vector128<T> v4, Vector128<T> v5, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3)); |
||||
|
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector128<T> v1, Vector128<T> v2, Vector128<T> v3, Vector128<T> v4, Vector128<T> v5, Vector128<T> v6, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3)); |
||||
|
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4)); |
||||
|
v6.StoreUnsafe(ref Unsafe.Add(ref memory, 5)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector256<T> v1, Vector256<T> v2, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector256<T> v1, Vector256<T> v2, Vector256<T> v3, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector256<T> v1, Vector256<T> v2, Vector256<T> v3, Vector256<T> v4, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector256<T> v1, Vector256<T> v2, Vector256<T> v3, Vector256<T> v4, Vector256<T> v5, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3)); |
||||
|
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4)); |
||||
|
} |
||||
|
|
||||
|
public static void StoreInterleaved<T>(Vector256<T> v1, Vector256<T> v2, Vector256<T> v3, Vector256<T> v4, Vector256<T> v5, Vector256<T> v6, ref T memory) |
||||
|
{ |
||||
|
v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0)); |
||||
|
v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1)); |
||||
|
v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2)); |
||||
|
v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3)); |
||||
|
v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4)); |
||||
|
v6.StoreUnsafe(ref Unsafe.Add(ref memory, 5)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
<#@ template debug="false" hostspecific="false" language="C#" #> |
||||
|
<#@ assembly name="System.Core" #> |
||||
|
<#@ import namespace="System.Linq" #> |
||||
|
<#@ import namespace="System.Text" #> |
||||
|
<#@ import namespace="System.Collections.Generic" #> |
||||
|
<#@ output extension=".Generated.cs" #> |
||||
|
// Copyright (c) Six Labors. |
||||
|
// Licensed under the Six Labors Split License. |
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
internal static partial class JxlSimdUtils |
||||
|
{ |
||||
|
<# |
||||
|
string[] vectorTypes = [ |
||||
|
"Vector<T>", |
||||
|
"Vector128<T>", |
||||
|
"Vector256<T>" |
||||
|
]; |
||||
|
|
||||
|
const int maxVectorSize = 6; |
||||
|
|
||||
|
foreach (string vect in vectorTypes) { |
||||
|
for (int i = 2; i <= maxVectorSize; i++) { |
||||
|
List<string> vectorParameters = []; |
||||
|
for (int j = 0; j < i; j++) { |
||||
|
vectorParameters.Add($"{vect} v{j + 1}"); |
||||
|
} |
||||
|
string inlineParameters = string.Join(", ", vectorParameters) + ", "; |
||||
|
#> |
||||
|
public static void StoreInterleaved<T>(<#= inlineParameters #>ref T memory) |
||||
|
{ |
||||
|
<# for (int j = 0; j < i; j++) { #> |
||||
|
v<#= j + 1 #>.StoreUnsafe(ref Unsafe.Add(ref memory, <#= j #>)); |
||||
|
<# } #> |
||||
|
} |
||||
|
|
||||
|
<# } } #> |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Shared SIMD-accelerated utilities.
|
||||
|
/// </summary>
|
||||
|
internal static partial class JxlSimdUtils |
||||
|
{ |
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<int> ConcatLowerLower(Vector256<int> a, Vector256<int> b) => Vector256.Create(a.GetLower(), b.GetLower()); |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<int> ConcatUpperUpper(Vector256<int> a, Vector256<int> b) => Vector256.Create(a.GetUpper(), b.GetUpper()); |
||||
|
|
||||
|
public static void Transpose8x8Block(Span<int> fromSpan, Span<int> toSpan, int stride) |
||||
|
{ |
||||
|
ref int from = ref MemoryMarshal.GetReference(fromSpan); |
||||
|
ref int to = ref MemoryMarshal.GetReference(toSpan); |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector256<int> i0 = Vector256.LoadUnsafe(ref from); |
||||
|
Vector256<int> i1 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, stride)); |
||||
|
Vector256<int> i2 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 2 * stride)); |
||||
|
Vector256<int> i3 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 3 * stride)); |
||||
|
Vector256<int> i4 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 4 * stride)); |
||||
|
Vector256<int> i5 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 5 * stride)); |
||||
|
Vector256<int> i6 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 6 * stride)); |
||||
|
Vector256<int> i7 = Vector256.LoadUnsafe(ref Unsafe.Add(ref from, 7 * stride)); |
||||
|
|
||||
|
Vector256<int> q0 = Vector256_.InterleaveLower(i0, i2); |
||||
|
Vector256<int> q1 = Vector256_.InterleaveLower(i1, i3); |
||||
|
Vector256<int> q2 = Vector256_.InterleaveUpper(i0, i2); |
||||
|
Vector256<int> q3 = Vector256_.InterleaveUpper(i1, i3); |
||||
|
Vector256<int> q4 = Vector256_.InterleaveLower(i4, i6); |
||||
|
Vector256<int> q5 = Vector256_.InterleaveLower(i5, i7); |
||||
|
Vector256<int> q6 = Vector256_.InterleaveUpper(i4, i6); |
||||
|
Vector256<int> q7 = Vector256_.InterleaveUpper(i5, i7); |
||||
|
|
||||
|
Vector256<int> r0 = Vector256_.InterleaveLower(q0, q1); |
||||
|
Vector256<int> r1 = Vector256_.InterleaveUpper(q0, q1); |
||||
|
Vector256<int> r2 = Vector256_.InterleaveLower(q2, q3); |
||||
|
Vector256<int> r3 = Vector256_.InterleaveUpper(q2, q3); |
||||
|
Vector256<int> r4 = Vector256_.InterleaveLower(q4, q5); |
||||
|
Vector256<int> r5 = Vector256_.InterleaveUpper(q4, q5); |
||||
|
Vector256<int> r6 = Vector256_.InterleaveLower(q6, q7); |
||||
|
Vector256<int> r7 = Vector256_.InterleaveUpper(q6, q7); |
||||
|
|
||||
|
i0 = ConcatLowerLower(r4, r0); |
||||
|
i1 = ConcatLowerLower(r5, r1); |
||||
|
i2 = ConcatLowerLower(r6, r2); |
||||
|
i3 = ConcatLowerLower(r7, r3); |
||||
|
i4 = ConcatUpperUpper(r4, r0); |
||||
|
i5 = ConcatUpperUpper(r5, r1); |
||||
|
i6 = ConcatUpperUpper(r6, r2); |
||||
|
i7 = ConcatUpperUpper(r7, r3); |
||||
|
|
||||
|
i0.StoreUnsafe(ref to); |
||||
|
i1.StoreUnsafe(ref Unsafe.Add(ref to, 8)); |
||||
|
i2.StoreUnsafe(ref Unsafe.Add(ref to, 16)); |
||||
|
i3.StoreUnsafe(ref Unsafe.Add(ref to, 24)); |
||||
|
i4.StoreUnsafe(ref Unsafe.Add(ref to, 32)); |
||||
|
i5.StoreUnsafe(ref Unsafe.Add(ref to, 40)); |
||||
|
i6.StoreUnsafe(ref Unsafe.Add(ref to, 48)); |
||||
|
i7.StoreUnsafe(ref Unsafe.Add(ref to, 56)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Vector128 fallback
|
||||
|
for (int n = 0; n < 8; n += 4) |
||||
|
{ |
||||
|
for (int m = 0; m < 8; m += 4) |
||||
|
{ |
||||
|
Vector128<int> p0 = Vector128.LoadUnsafe(ref Unsafe.Add(ref from, (n * stride) + m)); |
||||
|
Vector128<int> p1 = Vector128.LoadUnsafe(ref Unsafe.Add(ref from, ((n + 1) * stride) + m)); |
||||
|
Vector128<int> p2 = Vector128.LoadUnsafe(ref Unsafe.Add(ref from, ((n + 2) * stride) + m)); |
||||
|
Vector128<int> p3 = Vector128.LoadUnsafe(ref Unsafe.Add(ref from, ((n + 3) * stride) + m)); |
||||
|
|
||||
|
Vector128<int> q0 = Vector128_.InterleaveLower(p0, p2); |
||||
|
Vector128<int> q1 = Vector128_.InterleaveLower(p1, p3); |
||||
|
Vector128<int> q2 = Vector128_.InterleaveUpper(p0, p2); |
||||
|
Vector128<int> q3 = Vector128_.InterleaveUpper(p1, p3); |
||||
|
|
||||
|
Vector128<int> r0 = Vector128_.InterleaveLower(q0, q1); |
||||
|
Vector128<int> r1 = Vector128_.InterleaveUpper(q0, q1); |
||||
|
Vector128<int> r2 = Vector128_.InterleaveLower(q2, q3); |
||||
|
Vector128<int> r3 = Vector128_.InterleaveUpper(q2, q3); |
||||
|
|
||||
|
r0.StoreUnsafe(ref Unsafe.Add(ref to, (m * 8) + n)); |
||||
|
r1.StoreUnsafe(ref Unsafe.Add(ref to, ((m + 1) * 8) + n)); |
||||
|
r2.StoreUnsafe(ref Unsafe.Add(ref to, ((m + 2) * 8) + n)); |
||||
|
r3.StoreUnsafe(ref Unsafe.Add(ref to, ((m + 3) * 8) + n)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPrediction; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The result of context prediction.
|
||||
|
/// </summary>
|
||||
|
/// <param name="Context">Context used in MA lookup.</param>
|
||||
|
/// <param name="Guess">Predicted coefficient.</param>
|
||||
|
/// <param name="Predictor">Kind of predictor mode used.</param>
|
||||
|
/// <param name="Multiplier">Multiplier used in MA lookup.</param>
|
||||
|
internal record struct JxlPredictionResult(int Context, int Guess, JxlPredictor Predictor, int Multiplier); |
||||
@ -0,0 +1,35 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Encoding.ContextPrediction; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Flags for context prediction.
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
internal enum JxlPredictorMode : byte |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Should tree-based prediction be used?
|
||||
|
/// </summary>
|
||||
|
UseTree = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Should the weighted predictor be used?
|
||||
|
/// </summary>
|
||||
|
UseWeightedPrediction = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Should properties be computed? (When this bit is 0,
|
||||
|
/// the properties are not set and therefore have their
|
||||
|
/// default values)
|
||||
|
/// </summary>
|
||||
|
ForceComputeProperties = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Try all predictors?
|
||||
|
/// </summary>
|
||||
|
AllPredictions = 8, |
||||
|
|
||||
|
NoEdgeCases = 16 |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,793 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
#pragma warning disable IDE0057 // Use range operator
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Modular.Transforms; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Implements the <em>squeeze transform</em>.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The squeeze transform in JXL is a reversible
|
||||
|
/// wavelet-like decomposition used in the modular mode
|
||||
|
/// to reduce redundancy and improve compression,
|
||||
|
/// especially for structured or synthetic images.
|
||||
|
/// It works by hierarchically splitting channesl
|
||||
|
/// into lower-resolution representations plus
|
||||
|
/// residuals, giving us multi-resolution coding while
|
||||
|
/// remaining lossless.
|
||||
|
/// </remarks>
|
||||
|
internal static class JxlSqueeze |
||||
|
{ |
||||
|
private const int MaxFirstPreviewSize = 8; |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static int SmoothTendency(int b, int a, int n) |
||||
|
{ |
||||
|
int diff = 0; |
||||
|
if (b >= a && a >= n) |
||||
|
{ |
||||
|
diff = ((4 * b) - (3 * n) - a + 6) / 12; |
||||
|
|
||||
|
if (diff - (diff & 1) > 2 * (b - a)) |
||||
|
{ |
||||
|
diff = (2 * (b - a)) + 1; |
||||
|
} |
||||
|
|
||||
|
if (diff + (diff & 1) > 2 * (a - n)) |
||||
|
{ |
||||
|
diff = 2 * (a - n); |
||||
|
} |
||||
|
} |
||||
|
else if (b <= a && a <= n) |
||||
|
{ |
||||
|
diff = ((4 * b) - (3 * n) - a - 6) / 12; |
||||
|
|
||||
|
if (diff + (diff & 1) < 2 * (b - a)) |
||||
|
{ |
||||
|
diff = (2 * (b - a)) - 1; |
||||
|
} |
||||
|
|
||||
|
if (diff - (diff & 1) < 2 * (a - n)) |
||||
|
{ |
||||
|
diff = 2 * (a - n); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return diff; |
||||
|
} |
||||
|
|
||||
|
// The function operates on 256-bit fixed size vectors,
|
||||
|
// 8 elements at a time. It should still work even on CPUs
|
||||
|
// without 256-bit vector support (the JIT will translate
|
||||
|
// these into 128-bit halves, or scalar without SIMD support).
|
||||
|
//
|
||||
|
// The FastUnsqueeze method CAN operate on vectors below
|
||||
|
// 256-bit, but not above. It's better to simply use Vector256
|
||||
|
// rather than duplicate everything. Vector<T> may be a problem
|
||||
|
// as its number of elements can be greater than 8 which is too
|
||||
|
// much for this method.
|
||||
|
[MethodImpl(InliningOptions.HotPath)] // Called on an entire image
|
||||
|
private static void FastUnsqueeze(Span<int> pResidual, Span<int> pAvg, Span<int> pNAvg, Span<int> pPout, Span<int> pOut, Span<int> pNOut) |
||||
|
{ |
||||
|
Vector256<int> oneThird = Vector256.Create(0x55555556); |
||||
|
|
||||
|
ref int pAvgRef = ref MemoryMarshal.GetReference(pAvg); |
||||
|
ref int pNAvgRef = ref MemoryMarshal.GetReference(pNAvg); |
||||
|
ref int pPoutReference = ref MemoryMarshal.GetReference(pPout); |
||||
|
ref int pResidualRef = ref MemoryMarshal.GetReference(pResidual); |
||||
|
ref int pOutRef = ref MemoryMarshal.GetReference(pOut); |
||||
|
ref int pNOutRef = ref MemoryMarshal.GetReference(pNOut); |
||||
|
|
||||
|
Vector256<int> avg = Vector256.LoadUnsafe(ref pAvgRef); |
||||
|
Vector256<int> nextAvg = Vector256.LoadUnsafe(ref pNAvgRef); |
||||
|
Vector256<int> top = Vector256.LoadUnsafe(ref pPoutReference); |
||||
|
|
||||
|
Vector256<int> ba = top - avg; |
||||
|
Vector256<int> an = avg - nextAvg; |
||||
|
Vector256<int> nonmono = ba ^ an; |
||||
|
Vector256<int> absba = Vector256.Abs(ba); |
||||
|
Vector256<int> absan = Vector256.Abs(an); |
||||
|
Vector256<int> absbn = Vector256.Abs(top - nextAvg); |
||||
|
|
||||
|
Vector256<long> a3eh = Vector256_.MultiplyEven(absba, oneThird); |
||||
|
Vector256<long> a3oh = Vector256_.MultiplyOdd(absba, oneThird); |
||||
|
|
||||
|
Vector256<int> a3 = BitConverter.IsLittleEndian |
||||
|
? Vector256_.InterleaveOdd(a3eh.AsInt32(), a3oh.AsInt32()) |
||||
|
: Vector256_.InterleaveEven(a3eh.AsInt32(), a3oh.AsInt32()); |
||||
|
|
||||
|
a3 += absbn + Vector256.Create(2); |
||||
|
|
||||
|
Vector256<int> absdiff = a3 >> 2; |
||||
|
|
||||
|
Vector256<int> skipdiff = Vector256_.NotEqual(ba, Vector256<int>.Zero); |
||||
|
skipdiff &= Vector256_.NotEqual(an, Vector256<int>.Zero); |
||||
|
skipdiff &= Vector256.LessThan(nonmono, Vector256<int>.Zero); |
||||
|
|
||||
|
Vector256<int> absBa2 = (absba << 1) + (absdiff & Vector256<int>.One); |
||||
|
|
||||
|
absdiff = Vector256.ConditionalSelect( |
||||
|
Vector256.GreaterThan(absdiff, absBa2), |
||||
|
(absba << 1) + Vector256<int>.One, |
||||
|
absdiff); |
||||
|
|
||||
|
Vector256<int> absan2 = absan << 1; |
||||
|
absdiff = Vector256.ConditionalSelect( |
||||
|
Vector256.GreaterThan(absdiff + (absdiff & Vector256<int>.One), absan2), |
||||
|
absan2, |
||||
|
absdiff); |
||||
|
|
||||
|
Vector256<int> diff1 = Vector256.ConditionalSelect( |
||||
|
Vector256.LessThan(top, nextAvg), |
||||
|
-absdiff, |
||||
|
absdiff); |
||||
|
|
||||
|
Vector256<int> tendency = diff1 & ~skipdiff; |
||||
|
Vector256<int> diffMinusTendency = Vector256.LoadUnsafe(ref pResidualRef); |
||||
|
Vector256<int> diff = diffMinusTendency + tendency; |
||||
|
Vector256<int> output = avg + (diff + (diff << 31)); |
||||
|
|
||||
|
output.StoreUnsafe(ref pOutRef); |
||||
|
(output - diff).StoreUnsafe(ref pNOutRef); |
||||
|
} |
||||
|
|
||||
|
public static void InverseHorizontalSqueeze(Configuration configuration, JxlModularImage input, int c, int rc) |
||||
|
{ |
||||
|
// Channel offsets should not overflow.
|
||||
|
DebugGuard.MustBeLessThan(c, input.Channels.Count, nameof(c)); |
||||
|
DebugGuard.MustBeLessThan(rc, input.Channels.Count, nameof(c)); |
||||
|
|
||||
|
JxlModularChannel inputChannel = input.Channels[c]; |
||||
|
JxlModularChannel inputResidualChannel = input.Channels[rc]; |
||||
|
|
||||
|
if (inputChannel.Width != JxlMath.DivCeil(inputChannel.Width + inputResidualChannel.Width, 2)) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Invalid width"); |
||||
|
} |
||||
|
|
||||
|
if (inputChannel.Height != inputResidualChannel.Height) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Height of the input channel must be equal to the height of the residual channel"); |
||||
|
} |
||||
|
|
||||
|
if (inputResidualChannel.Width == 0) |
||||
|
{ |
||||
|
input.Channels[c].HorizontalShift--; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Do not dispose.
|
||||
|
JxlModularChannel outputChannel = new( |
||||
|
configuration, |
||||
|
inputChannel.Width + inputResidualChannel.Width, |
||||
|
inputChannel.Height, |
||||
|
inputChannel.HorizontalShift - 1, |
||||
|
inputChannel.VerticalShift); |
||||
|
|
||||
|
if (inputResidualChannel.Height == 0) |
||||
|
{ |
||||
|
input.Channels[c] = outputChannel; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// The number of rows a single parallel iteration computes
|
||||
|
// is stored here.
|
||||
|
const int rowsPerThread = 8; |
||||
|
|
||||
|
// rowsPerThread * 9, aligned to the power of 2.
|
||||
|
const int rowsPerThreadMul9Alignment = 128; |
||||
|
|
||||
|
// rowsPerThread * 8, aligned to the power of 2.
|
||||
|
const int rowsPerThreadMul8Alignment = 64; |
||||
|
|
||||
|
_ = Parallel.For(0, JxlMath.DivCeil(inputChannel.Height, rowsPerThread), configuration.GetParallelOptions(), idx => |
||||
|
{ |
||||
|
int y0 = idx * rowsPerThread; |
||||
|
int rows = Math.Min(rowsPerThread, inputChannel.Height - y0); |
||||
|
int x = 0; |
||||
|
|
||||
|
int onerow_in = inputChannel.Plane.PixelsPerRow; |
||||
|
int onerow_inr = inputResidualChannel.Plane.PixelsPerRow; |
||||
|
int onerow_out = outputChannel.Plane.PixelsPerRow; |
||||
|
Span<int> pResidual = inputResidualChannel.GetRow(y0); |
||||
|
Span<int> pAverage = inputChannel.GetRow(y0); |
||||
|
Span<int> pOut = outputChannel.GetRow(y0); |
||||
|
ref int pOutRef = ref MemoryMarshal.GetReference(pOut); |
||||
|
|
||||
|
Span<int> bpAvg = stackalloc int[rowsPerThreadMul9Alignment].Slice(0, rowsPerThread * 9); |
||||
|
Span<int> bpResidual = stackalloc int[rowsPerThreadMul8Alignment].Slice(0, rowsPerThread * 8); |
||||
|
Span<int> bpOutEven = stackalloc int[rowsPerThreadMul8Alignment].Slice(0, rowsPerThread * 8); |
||||
|
Span<int> bpOutOdd = stackalloc int[rowsPerThreadMul8Alignment].Slice(0, rowsPerThread * 8); |
||||
|
Span<int> bpOutEvenT = stackalloc int[rowsPerThreadMul8Alignment].Slice(0, rowsPerThread * 8); |
||||
|
Span<int> bpOutOddT = stackalloc int[rowsPerThreadMul8Alignment].Slice(0, rowsPerThread * 8); |
||||
|
|
||||
|
ref int bpOutEvenTRef = ref MemoryMarshal.GetReference(bpOutEvenT); |
||||
|
ref int bpOutOddTRef = ref MemoryMarshal.GetReference(bpOutOddT); |
||||
|
|
||||
|
int n = Vector256<int>.Count; |
||||
|
|
||||
|
if (inputResidualChannel.Width > 16 && rows == rowsPerThread) |
||||
|
{ |
||||
|
for (; x < inputResidualChannel.Width - 9; x += 8) |
||||
|
{ |
||||
|
JxlSimdUtils.Transpose8x8Block(pResidual[x..], bpResidual, onerow_inr); |
||||
|
JxlSimdUtils.Transpose8x8Block(pAverage[x..], bpAvg, onerow_in); |
||||
|
|
||||
|
for (int y = 0; y < rowsPerThread; y++) |
||||
|
{ |
||||
|
bpAvg[64 + y] = pAverage[x + 8 + (onerow_in * y)]; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < 8; i++) |
||||
|
{ |
||||
|
// i * 8
|
||||
|
int i8 = i << 3; |
||||
|
|
||||
|
FastUnsqueeze( |
||||
|
bpResidual[i8..], |
||||
|
bpAvg[i8..], |
||||
|
bpAvg[(8 * (i + 1))..], |
||||
|
(x + i > 0) ? bpOutOdd[(8 * ((x + i - 1) & 7))..] : bpAvg[i8..], |
||||
|
bpOutEven[i8..], |
||||
|
bpOutOdd[i8..]); |
||||
|
} |
||||
|
|
||||
|
JxlSimdUtils.Transpose8x8Block(bpOutEven, bpOutEvenT, 8); |
||||
|
JxlSimdUtils.Transpose8x8Block(bpOutOdd, bpOutOddT, 8); |
||||
|
|
||||
|
for (int y = 0; y < rowsPerThread; y++) |
||||
|
{ |
||||
|
// y * 8
|
||||
|
int y8 = y << 3; |
||||
|
|
||||
|
for (int i = 0; i < rowsPerThread; i += n) |
||||
|
{ |
||||
|
int offset = y8 + i; |
||||
|
|
||||
|
Vector256<int> even = Vector256.LoadUnsafe(ref Unsafe.Add(ref bpOutEvenTRef, offset)); |
||||
|
Vector256<int> odd = Vector256.LoadUnsafe(ref Unsafe.Add(ref bpOutOddTRef, offset)); |
||||
|
|
||||
|
JxlSimdUtils.StoreInterleaved( |
||||
|
even, |
||||
|
odd, |
||||
|
ref Unsafe.Add(ref pOutRef, ((x + i) << 1) + (onerow_out * y))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (int y = 0; y < rows; y++) |
||||
|
{ |
||||
|
UnsqueezeRow(y0 + y, x); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
input.Channels[c] = outputChannel; |
||||
|
|
||||
|
void UnsqueezeRow(int y, int x0) |
||||
|
{ |
||||
|
Span<int> residual = inputResidualChannel.GetRow(y); |
||||
|
Span<int> average = inputChannel.GetRow(y); |
||||
|
Span<int> output = outputChannel.GetRow(y); |
||||
|
int inputChannelWidth = inputChannel.Width; |
||||
|
int outputChannelWidth = outputChannel.Width; |
||||
|
|
||||
|
for (int x = x0; x < inputResidualChannel.Width; x++) |
||||
|
{ |
||||
|
int xLsh1 = x << 1; // Prevents left shifting three times. Saves on CPU cycles.
|
||||
|
|
||||
|
int diffMinusTendency = residual[x]; |
||||
|
int avg = average[x]; |
||||
|
int nextAverage = x + 1 < inputChannelWidth ? average[x + 1] : avg; |
||||
|
|
||||
|
int left = x > 0 ? output[xLsh1 - 1] : avg; |
||||
|
int tendency = SmoothTendency(left, avg, nextAverage); |
||||
|
int diff = diffMinusTendency + tendency; |
||||
|
|
||||
|
int a = avg + (diff / 2); |
||||
|
output[xLsh1] = a; |
||||
|
|
||||
|
int b = a - diff; |
||||
|
output[xLsh1 + 1] = b; |
||||
|
} |
||||
|
|
||||
|
if ((outputChannelWidth & 1) > 0) |
||||
|
{ |
||||
|
output[outputChannelWidth - 1] = average[inputChannelWidth - 1]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void InverseVerticalSqueeze(Configuration configuration, JxlModularImage input, int c, int rc) |
||||
|
{ |
||||
|
// Channel offsets should not overflow.
|
||||
|
DebugGuard.MustBeLessThan(c, input.Channels.Count, nameof(c)); |
||||
|
DebugGuard.MustBeLessThan(rc, input.Channels.Count, nameof(c)); |
||||
|
|
||||
|
JxlModularChannel inputChannel = input.Channels[c]; |
||||
|
JxlModularChannel inputResidualChannel = input.Channels[rc]; |
||||
|
|
||||
|
if (inputChannel.Height != JxlMath.DivCeil(inputChannel.Height + inputResidualChannel.Height, 2)) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Invalid height"); |
||||
|
} |
||||
|
|
||||
|
if (inputChannel.Width != inputResidualChannel.Width) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Width of the input channel must be equal to the width of the residual channel"); |
||||
|
} |
||||
|
|
||||
|
if (inputResidualChannel.Height == 0) |
||||
|
{ |
||||
|
input.Channels[c].VerticalShift--; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Do not dispose.
|
||||
|
JxlModularChannel outputChannel = new( |
||||
|
configuration, |
||||
|
inputChannel.Width, |
||||
|
inputChannel.Height + inputResidualChannel.Height, |
||||
|
inputChannel.HorizontalShift, |
||||
|
inputChannel.VerticalShift - 1); |
||||
|
|
||||
|
if (inputResidualChannel.Width == 0) |
||||
|
{ |
||||
|
input.Channels[c] = outputChannel; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// The number of columns a single parallel iteration computes
|
||||
|
// is stored here.
|
||||
|
const int colsPerThread = 8; |
||||
|
|
||||
|
_ = Parallel.For(0, JxlMath.DivCeil(inputChannel.Width, colsPerThread), configuration.GetParallelOptions(), idx => |
||||
|
{ |
||||
|
int x0 = idx * colsPerThread; |
||||
|
int x1 = Math.Min((idx + 1) * colsPerThread, inputChannel.Width); |
||||
|
int w = x1 - x0; |
||||
|
|
||||
|
for (int y = 0; y < inputResidualChannel.Height; y++) |
||||
|
{ |
||||
|
int yLsh1 = y << 1; |
||||
|
|
||||
|
Span<int> pResidual = inputResidualChannel.GetRow(y)[x0..]; |
||||
|
Span<int> pAverage = inputChannel.GetRow(y)[x0..]; |
||||
|
Span<int> pNAvg = inputChannel.GetRow(y + 1 < inputChannel.Height ? y + 1 : y)[x0..]; |
||||
|
Span<int> pOut = outputChannel.GetRow(yLsh1)[x0..]; |
||||
|
Span<int> pNOut = outputChannel.GetRow(yLsh1 + 1)[x0..]; |
||||
|
Span<int> pPOut = y > 0 ? outputChannel.GetRow(yLsh1 - 1)[x0..] : pNAvg; |
||||
|
int x = 0; |
||||
|
|
||||
|
for (; x + 7 < w; x += 8) |
||||
|
{ |
||||
|
FastUnsqueeze( |
||||
|
pResidual[x..], |
||||
|
pAverage[x..], |
||||
|
pNAvg[x..], |
||||
|
pPOut[x..], |
||||
|
pOut[x..], |
||||
|
pNOut[x..]); |
||||
|
} |
||||
|
|
||||
|
// Remainder
|
||||
|
for (; x < w; x++) |
||||
|
{ |
||||
|
int avg = pNAvg[x]; |
||||
|
int nextAvg = pNAvg[x]; |
||||
|
int top = pPOut[x]; |
||||
|
int tendency = SmoothTendency(top, avg, nextAvg); |
||||
|
int diffMinusTendency = pResidual[x]; |
||||
|
int diff = diffMinusTendency + tendency; |
||||
|
int output = avg + (diff >> 1); |
||||
|
pOut[x] = output; |
||||
|
pNOut[x] = output - diff; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if ((outputChannel.Height & 1) > 0) |
||||
|
{ |
||||
|
int y = inputChannel.Height - 1; |
||||
|
|
||||
|
Span<int> pAverage = inputChannel.GetRow(y); |
||||
|
Span<int> pOutput = outputChannel.GetRow(y << 1); |
||||
|
|
||||
|
for (int x = 0; x < inputChannel.Width; x++) |
||||
|
{ |
||||
|
pOutput[x] = pAverage[x]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
input.Channels[c] = outputChannel; |
||||
|
} |
||||
|
|
||||
|
public static void InverseSqueeze(Configuration configuration, JxlModularImage input, Span<JxlSqueezeParameters> parameters) |
||||
|
{ |
||||
|
int totalNumberOfChannels = input.Channels.Count; |
||||
|
|
||||
|
for (int i = parameters.Length - 1; i >= 0; i--) |
||||
|
{ |
||||
|
ref JxlSqueezeParameters parameter = ref parameters[i]; |
||||
|
|
||||
|
CheckMetaSqueezeParameters(parameter, totalNumberOfChannels); |
||||
|
|
||||
|
bool horizontal = parameter.Horizontal; |
||||
|
bool inPlace = parameter.InPlace; |
||||
|
int beginC = parameter.BeginC; |
||||
|
int endC = parameter.BeginC + parameter.NumC - 1; |
||||
|
|
||||
|
int offset = inPlace |
||||
|
? endC + 1 |
||||
|
: totalNumberOfChannels + beginC + endC - 1; |
||||
|
|
||||
|
if (beginC < input.MetaChannels) |
||||
|
{ |
||||
|
if (input.MetaChannels <= parameter.NumC) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Not enough meta channels"); |
||||
|
} |
||||
|
|
||||
|
input.MetaChannels -= parameter.NumC; |
||||
|
} |
||||
|
|
||||
|
for (int c = beginC; c <= endC; c++) |
||||
|
{ |
||||
|
int rc = offset + c - beginC; |
||||
|
|
||||
|
if (rc >= totalNumberOfChannels) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Residual channel offset out of bounds"); |
||||
|
} |
||||
|
|
||||
|
JxlModularChannel channelC = input.Channels[c]; // Input channel
|
||||
|
JxlModularChannel channelRC = input.Channels[rc]; // Residual channel
|
||||
|
|
||||
|
if (channelC.Width < channelRC.Width || channelC.Height < channelRC.Height) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Input channel width or height does not match residual channel width/height"); |
||||
|
} |
||||
|
|
||||
|
if (horizontal) |
||||
|
{ |
||||
|
InverseHorizontalSqueeze(configuration, input, c, rc); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
InverseVerticalSqueeze(configuration, input, c, rc); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void DefaultSqueezeParameters(List<JxlSqueezeParameters> squeezeParameters, JxlModularImage image) |
||||
|
{ |
||||
|
int numberOfChannels = image.Channels.Count - image.MetaChannels; |
||||
|
squeezeParameters.Clear(); |
||||
|
|
||||
|
JxlModularChannel numMetaChannelsChannel = image.Channels[image.MetaChannels]; |
||||
|
int w = numMetaChannelsChannel.Width; |
||||
|
int h = numMetaChannelsChannel.Height; |
||||
|
bool wide = w > h; |
||||
|
|
||||
|
JxlModularChannel nextNumMetaChannelsChannel = image.Channels[image.MetaChannels + 1]; |
||||
|
|
||||
|
if (numberOfChannels > 2 && nextNumMetaChannelsChannel.Width == w && nextNumMetaChannelsChannel.Height == h) |
||||
|
{ |
||||
|
JxlSqueezeParameters parameters = new() |
||||
|
{ |
||||
|
Horizontal = true, |
||||
|
InPlace = false, |
||||
|
BeginC = image.MetaChannels + 1, |
||||
|
NumC = 2 |
||||
|
}; |
||||
|
|
||||
|
squeezeParameters.Add(parameters); |
||||
|
parameters.Horizontal = false; |
||||
|
squeezeParameters.Add(parameters); |
||||
|
} |
||||
|
|
||||
|
JxlSqueezeParameters newParameters = new() |
||||
|
{ |
||||
|
BeginC = image.MetaChannels, |
||||
|
NumC = numberOfChannels, |
||||
|
InPlace = true |
||||
|
}; |
||||
|
|
||||
|
if (!wide) |
||||
|
{ |
||||
|
if (h > MaxFirstPreviewSize) |
||||
|
{ |
||||
|
newParameters.Horizontal = false; |
||||
|
squeezeParameters.Add(newParameters); |
||||
|
h = (h + 1) >> 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
while (w > MaxFirstPreviewSize || h > MaxFirstPreviewSize) |
||||
|
{ |
||||
|
if (w > MaxFirstPreviewSize) |
||||
|
{ |
||||
|
newParameters.Horizontal = true; |
||||
|
squeezeParameters.Add(newParameters); |
||||
|
w = (w + 1) >> 1; |
||||
|
} |
||||
|
|
||||
|
if (w > MaxFirstPreviewSize) |
||||
|
{ |
||||
|
newParameters.Horizontal = false; |
||||
|
squeezeParameters.Add(newParameters); |
||||
|
h = (h + 1) >> 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void CheckMetaSqueezeParameters(in JxlSqueezeParameters parameter, int numChannels) |
||||
|
{ |
||||
|
int c1 = parameter.BeginC; |
||||
|
int c2 = parameter.BeginC + parameter.NumC - 1; |
||||
|
|
||||
|
if (c1 < 0 || |
||||
|
c1 >= numChannels || |
||||
|
c2 < 0 || |
||||
|
c2 >= numChannels || |
||||
|
c2 < c1) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Invalid channel range"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void MetaSqueeze(Configuration configuration, JxlModularImage image, List<JxlSqueezeParameters> parameters) |
||||
|
{ |
||||
|
if (parameters.Count == 0) |
||||
|
{ |
||||
|
DefaultSqueezeParameters(parameters, image); |
||||
|
} |
||||
|
|
||||
|
foreach (JxlSqueezeParameters parameter in parameters) |
||||
|
{ |
||||
|
CheckMetaSqueezeParameters(parameter, image.Channels.Count); |
||||
|
|
||||
|
bool horizontal = parameter.Horizontal; |
||||
|
bool inPlace = parameter.InPlace; |
||||
|
int beginC = parameter.BeginC; |
||||
|
int endC = parameter.BeginC + parameter.NumC - 1; |
||||
|
|
||||
|
if (beginC < image.MetaChannels) |
||||
|
{ |
||||
|
if (endC >= image.MetaChannels) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Invalid squeeze: mix of meta and nonmeta channels"); |
||||
|
} |
||||
|
|
||||
|
if (!inPlace) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Invalid squeeze: meta channels require in-place residuals"); |
||||
|
} |
||||
|
|
||||
|
image.MetaChannels += parameter.NumC; |
||||
|
} |
||||
|
|
||||
|
int offset = inPlace |
||||
|
? endC + 1 |
||||
|
: image.Channels.Count; |
||||
|
|
||||
|
for (int c = beginC; c <= endC; c++) |
||||
|
{ |
||||
|
JxlModularChannel channel = image.Channels[c]; |
||||
|
|
||||
|
if (channel.Height > 30 || channel.VerticalShift > 30) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Too many squeezes: shift > 30"); |
||||
|
} |
||||
|
|
||||
|
int w = channel.Width; |
||||
|
int h = channel.Height; |
||||
|
|
||||
|
if ((w & h) == 0) // either w, or h, is 0
|
||||
|
{ |
||||
|
throw new InvalidOperationException("Squeezing empty channel"); |
||||
|
} |
||||
|
|
||||
|
if (horizontal) |
||||
|
{ |
||||
|
channel.Width = (w + 1) >> 1; |
||||
|
|
||||
|
if (channel.HorizontalShift >= 0) |
||||
|
{ |
||||
|
channel.HorizontalShift++; |
||||
|
} |
||||
|
|
||||
|
w -= (w + 1) >> 1; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
channel.HorizontalShift = (h + 1) >> 1; |
||||
|
|
||||
|
if (channel.VerticalShift >= 0) |
||||
|
{ |
||||
|
channel.VerticalShift++; |
||||
|
} |
||||
|
|
||||
|
h -= (h + 1) >> 1; |
||||
|
} |
||||
|
|
||||
|
channel.Shrink(configuration); |
||||
|
|
||||
|
JxlModularChannel placeholder = new(configuration, w, h, channel.HorizontalShift, channel.VerticalShift) |
||||
|
{ |
||||
|
Component = channel.Component |
||||
|
}; |
||||
|
|
||||
|
image.Channels.Insert(offset + (c - beginC), placeholder); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ForwardHorizontalSqueeze(Configuration configuration, JxlModularImage input, int c, int rc) |
||||
|
{ |
||||
|
JxlModularChannel inputChannel = input.Channels[c]; |
||||
|
|
||||
|
// Do not dispose these.
|
||||
|
JxlModularChannel outputChannel = new(configuration, (inputChannel.Width + 1) >> 1, inputChannel.Height, inputChannel.HorizontalShift + 1, inputChannel.VerticalShift); |
||||
|
JxlModularChannel outputChannelResidual = new(configuration, inputChannel.Width - outputChannel.Width, outputChannel.Height, inputChannel.HorizontalShift + 1, inputChannel.VerticalShift); |
||||
|
|
||||
|
outputChannel.Component = inputChannel.Component; |
||||
|
outputChannelResidual.Component = inputChannel.Component; |
||||
|
|
||||
|
for (int y = 0; y < outputChannel.Height; y++) |
||||
|
{ |
||||
|
Span<int> pIn = inputChannel.GetRow(y); |
||||
|
Span<int> pOut = outputChannel.GetRow(y); |
||||
|
Span<int> pRes = outputChannelResidual.GetRow(y); |
||||
|
|
||||
|
for (int x = 0; x < outputChannelResidual.Width; x++) |
||||
|
{ |
||||
|
int x2 = x << 1; // x * 2
|
||||
|
|
||||
|
int a = pIn[x2]; |
||||
|
int b = pIn[x2 + 1]; |
||||
|
int avg = Numerics.Average(a, b); |
||||
|
pOut[x] = avg; |
||||
|
int diff = a - b; |
||||
|
int nextAvg = avg; |
||||
|
|
||||
|
if (x + 1 < outputChannelResidual.Width) |
||||
|
{ |
||||
|
int c2 = pIn[x2 + 2]; // actually C, but 1. variable 'c' already defined 2. names should be camelCase
|
||||
|
int d = pIn[x2 + 3]; |
||||
|
|
||||
|
nextAvg = Numerics.Average(c2, d); |
||||
|
} |
||||
|
else if ((inputChannel.Width & 1) != 0) |
||||
|
{ |
||||
|
nextAvg = pIn[x2 + 2]; |
||||
|
} |
||||
|
|
||||
|
int left = x > 0 ? pIn[x2 - 1] : avg; |
||||
|
int tendency = SmoothTendency(left, avg, nextAvg); |
||||
|
|
||||
|
pRes[x] = diff - tendency; |
||||
|
} |
||||
|
|
||||
|
if ((inputChannel.Width & 1) != 0) |
||||
|
{ |
||||
|
int x = outputChannel.Width - 1; |
||||
|
pOut[x] = pIn[x * 2]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
input.Channels[c] = outputChannel; |
||||
|
input.Channels.Insert(rc, outputChannelResidual); |
||||
|
} |
||||
|
|
||||
|
public static void ForwardVerticalSqueeze(Configuration configuration, JxlModularImage input, int c, int rc) |
||||
|
{ |
||||
|
JxlModularChannel inputChannel = input.Channels[c]; |
||||
|
|
||||
|
// Do not dispose these.
|
||||
|
JxlModularChannel outputChannel = new(configuration, inputChannel.Width, (inputChannel.Height + 1) >> 1, inputChannel.HorizontalShift, inputChannel.VerticalShift + 1); |
||||
|
JxlModularChannel outputResidualChannel = new(configuration, inputChannel.Width, inputChannel.Height - outputChannel.Height, inputChannel.HorizontalShift, inputChannel.VerticalShift + 1); |
||||
|
|
||||
|
outputChannel.Component = inputChannel.Component; |
||||
|
outputResidualChannel.Component = inputChannel.Component; |
||||
|
|
||||
|
int oneRowInput = inputChannel.Plane.PixelsPerRow; |
||||
|
|
||||
|
for (int y = 0; y < outputChannel.Height; y++) |
||||
|
{ |
||||
|
Span<int> pIn = inputChannel.GetRow(y * 2); |
||||
|
Span<int> pOut = outputChannel.GetRow(y); |
||||
|
Span<int> pResidual = outputResidualChannel.GetRow(y); |
||||
|
|
||||
|
for (int x = 0; x < outputChannel.Width; x++) |
||||
|
{ |
||||
|
int a = pIn[x]; |
||||
|
int b = pIn[x + oneRowInput]; |
||||
|
int avg = Numerics.Average(a, b); |
||||
|
pOut[x] = avg; |
||||
|
int diff = a - b; |
||||
|
int nextAvg = avg; |
||||
|
|
||||
|
if (y + 1 < outputResidualChannel.Height) |
||||
|
{ |
||||
|
int c2 = pIn[x + (2 * oneRowInput)]; // actually C, but 1. variable 'c' already defined 2. names should be camelCase
|
||||
|
int d = pIn[x + (3 * oneRowInput)]; |
||||
|
nextAvg = Numerics.Average(c2, d); |
||||
|
} |
||||
|
else if ((inputChannel.Height & 1) != 0) |
||||
|
{ |
||||
|
nextAvg = pIn[x + (2 * oneRowInput)]; |
||||
|
} |
||||
|
|
||||
|
int top = y > 0 ? pIn[x - oneRowInput] : avg; |
||||
|
int tendency = SmoothTendency(top, avg, nextAvg); |
||||
|
|
||||
|
pResidual[x] = diff - tendency; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if ((inputChannel.Height & 1) != 0) |
||||
|
{ |
||||
|
int y = outputChannel.Height - 1; |
||||
|
|
||||
|
Span<int> pIn = inputChannel.GetRow(y * 2); |
||||
|
Span<int> pOut = outputChannel.GetRow(y); |
||||
|
|
||||
|
for (int x = 0; x < outputChannel.Width; x++) |
||||
|
{ |
||||
|
pOut[x] = pIn[x]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
input.Channels[c] = outputChannel; |
||||
|
input.Channels.Insert(rc, outputResidualChannel); |
||||
|
} |
||||
|
|
||||
|
public static void ForwardSqueeze(Configuration configuration, JxlModularImage input, List<JxlSqueezeParameters> parameters) |
||||
|
{ |
||||
|
if (parameters.Count == 0) |
||||
|
{ |
||||
|
DefaultSqueezeParameters(parameters, input); |
||||
|
|
||||
|
if (parameters.Count == 0) |
||||
|
{ |
||||
|
// If there's nothing to do, don't squeeze.
|
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
foreach (JxlSqueezeParameters parameter in parameters) |
||||
|
{ |
||||
|
CheckMetaSqueezeParameters(parameter, input.Channels.Count); |
||||
|
|
||||
|
bool horizontal = parameter.Horizontal; |
||||
|
bool inPlace = parameter.InPlace; |
||||
|
int beginC = parameter.BeginC; |
||||
|
int endC = parameter.BeginC + parameter.NumC - 1; |
||||
|
|
||||
|
int offset = inPlace |
||||
|
? endC + 1 |
||||
|
: input.Channels.Count; |
||||
|
|
||||
|
for (int c = beginC; c <= endC; c++) |
||||
|
{ |
||||
|
if (horizontal) |
||||
|
{ |
||||
|
ForwardHorizontalSqueeze(configuration, input, c, offset + c - beginC); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ForwardVerticalSqueeze(configuration, input, c, offset + c - beginC); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Edge Preserving Filter (type 0) stage
|
||||
|
/// </summary>
|
||||
|
internal sealed class Epf0Stage : RenderPipelineStageBase |
||||
|
{ |
||||
|
private static readonly int[][] SadOffsets = |
||||
|
[ |
||||
|
[-2, 0], [-1, -1], [-1, 0], [-1, 1], [0, -2], [0, -1], |
||||
|
[0, 1], [0, 2], [1, -1], [1, 0], [1, 1], [2, 0] |
||||
|
]; |
||||
|
|
||||
|
private readonly JxlLoopFilter loopFilter; |
||||
|
private readonly JxlImageF sigma; |
||||
|
|
||||
|
public Epf0Stage(JxlLoopFilter loopFilter, JxlImageF sigma, Configuration configuration) : base(configuration) |
||||
|
{ |
||||
|
this.loopFilter = loopFilter; |
||||
|
this.sigma = sigma; |
||||
|
this.Settings = RenderPipelineStageConfiguration.CreateSymmetricBorderOnly(3); |
||||
|
} |
||||
|
|
||||
|
public override string Name => "EPF0"; |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void AddPixel( |
||||
|
int row, |
||||
|
InlineArray7<InlineArray3<Memory<float>>> rows, |
||||
|
int x, |
||||
|
Vector256<float> sad, |
||||
|
Vector256<float> inverseSigma, |
||||
|
ref Vector256<float> xOut, |
||||
|
ref Vector256<float> yOut, |
||||
|
ref Vector256<float> bOut, |
||||
|
ref Vector256<float> wOut) |
||||
|
{ |
||||
|
int rowPlus3 = row + 3; |
||||
|
Vector256<float> cx = Vector256.Create<float>(rows[0][rowPlus3].Span[x..]); |
||||
|
Vector256<float> cy = Vector256.Create<float>(rows[1][rowPlus3].Span[x..]); |
||||
|
Vector256<float> cb = Vector256.Create<float>(rows[2][rowPlus3].Span[x..]); |
||||
|
Vector256<float> weight = EpfUtils.Weight(sad, inverseSigma); |
||||
|
wOut += weight; |
||||
|
xOut += (weight * cx) + xOut; |
||||
|
yOut += (weight * cy) + yOut; |
||||
|
bOut += (weight * cb) + bOut; |
||||
|
} |
||||
|
|
||||
|
public override void ProcessRow(Buffer2D<Memory<float>> inputRows, Buffer2D<Memory<float>> outputRows, int xExtraLeft, int xExtraRight, int width, int xPos, int yPos) |
||||
|
{ |
||||
|
Span<Vector256<float>> sads = stackalloc Vector256<float>[16].Slice(0, 12); |
||||
|
sads.Clear(); |
||||
|
|
||||
|
int xStart = -JxlMath.RoundUpTo(xExtraLeft, Vector256<float>.Count); |
||||
|
int xEnd = width + xExtraRight; |
||||
|
Span<float> rowSigma = this.sigma.GetRow((yPos / JxlFrameDimensions.BlockDimensions) + JxlDecoderCache.SigmaPadding); |
||||
|
|
||||
|
float sm = this.loopFilter.EpfPass0SigmaScale * 1.65f; |
||||
|
float bsm = sm * this.loopFilter.EpfBorderSadMul; |
||||
|
|
||||
|
Span<float> sadMulCenter = [bsm, sm, sm, sm, sm, sm, sm, bsm]; |
||||
|
Span<float> sadMulBorder = [bsm, bsm, bsm, bsm, bsm, bsm, bsm, bsm]; |
||||
|
|
||||
|
int yPosModBlockDim = yPos % JxlFrameDimensions.BlockDimensions; |
||||
|
Span<float> sadMul = yPosModBlockDim is 0 or JxlFrameDimensions.BlockDimensions - 1 |
||||
|
? sadMulBorder |
||||
|
: sadMulCenter; |
||||
|
|
||||
|
InlineArray3<InlineArray7<Memory<float>>> rows = default; |
||||
|
for (int c = 0; c < 3; c++) |
||||
|
{ |
||||
|
for (int i = 0; i < 7; i++) |
||||
|
{ |
||||
|
rows[c][i] = this.GetInputRowMemory(inputRows, c, i - 3); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (int x = xStart; x < xEnd; x += Vector256<float>.Count) |
||||
|
{ |
||||
|
int xPlusXpos = x + xPos; |
||||
|
|
||||
|
int bx = (xPlusXpos + (JxlDecoderCache.SigmaPadding * JxlFrameDimensions.BlockDimensions)) / JxlFrameDimensions.BlockDimensions; |
||||
|
int ix = xPlusXpos % JxlFrameDimensions.BlockDimensions; |
||||
|
|
||||
|
if (rowSigma[bx] < JxlLoopFilter.MinimumSigma) |
||||
|
{ |
||||
|
for (int c = 0; c < 3; c++) |
||||
|
{ |
||||
|
Vector256<float> px = Vector256.Create<float>(rows[c][3].Span[x..]); |
||||
|
px.CopyTo(GetOutputRow(outputRows, c, 0)[x..]); |
||||
|
} |
||||
|
|
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
Vector256<float> vsm = Vector256.Create<float>(sadMul[ix..]); |
||||
|
Vector256<float> inverseSigma = Vector256.Create<float>(rowSigma[bx]) * vsm; |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Used by the EPF render pipeline stage.
|
||||
|
/// </summary>
|
||||
|
internal enum EpfStageType : byte |
||||
|
{ |
||||
|
Zero, |
||||
|
One, |
||||
|
Two |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Utilities for EPF stages.
|
||||
|
/// </summary>
|
||||
|
internal static class EpfUtils |
||||
|
{ |
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<float> Weight(Vector256<float> sad, Vector256<float> inverseSigma) |
||||
|
{ |
||||
|
Vector256<float> v = (sad * inverseSigma) + Vector256<float>.One; |
||||
|
Vector256<float> whereNegative = Vector256.LessThan(v, Vector256<float>.Zero); |
||||
|
Vector256<float> zeroIfNegative = Vector256.ConditionalSelect(whereNegative, Vector256<float>.Zero, whereNegative); |
||||
|
return zeroIfNegative; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Specifies how does a render pipeline stage apply to channels.
|
||||
|
/// </summary>
|
||||
|
internal enum RenderPipelineChannelMode : byte |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Channel is not modified.
|
||||
|
/// </summary>
|
||||
|
Ignored, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Channel is in-place.
|
||||
|
/// </summary>
|
||||
|
InPlace, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Channel is modified and written to a new buffer.
|
||||
|
/// </summary>
|
||||
|
InOut, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Read-only channel.
|
||||
|
/// </summary>
|
||||
|
Input |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Diagnostics; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Base class for a render pipeline stage.
|
||||
|
/// </summary>
|
||||
|
[DebuggerDisplay($"{{{nameof(Name)}}}")] |
||||
|
internal abstract class RenderPipelineStageBase(Configuration configuration) : IDisposable |
||||
|
{ |
||||
|
private const int RenderPipelineXOffset = 32; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the configuration for this render pipeline stage.
|
||||
|
/// </summary>
|
||||
|
public RenderPipelineStageConfiguration Settings { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether this stage is initialized and is therefore
|
||||
|
/// ready to use.
|
||||
|
/// </summary>
|
||||
|
public virtual bool IsInitialized => true; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether, from this stage on, the pipeline will operate
|
||||
|
/// on an image rather than the frame-sized buffer. Only one stage in the pipeline
|
||||
|
/// should return true, and it should implement <see cref="ProcessPaddingRow(Buffer2D{Memory{float}}, int, int, int)"/>.
|
||||
|
/// </summary>
|
||||
|
public virtual bool SwitchToImageDimensions => false; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a friendly name representing this stage.
|
||||
|
/// </summary>
|
||||
|
public virtual string Name => "(invalid pipeline stage)"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// If any unmanaged or pooled memory is present by the derived stage, releases
|
||||
|
/// memory used by that.
|
||||
|
/// </summary>
|
||||
|
public virtual void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public virtual void ProcessRow( |
||||
|
Buffer2D<Memory<float>> inputRows, |
||||
|
Buffer2D<Memory<float>> outputRows, |
||||
|
int xExtraLeft, |
||||
|
int xExtraRight, |
||||
|
int width, |
||||
|
int xPos, |
||||
|
int yPos) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Represents how each channel will be processed.
|
||||
|
/// </summary>
|
||||
|
/// <param name="channel">Desired channel.</param>
|
||||
|
/// <returns>Mode specifying how the specified channel will be processed.</returns>
|
||||
|
public virtual RenderPipelineChannelMode GetChannelMode(int channel) |
||||
|
=> RenderPipelineChannelMode.Ignored; |
||||
|
|
||||
|
public virtual void SetInputSizes(Span<Size> inputSizes) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public Span<float> GetInputRow(Buffer2D<Memory<float>> inputRows, int c, int offset) |
||||
|
=> inputRows[c, this.Settings.BorderY + offset].Span[RenderPipelineXOffset..]; |
||||
|
|
||||
|
public Memory<float> GetInputRowMemory(Buffer2D<Memory<float>> inputRows, int c, int offset) |
||||
|
=> inputRows[c, this.Settings.BorderY + offset][RenderPipelineXOffset..]; |
||||
|
|
||||
|
public static Span<float> GetOutputRow(Buffer2D<Memory<float>> outputRows, int c, int offset) |
||||
|
=> outputRows[c, offset].Span[RenderPipelineXOffset..]; |
||||
|
|
||||
|
public virtual void GetImageDimensions(out int width, out int height, out Point frameOrigin) |
||||
|
{ |
||||
|
width = 0; |
||||
|
height = 0; |
||||
|
frameOrigin = default; |
||||
|
} |
||||
|
|
||||
|
public virtual void ProcessPaddingRow(Buffer2D<Memory<float>> outputRows, int width, int xPos, int yPos) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected Configuration GetConfiguration() => configuration; |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.RenderPipeline; |
||||
|
|
||||
|
internal record struct RenderPipelineStageConfiguration(int BorderX, int BorderY, int ShiftX, int ShiftY) |
||||
|
{ |
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static RenderPipelineStageConfiguration CreateShiftX(int shift, int border) => new(border, 0, shift, 0); |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static RenderPipelineStageConfiguration CreateShiftY(int shift, int border) => new(0, border, 0, shift); |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static RenderPipelineStageConfiguration CreateSymmetric(int shift, int border) => new(border, border, shift, shift); |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static RenderPipelineStageConfiguration CreateSymmetricBorderOnly(int border) => CreateSymmetric(shift: 0, border); |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Common; |
||||
|
|
||||
|
public class Vector256UtilitiesTests |
||||
|
{ |
||||
|
[Theory] |
||||
|
[InlineData(new int[] { 4, 5, 6, 7, 8, 9, 10, 11 }, new int[] { 1, 2, 3, 4, 0, -1, -2, -3 }, new int[] { 4, 1, 5, 2, 6, 3, 7, 4 })] |
||||
|
public void TestVector256InterleaveLower(int[] a, int[] b, int[] expected) |
||||
|
{ |
||||
|
Vector256<int> v256a = Vector256.Create(a); |
||||
|
Vector256<int> v256b = Vector256.Create(b); |
||||
|
|
||||
|
Vector256<int> v256 = Vector256_.InterleaveLower(v256a, v256b); |
||||
|
|
||||
|
int[] result = new int[Vector256<int>.Count]; |
||||
|
v256.CopyTo(result); |
||||
|
|
||||
|
bool isEqual = expected.SequenceEqual(result); |
||||
|
if (!isEqual) |
||||
|
{ |
||||
|
Assert.Fail($"Lower shuffle failed.\n\nExpected: [{string.Join(", ", expected)}]\nActual: [{string.Join(", ", result)}]"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(new int[] { 4, 5, 6, 7, 8, 9, 10, 11 }, new int[] { 1, 2, 3, 4, 0, -1, -2, -3 }, new int[] { 8, 0, 9, -1, 10, -2, 11, -3 })] |
||||
|
public void TestVector256InterleaveUpper(int[] a, int[] b, int[] expected) |
||||
|
{ |
||||
|
Vector256<int> v256a = Vector256.Create(a); |
||||
|
Vector256<int> v256b = Vector256.Create(b); |
||||
|
|
||||
|
Vector256<int> v256 = Vector256_.InterleaveUpper(v256a, v256b); |
||||
|
|
||||
|
int[] result = new int[Vector256<int>.Count]; |
||||
|
v256.CopyTo(result); |
||||
|
|
||||
|
bool isEqual = expected.SequenceEqual(result); |
||||
|
if (!isEqual) |
||||
|
{ |
||||
|
Assert.Fail($"Lower shuffle failed.\n\nExpected: [{string.Join(", ", expected)}]\nActual: [{string.Join(", ", result)}]"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue