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