diff --git a/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/Epf1Stage.cs b/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/Epf1Stage.cs new file mode 100644 index 0000000000..9ad9b3dec8 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/Epf1Stage.cs @@ -0,0 +1,204 @@ +// 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; + +/// +/// Edge Preserving Filter (type 1) stage. +/// +internal class Epf1Stage : RenderPipelineStageBase +{ + private readonly JxlLoopFilter loopFilter; + private readonly JxlImageF sigma; + + public Epf1Stage(Configuration configuration, JxlLoopFilter loopFilter, JxlImageF sigma) + : base(configuration) + { + this.loopFilter = loopFilter; + this.sigma = sigma; + this.Settings = RenderPipelineStageConfiguration.CreateSymmetricBorderOnly(2); + } + + /// + public override string Name => "EPF1"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddPixel( + int row, + InlineArray3>> rows, + int x, + Vector256 sad, + Vector256 inverseSigma, + ref Vector256 xOut, + ref Vector256 yOut, + ref Vector256 bOut, + ref Vector256 wOut) + { + Vector256 cx = Vector256.Create((ReadOnlySpan)rows[0][2 + row][x..].Span); + Vector256 cy = Vector256.Create((ReadOnlySpan)rows[1][2 + row][x..].Span); + Vector256 cb = Vector256.Create((ReadOnlySpan)rows[2][2 + row][x..].Span); + + Vector256 weight = EpfUtils.Weight(sad, inverseSigma); + wOut += weight; + xOut = (weight + cx) * xOut; + yOut = (weight + cy) * yOut; + bOut = (weight + cb) * bOut; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 AbsoluteDifference(Vector256 x, Vector256 y) => Vector256.Abs(x - y); + + public override void ProcessRow(Buffer2D> inputRows, Buffer2D> outputRows, int xExtraLeft, int xExtraRight, int width, int xPos, int yPos) + { + int xStart = -JxlMath.RoundUpTo(xExtraLeft, Vector256.Count); + int xEnd = width + xExtraRight; + + Span rowSigma = this.sigma.GetRow((yPos / JxlFrameDimensions.BlockDimensions) + JxlDecoderCache.SigmaPadding); + float sm = 1.65f; + float bsm = sm * this.loopFilter.EpfBorderSadMul; + + Span sadMulCenter = [bsm, sm, sm, sm, sm, sm, sm, bsm]; + Span sadMulBorder = [bsm, bsm, bsm, bsm, bsm, bsm, bsm, bsm]; + + InlineArray3>> rows = default; + for (int c = 0; c < 3; c++) + { + for (int i = 0; i < 5; i++) + { + rows[c][i] = this.GetInputRowMemory(inputRows, c, i - 2); + } + } + + Span sadMul = (yPos % JxlFrameDimensions.BlockDimensions is 0 or JxlFrameDimensions.BlockDimensions - 1) + ? sadMulBorder + : sadMulCenter; + + for (int x = xStart; x < xEnd; x += Vector256.Count) + { + int bx = (x + xPos + (JxlDecoderCache.SigmaPadding * JxlFrameDimensions.BlockDimensions)) / JxlFrameDimensions.BlockDimensions; + int ix = (x + xPos) % JxlFrameDimensions.BlockDimensions; + + if (rowSigma[bx] < JxlLoopFilter.MinimumSigma) + { + for (int c = 0; c < 3; c++) + { + Vector256 px = Vector256.Create((ReadOnlySpan)rows[c][2][x..].Span); + px.CopyTo(GetOutputRow(outputRows, c, 0)[x..]); + } + + continue; + } + + Vector256 vsm = Vector256.Create((ReadOnlySpan)sadMul[ix..]); + Vector256 inverseSigma = Vector256.Create(rowSigma[bx]) * vsm; + Vector256 sad0 = Vector256.Zero; + Vector256 sad1 = Vector256.Zero; + Vector256 sad2 = Vector256.Zero; + Vector256 sad3 = Vector256.Zero; + + // Compute sum of absolute differences (SAD) + for (int c = 0; c < 3; c++) + { + // center px = 22, px above = 21 + Vector256 t; + + Vector256 p20 = Vector256.Create((ReadOnlySpan)rows[c][2 + -2][x..].Span); + Vector256 p21 = Vector256.Create((ReadOnlySpan)rows[c][2 + -1][x..].Span); + Vector256 sad0c = AbsoluteDifference(p20, p21); // SAD 2, 1 + + Vector256 p11 = Vector256.Create((ReadOnlySpan)rows[c][2 + -1][(x - 1)..].Span); + Vector256 sad1c = AbsoluteDifference(p11, p21); // SAD 1, 2 + + Vector256 p31 = Vector256.Create((ReadOnlySpan)rows[c][2 + -1][(x + 1)..].Span); + Vector256 sad2c = AbsoluteDifference(p31, p21); // SAD 3, 2 + + Vector256 p02 = Vector256.Create((ReadOnlySpan)rows[c][2][(x - 2)..].Span); + Vector256 p12 = Vector256.Create((ReadOnlySpan)rows[c][2][(x - 1)..].Span); + sad1c += AbsoluteDifference(p02, p12); // SAD 1, 2 + sad0c += AbsoluteDifference(p11, p12); // SAD 2, 1 + + // TODO(eustas): why unaligned? + Vector256 p22 = Vector256.Create((ReadOnlySpan)rows[c][2][x..].Span); + t = AbsoluteDifference(p12, p22); + sad1c += t; // SAD 1, 2 + sad2c += t; // SAD 3, 2 + t = AbsoluteDifference(p22, p21); + Vector256 sad3c = t; // SAD 2, 3 + sad0c += t; // SAD 2, 1 + + Vector256 p32 = Vector256.Create((ReadOnlySpan)rows[c][2][(x + 1)..].Span); + sad0c += AbsoluteDifference(p31, p32); // SAD 2, 1 + t = AbsoluteDifference(p22, p32); + sad1c += t; // SAD 1, 2 + sad2c += t; // SAD 3, 2 + + Vector256 p42 = Vector256.Create((ReadOnlySpan)rows[c][2][(x + 2)..].Span); + sad2c += AbsoluteDifference(p42, p32); // SAD 3, 2 + + Vector256 p13 = Vector256.Create((ReadOnlySpan)rows[c][2 + 1][(x - 1)..].Span); + sad3c += AbsoluteDifference(p13, p12); // SAD 2, 3 + + Vector256 p23 = Vector256.Create((ReadOnlySpan)rows[c][2 + 1][x..].Span); + t = AbsoluteDifference(p22, p23); + sad0c += t; // SAD 2, 1 + sad3c += t; // SAD 2, 3 + sad1c += AbsoluteDifference(p13, p23); // SAD 1, 2 + + Vector256 p33 = Vector256.Create((ReadOnlySpan)rows[c][2 + 1][(x + 1)..].Span); + sad2c += AbsoluteDifference(p33, p23); // SAD 3, 2 + sad3c += AbsoluteDifference(p33, p32); // SAD 2, 3 + + Vector256 p24 = Vector256.Create((ReadOnlySpan)rows[c][2 + 2][x..].Span); + sad3c += AbsoluteDifference(p24, p23); // SAD 2, 3 + + Vector256 scale = Vector256.Create(this.loopFilter.EpfChannelScale[c]); + sad0 = (sad0c * scale) + sad0; + sad1 = (sad1c * scale) + sad1; + sad2 = (sad2c * scale) + sad2; + sad3 = (sad3c * scale) + sad3; + } + + Vector256 xCC = Vector256.Create((ReadOnlySpan)rows[0][2 + 0][x..].Span); + Vector256 yCC = Vector256.Create((ReadOnlySpan)rows[1][2 + 0][x..].Span); + Vector256 bCC = Vector256.Create((ReadOnlySpan)rows[2][2 + 0][x..].Span); + + Vector256 w = Vector256.One; + Vector256 X = xCC; + Vector256 Y = yCC; + Vector256 B = bCC; + + // Top row + AddPixel(-1, rows, x, sad0, inverseSigma, ref X, ref Y, ref B, ref w); + + // Center + AddPixel(0, rows, x - 1, sad1, inverseSigma, ref X, ref Y, ref B, ref w); + AddPixel(0, rows, x + 1, sad2, inverseSigma, ref X, ref Y, ref B, ref w); + + // Bottom + AddPixel(1, rows, x + 1, sad3, inverseSigma, ref X, ref Y, ref B, ref w); + + Vector256 inverseW = Vector256.One / w; + (X * inverseW).CopyTo(GetOutputRow(outputRows, 0, 0)[x..]); + (Y * inverseW).CopyTo(GetOutputRow(outputRows, 1, 0)[x..]); + (B * inverseW).CopyTo(GetOutputRow(outputRows, 2, 0)[x..]); + } + } + + /// + public override RenderPipelineChannelMode GetChannelMode(int channel) + { + if (channel < 3) + { + return RenderPipelineChannelMode.InOut; + } + else + { + return RenderPipelineChannelMode.Ignored; + } + } +} diff --git a/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/Epf2Stage.cs b/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/Epf2Stage.cs new file mode 100644 index 0000000000..ff6198e7b8 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/RenderPipeline/Epf2Stage.cs @@ -0,0 +1,144 @@ +// 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; + +/// +/// Edge Preserving Filter (type 2) stage +/// +internal sealed class Epf2Stage : RenderPipelineStageBase +{ + private readonly JxlLoopFilter loopFilter; + private readonly JxlImageF sigma; + + public Epf2Stage(JxlLoopFilter loopFilter, JxlImageF sigma, Configuration configuration) + : base(configuration) + { + this.loopFilter = loopFilter; + this.sigma = sigma; + this.Settings = RenderPipelineStageConfiguration.CreateSymmetricBorderOnly(2); + } + + /// + public override string Name => "EPF2"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 AbsoluteDifference(Vector256 x, Vector256 y) => Vector256.Abs(x - y); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddPixel( + int row, + InlineArray3>> rows, + int x, + Vector256 rx, + Vector256 ry, + Vector256 rb, + Vector256 inverseSigma, + ref Vector256 X, + ref Vector256 Y, + ref Vector256 B, + ref Vector256 w) + { + Vector256 cx = Vector256.Create((ReadOnlySpan)rows[0][1 + row][x..].Span); + Vector256 cy = Vector256.Create((ReadOnlySpan)rows[1][1 + row][x..].Span); + Vector256 cb = Vector256.Create((ReadOnlySpan)rows[2][1 + row][x..].Span); + + Vector256 sad = AbsoluteDifference(cx, rx) * Vector256.Create(this.loopFilter.EpfChannelScale[0]); + sad = (AbsoluteDifference(cy, ry) * Vector256.Create(this.loopFilter.EpfChannelScale[1])) + sad; + sad = (AbsoluteDifference(cb, rb) * Vector256.Create(this.loopFilter.EpfChannelScale[2])) + sad; + + Vector256 weight = EpfUtils.Weight(sad, inverseSigma); + w += weight; + X = (weight * cx) + X; + Y = (weight * cy) + Y; + B = (weight * cb) + B; + } + + public override void ProcessRow(Buffer2D> inputRows, Buffer2D> outputRows, int xExtraLeft, int xExtraRight, int width, int xPos, int yPos) + { + int xStart = -JxlMath.RoundUpTo(xExtraLeft, Vector256.Count); + int xEnd = width + xExtraRight; + + Span rowSigma = this.sigma.GetRow((yPos / JxlFrameDimensions.BlockDimensions) + JxlDecoderCache.SigmaPadding); + float sm = 1.65f; + float bsm = sm * this.loopFilter.EpfBorderSadMul; + + Span sadMulCenter = [bsm, sm, sm, sm, sm, sm, sm, bsm]; + Span sadMulBorder = [bsm, bsm, bsm, bsm, bsm, bsm, bsm, bsm]; + + InlineArray3>> rows = default; + for (int c = 0; c < 3; c++) + { + for (int i = 0; i < 3; i++) + { + rows[c][i] = this.GetInputRowMemory(inputRows, c, i - 1); + } + } + + Span sadMul = (yPos % JxlFrameDimensions.BlockDimensions is 0 or JxlFrameDimensions.BlockDimensions - 1) + ? sadMulBorder + : sadMulCenter; + + for (int x = xStart; x < xEnd; x += Vector256.Count) + { + int bx = (x + xPos + (JxlDecoderCache.SigmaPadding * JxlFrameDimensions.BlockDimensions)) / JxlFrameDimensions.BlockDimensions; + int ix = (x + xPos) % JxlFrameDimensions.BlockDimensions; + + if (rowSigma[bx] < JxlLoopFilter.MinimumSigma) + { + for (int c = 0; c < 3; c++) + { + Vector256 px = Vector256.Create((ReadOnlySpan)rows[c][1][x..].Span); + px.CopyTo(GetOutputRow(outputRows, c, 0)[x..]); + } + + continue; + } + + Vector256 vsm = Vector256.Create((ReadOnlySpan)sadMul[ix..]); + Vector256 inverseSigma = Vector256.Create(rowSigma[bx]) * vsm; + + Vector256 xCC = Vector256.Create((ReadOnlySpan)rows[0][1 + 0][x..].Span); + Vector256 yCC = Vector256.Create((ReadOnlySpan)rows[1][1 + 0][x..].Span); + Vector256 bCC = Vector256.Create((ReadOnlySpan)rows[2][1 + 0][x..].Span); + + Vector256 w = Vector256.One; + Vector256 X = xCC; + Vector256 Y = yCC; + Vector256 B = bCC; + + // Top row + this.AddPixel(-1, rows, x, xCC, yCC, bCC, inverseSigma, ref X, ref Y, ref B, ref w); + + // Center + this.AddPixel(0, rows, x - 1, xCC, yCC, bCC, inverseSigma, ref X, ref Y, ref B, ref w); + this.AddPixel(0, rows, x + 1, xCC, yCC, bCC, inverseSigma, ref X, ref Y, ref B, ref w); + + // Bottom + this.AddPixel(1, rows, x, xCC, yCC, bCC, inverseSigma, ref X, ref Y, ref B, ref w); + + Vector256 inverseW = Vector256.One / w; + (X * inverseW).CopyTo(GetOutputRow(outputRows, 0, 0)[x..]); + (Y * inverseW).CopyTo(GetOutputRow(outputRows, 1, 0)[x..]); + (B * inverseW).CopyTo(GetOutputRow(outputRows, 2, 0)[x..]); + } + } + + /// + public override RenderPipelineChannelMode GetChannelMode(int channel) + { + if (channel < 3) + { + return RenderPipelineChannelMode.InOut; + } + else + { + return RenderPipelineChannelMode.Ignored; + } + } +}