mirror of https://github.com/SixLabors/ImageSharp
18 changed files with 1094 additions and 39 deletions
@ -0,0 +1,79 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal class Av1DirectionalZone1Predictor |
||||
|
{ |
||||
|
private readonly nuint blockWidth; |
||||
|
private readonly nuint blockHeight; |
||||
|
|
||||
|
public Av1DirectionalZone1Predictor(Size blockSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)blockSize.Width; |
||||
|
this.blockHeight = (nuint)blockSize.Height; |
||||
|
} |
||||
|
|
||||
|
public Av1DirectionalZone1Predictor(Av1TransformSize transformSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)transformSize.GetWidth(); |
||||
|
this.blockHeight = (nuint)transformSize.GetHeight(); |
||||
|
} |
||||
|
|
||||
|
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, bool upsampleAbove, int dx) |
||||
|
=> new Av1DirectionalZone1Predictor(transformSize).PredictScalar(destination, stride, above, upsampleAbove, dx); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SVT: svt_av1_dr_prediction_z1_c
|
||||
|
/// </summary>
|
||||
|
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, bool upsample, int dx) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
||||
|
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
||||
|
int upsampleAbove = upsample ? 1 : 0; |
||||
|
ref byte aboveRef = ref above[0]; |
||||
|
ref byte destinationRef = ref destination[0]; |
||||
|
int maxBasisX = (((int)this.blockWidth + (int)this.blockHeight) - 1) << upsampleAbove; |
||||
|
int fractionBitCount = 6 - upsampleAbove; |
||||
|
int basisIncrement = 1 << upsampleAbove; |
||||
|
int x = dx; |
||||
|
for (nuint r = 0; r < this.blockHeight; ++r) |
||||
|
{ |
||||
|
int basis = x >> fractionBitCount, shift = ((x << upsampleAbove) & 0x3F) >> 1; |
||||
|
|
||||
|
if (basis >= maxBasisX) |
||||
|
{ |
||||
|
for (nuint i = r; i < this.blockHeight; ++i) |
||||
|
{ |
||||
|
Unsafe.InitBlock(ref destinationRef, Unsafe.Add(ref aboveRef, maxBasisX), (uint)this.blockWidth); |
||||
|
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (nuint c = 0; c < this.blockWidth; ++c) |
||||
|
{ |
||||
|
if (basis < maxBasisX) |
||||
|
{ |
||||
|
int val; |
||||
|
val = (Unsafe.Add(ref aboveRef, basis) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basis + 1) * shift); |
||||
|
val = Av1Math.RoundPowerOf2(val, 5); |
||||
|
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.Clamp(val, 0, 255); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, c) = Unsafe.Add(ref aboveRef, maxBasisX); |
||||
|
} |
||||
|
|
||||
|
basis += basisIncrement; |
||||
|
} |
||||
|
|
||||
|
x += dx; |
||||
|
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Security.Cryptography; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal class Av1DirectionalZone2Predictor |
||||
|
{ |
||||
|
private readonly nuint blockWidth; |
||||
|
private readonly nuint blockHeight; |
||||
|
|
||||
|
public Av1DirectionalZone2Predictor(Size blockSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)blockSize.Width; |
||||
|
this.blockHeight = (nuint)blockSize.Height; |
||||
|
} |
||||
|
|
||||
|
public Av1DirectionalZone2Predictor(Av1TransformSize transformSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)transformSize.GetWidth(); |
||||
|
this.blockHeight = (nuint)transformSize.GetHeight(); |
||||
|
} |
||||
|
|
||||
|
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy) |
||||
|
=> new Av1DirectionalZone2Predictor(transformSize).PredictScalar(destination, stride, above, left, upsampleAbove, upsampleAbove, dx, dy); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SVT: svt_av1_dr_prediction_z1_c
|
||||
|
/// </summary>
|
||||
|
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left, bool doUpsampleAbove, bool doUpsampleLeft, int dx, int dy) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
||||
|
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
||||
|
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
||||
|
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
||||
|
int upsampleAbove = doUpsampleAbove ? 1 : 0; |
||||
|
int upsampleLeft = doUpsampleLeft ? 1 : 0; |
||||
|
ref byte aboveRef = ref above[0]; |
||||
|
ref byte leftRef = ref left[0]; |
||||
|
ref byte destinationRef = ref destination[0]; |
||||
|
int minBasisX = -(1 << upsampleAbove); |
||||
|
int fractionBitCountX = 6 - upsampleAbove; |
||||
|
int fractionBitCountY = 6 - upsampleLeft; |
||||
|
int basisIncrementX = 1 << upsampleAbove; |
||||
|
int x = -dx; |
||||
|
for (nuint r = 0; r < this.blockHeight; ++r) |
||||
|
{ |
||||
|
int val; |
||||
|
int base1 = x >> fractionBitCountX; |
||||
|
int y = ((int)r << 6) - dy; |
||||
|
for (nuint c = 0; c < this.blockWidth; ++c, base1 += basisIncrementX, y -= dy) |
||||
|
{ |
||||
|
if (base1 >= minBasisX) |
||||
|
{ |
||||
|
int shift1 = ((x * (1 << upsampleAbove)) & 0x3F) >> 1; |
||||
|
val = (Unsafe.Add(ref aboveRef, base1) * (32 - shift1)) + (Unsafe.Add(ref aboveRef, base1 + 1) * shift1); |
||||
|
val = Av1Math.RoundPowerOf2(val, 5); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
int base2 = y >> fractionBitCountY; |
||||
|
Guard.MustBeGreaterThanOrEqualTo(base2, -(1 << upsampleLeft), nameof(base2)); |
||||
|
int shift2 = ((y * (1 << upsampleLeft)) & 0x3F) >> 1; |
||||
|
val = (Unsafe.Add(ref leftRef, base2) * (32 - shift2)) + (Unsafe.Add(ref leftRef, base2 + 1) * shift2); |
||||
|
val = Av1Math.RoundPowerOf2(val, 5); |
||||
|
} |
||||
|
|
||||
|
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.Clamp(val, 0, 255); |
||||
|
} |
||||
|
|
||||
|
x -= dx; |
||||
|
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal class Av1DirectionalZone3Predictor |
||||
|
{ |
||||
|
private readonly nuint blockWidth; |
||||
|
private readonly nuint blockHeight; |
||||
|
|
||||
|
public Av1DirectionalZone3Predictor(Size blockSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)blockSize.Width; |
||||
|
this.blockHeight = (nuint)blockSize.Height; |
||||
|
} |
||||
|
|
||||
|
public Av1DirectionalZone3Predictor(Av1TransformSize transformSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)transformSize.GetWidth(); |
||||
|
this.blockHeight = (nuint)transformSize.GetHeight(); |
||||
|
} |
||||
|
|
||||
|
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> left, bool upsampleAbove, int dx, int dy) |
||||
|
=> new Av1DirectionalZone3Predictor(transformSize).PredictScalar(destination, stride, left, upsampleAbove, dx, dy); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SVT: svt_av1_dr_prediction_z3_c
|
||||
|
/// </summary>
|
||||
|
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> left, bool upsample, int dx, int dy) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
||||
|
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
||||
|
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
||||
|
int upsampleLeft = upsample ? 1 : 0; |
||||
|
ref byte leftRef = ref left[0]; |
||||
|
ref byte destinationRef = ref destination[0]; |
||||
|
Guard.IsTrue(dx == 1, nameof(dx), "Dx expected to be always equal to 1 for directional Zone 3 prediction."); |
||||
|
Guard.MustBeGreaterThan(dy, 0, nameof(dy)); |
||||
|
|
||||
|
int maxBasisY = ((int)this.blockWidth + (int)this.blockHeight - 1) << upsampleLeft; |
||||
|
int fractionBitCount = 6 - upsampleLeft; |
||||
|
int basisIncrement = 1 << upsampleLeft; |
||||
|
int y = dy; |
||||
|
for (nuint c = 0; c < this.blockWidth; ++c) |
||||
|
{ |
||||
|
int basis = y >> fractionBitCount; |
||||
|
int shift = ((y << upsampleLeft) & 0x3F) >> 1; |
||||
|
|
||||
|
for (nuint r = 0; r < this.blockHeight; ++r) |
||||
|
{ |
||||
|
if (basis < maxBasisY) |
||||
|
{ |
||||
|
int val; |
||||
|
val = (Unsafe.Add(ref leftRef, basis) * (32 - shift)) + (Unsafe.Add(ref leftRef, basis + 1) * shift); |
||||
|
val = Av1Math.RoundPowerOf2(val, 5); |
||||
|
Unsafe.Add(ref destinationRef, (r * stride) + c) = (byte)Av1Math.Clamp(val, 0, 255); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
for (; r < this.blockHeight; ++r) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, (r * stride) + c) = left[maxBasisY]; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
basis += basisIncrement; |
||||
|
} |
||||
|
|
||||
|
y += dy; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal class Av1HorizontalPredictor : IAv1Predictor |
||||
|
{ |
||||
|
private readonly nuint blockWidth; |
||||
|
private readonly nuint blockHeight; |
||||
|
|
||||
|
public Av1HorizontalPredictor(Size blockSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)blockSize.Width; |
||||
|
this.blockHeight = (nuint)blockSize.Height; |
||||
|
} |
||||
|
|
||||
|
public Av1HorizontalPredictor(Av1TransformSize transformSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)transformSize.GetWidth(); |
||||
|
this.blockHeight = (nuint)transformSize.GetHeight(); |
||||
|
} |
||||
|
|
||||
|
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
=> new Av1HorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SVT: highbd_h_predictor
|
||||
|
/// </summary>
|
||||
|
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
||||
|
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
||||
|
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
||||
|
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
||||
|
ref byte leftRef = ref left[0]; |
||||
|
ref byte destinationRef = ref destination[0]; |
||||
|
uint width = (uint)this.blockWidth; |
||||
|
for (nuint r = 0; r < this.blockHeight; ++r) |
||||
|
{ |
||||
|
Unsafe.InitBlock(ref destinationRef, Unsafe.Add(ref leftRef, r), width); |
||||
|
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal class Av1PaethPredictor : IAv1Predictor |
||||
|
{ |
||||
|
private readonly uint blockWidth; |
||||
|
private readonly uint blockHeight; |
||||
|
|
||||
|
public Av1PaethPredictor(Size blockSize) |
||||
|
{ |
||||
|
this.blockWidth = (uint)blockSize.Width; |
||||
|
this.blockHeight = (uint)blockSize.Height; |
||||
|
} |
||||
|
|
||||
|
public Av1PaethPredictor(Av1TransformSize transformSize) |
||||
|
{ |
||||
|
this.blockWidth = (uint)transformSize.GetWidth(); |
||||
|
this.blockHeight = (uint)transformSize.GetHeight(); |
||||
|
} |
||||
|
|
||||
|
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
=> new Av1DcPredictor(transformSize).PredictScalar(destination, stride, above, left); |
||||
|
|
||||
|
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
||||
|
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
||||
|
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
||||
|
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
||||
|
ref byte leftRef = ref left[0]; |
||||
|
ref byte aboveRef = ref above[0]; |
||||
|
int yTopLeft = above[-1]; |
||||
|
ref byte destinationRef = ref destination[0]; |
||||
|
for (nuint r = 0; r < this.blockHeight; r++) |
||||
|
{ |
||||
|
for (nuint c = 0; c < this.blockWidth; c++) |
||||
|
{ |
||||
|
destinationRef = PredictSingle(Unsafe.Add(ref leftRef, r), Unsafe.Add(ref aboveRef, c), yTopLeft); |
||||
|
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static byte PredictSingle(byte left, byte top, int topLeft) |
||||
|
{ |
||||
|
int basis = top + left - topLeft; |
||||
|
int pLeft = Av1Math.AbsoluteDifference(basis, left); |
||||
|
int pTop = Av1Math.AbsoluteDifference(basis, top); |
||||
|
int pTopLeft = Av1Math.AbsoluteDifference(basis, topLeft); |
||||
|
|
||||
|
// Return nearest to base of left, top and top_left.
|
||||
|
return (byte)((pLeft <= pTop && pLeft <= pTopLeft) ? left : (pTop <= pTopLeft) ? top : topLeft); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal class Av1SmoothHorizontalPredictor : IAv1Predictor |
||||
|
{ |
||||
|
private readonly nuint blockWidth; |
||||
|
private readonly nuint blockHeight; |
||||
|
|
||||
|
public Av1SmoothHorizontalPredictor(Size blockSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)blockSize.Width; |
||||
|
this.blockHeight = (nuint)blockSize.Height; |
||||
|
} |
||||
|
|
||||
|
public Av1SmoothHorizontalPredictor(Av1TransformSize transformSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)transformSize.GetWidth(); |
||||
|
this.blockHeight = (nuint)transformSize.GetHeight(); |
||||
|
} |
||||
|
|
||||
|
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
=> new Av1SmoothHorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SVT: highbd_smooth_h_predictor
|
||||
|
/// </summary>
|
||||
|
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
||||
|
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
||||
|
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
||||
|
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
||||
|
ref byte leftRef = ref left[0]; |
||||
|
ref byte aboveRef = ref above[0]; |
||||
|
ref byte destinationRef = ref destination[0]; |
||||
|
int rightPrediction = Unsafe.Add(ref aboveRef, this.blockWidth - 1); // estimated by top-right pixel
|
||||
|
ref int weights = ref Av1SmoothPredictor.Weights[(int)this.blockWidth]; |
||||
|
|
||||
|
// scale = 2 * 2^sm_weight_log2_scale
|
||||
|
int log2Scale = 1 + Av1SmoothPredictor.WeightLog2Scale; |
||||
|
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale; |
||||
|
|
||||
|
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
|
||||
|
for (nuint r = 0; r < this.blockHeight; ++r) |
||||
|
{ |
||||
|
for (nuint c = 0; c < this.blockWidth; ++c) |
||||
|
{ |
||||
|
int columnWeight = Unsafe.Add(ref weights, c); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(scale, columnWeight, nameof(scale)); |
||||
|
int thisPredition = Unsafe.Add(ref leftRef, r) * columnWeight; |
||||
|
thisPredition += rightPrediction * (scale - columnWeight); |
||||
|
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale); |
||||
|
} |
||||
|
|
||||
|
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal class Av1SmoothPredictor : IAv1Predictor |
||||
|
{ |
||||
|
// Weights are quadratic from '1' to '1 / BlockSize', scaled by
|
||||
|
// 2^sm_weight_log2_scale.
|
||||
|
internal static readonly int WeightLog2Scale = 8; |
||||
|
|
||||
|
internal static readonly int[] Weights = [ |
||||
|
|
||||
|
// Unused, because we always offset by bs, which is at least 2.
|
||||
|
0, 0, |
||||
|
|
||||
|
// bs = 2
|
||||
|
255, 128, |
||||
|
|
||||
|
// bs = 4
|
||||
|
255, 149, 85, 64, |
||||
|
|
||||
|
// bs = 8
|
||||
|
255, 197, 146, 105, 73, 50, 37, 32, |
||||
|
|
||||
|
// bs = 16
|
||||
|
255, 225, 196, 170, 145, 123, 102, 84, 68, 54, 43, 33, 26, 20, 17, 16, |
||||
|
|
||||
|
// bs = 32
|
||||
|
255, 240, 225, 210, 196, 182, 169, 157, 145, 133, 122, 111, 101, 92, 83, 74, |
||||
|
66, 59, 52, 45, 39, 34, 29, 25, 21, 17, 14, 12, 10, 9, 8, 8, |
||||
|
|
||||
|
// bs = 64
|
||||
|
255, 248, 240, 233, 225, 218, 210, 203, 196, 189, 182, 176, 169, 163, 156, |
||||
|
150, 144, 138, 133, 127, 121, 116, 111, 106, 101, 96, 91, 86, 82, 77, 73, 69, |
||||
|
65, 61, 57, 54, 50, 47, 44, 41, 38, 35, 32, 29, 27, 25, 22, 20, 18, 16, 15, |
||||
|
13, 12, 10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4, |
||||
|
]; |
||||
|
|
||||
|
private readonly nuint blockWidth; |
||||
|
private readonly nuint blockHeight; |
||||
|
|
||||
|
public Av1SmoothPredictor(Size blockSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)blockSize.Width; |
||||
|
this.blockHeight = (nuint)blockSize.Height; |
||||
|
} |
||||
|
|
||||
|
public Av1SmoothPredictor(Av1TransformSize transformSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)transformSize.GetWidth(); |
||||
|
this.blockHeight = (nuint)transformSize.GetHeight(); |
||||
|
} |
||||
|
|
||||
|
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
=> new Av1SmoothPredictor(transformSize).PredictScalar(destination, stride, above, left); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SVT: highbd_smooth_predictor
|
||||
|
/// </summary>
|
||||
|
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
||||
|
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
||||
|
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
||||
|
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
||||
|
ref byte leftRef = ref left[0]; |
||||
|
ref byte aboveRef = ref above[0]; |
||||
|
ref byte destinationRef = ref destination[0]; |
||||
|
int belowPrediction = Unsafe.Add(ref leftRef, this.blockHeight - 1); // estimated by bottom-left pixel
|
||||
|
int rightPrediction = Unsafe.Add(ref aboveRef, this.blockWidth - 1); // estimated by top-right pixel
|
||||
|
ref int heightWeights = ref Weights[(int)this.blockWidth]; |
||||
|
ref int widthWeights = ref Weights[(int)this.blockHeight]; |
||||
|
|
||||
|
// scale = 2 * 2^sm_weight_log2_scale
|
||||
|
int log2Scale = 1 + WeightLog2Scale; |
||||
|
int scale = 1 << WeightLog2Scale; |
||||
|
|
||||
|
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
|
||||
|
for (nuint r = 0; r < this.blockHeight; ++r) |
||||
|
{ |
||||
|
int rowWeight = Unsafe.Add(ref heightWeights, r); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(scale, rowWeight, nameof(scale)); |
||||
|
for (nuint c = 0; c < this.blockWidth; ++c) |
||||
|
{ |
||||
|
int columnWeight = Unsafe.Add(ref widthWeights, c); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(scale, columnWeight, nameof(scale)); |
||||
|
int thisPredition = Unsafe.Add(ref aboveRef, c) * rowWeight; |
||||
|
thisPredition += belowPrediction * (scale - rowWeight); |
||||
|
thisPredition += Unsafe.Add(ref leftRef, r) * columnWeight; |
||||
|
thisPredition += rightPrediction * (scale - columnWeight); |
||||
|
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale); |
||||
|
} |
||||
|
|
||||
|
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal class Av1SmoothVerticalPredictor : IAv1Predictor |
||||
|
{ |
||||
|
private readonly nuint blockWidth; |
||||
|
private readonly nuint blockHeight; |
||||
|
|
||||
|
public Av1SmoothVerticalPredictor(Size blockSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)blockSize.Width; |
||||
|
this.blockHeight = (nuint)blockSize.Height; |
||||
|
} |
||||
|
|
||||
|
public Av1SmoothVerticalPredictor(Av1TransformSize transformSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)transformSize.GetWidth(); |
||||
|
this.blockHeight = (nuint)transformSize.GetHeight(); |
||||
|
} |
||||
|
|
||||
|
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
=> new Av1SmoothVerticalPredictor(transformSize).PredictScalar(destination, stride, above, left); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SVT: highbd_smooth_v_predictor
|
||||
|
/// </summary>
|
||||
|
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
||||
|
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
||||
|
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
||||
|
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
||||
|
ref byte leftRef = ref left[0]; |
||||
|
ref byte aboveRef = ref above[0]; |
||||
|
ref byte destinationRef = ref destination[0]; |
||||
|
int belowPrediction = Unsafe.Add(ref leftRef, this.blockHeight - 1); // estimated by bottom-left pixel
|
||||
|
ref int weights = ref Av1SmoothPredictor.Weights[(int)this.blockHeight]; |
||||
|
|
||||
|
// scale = 2 * 2^sm_weight_log2_scale
|
||||
|
int log2Scale = 1 + Av1SmoothPredictor.WeightLog2Scale; |
||||
|
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale; |
||||
|
|
||||
|
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
|
||||
|
for (nuint r = 0; r < this.blockHeight; ++r) |
||||
|
{ |
||||
|
int rowWeight = Unsafe.Add(ref weights, r); |
||||
|
for (nuint c = 0; c < this.blockWidth; ++c) |
||||
|
{ |
||||
|
int thisPredition = Unsafe.Add(ref aboveRef, c) * rowWeight; |
||||
|
thisPredition += belowPrediction * (scale - rowWeight); |
||||
|
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale); |
||||
|
} |
||||
|
|
||||
|
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal class Av1VerticalPredictor : IAv1Predictor |
||||
|
{ |
||||
|
private readonly nuint blockWidth; |
||||
|
private readonly nuint blockHeight; |
||||
|
|
||||
|
public Av1VerticalPredictor(Size blockSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)blockSize.Width; |
||||
|
this.blockHeight = (nuint)blockSize.Height; |
||||
|
} |
||||
|
|
||||
|
public Av1VerticalPredictor(Av1TransformSize transformSize) |
||||
|
{ |
||||
|
this.blockWidth = (nuint)transformSize.GetWidth(); |
||||
|
this.blockHeight = (nuint)transformSize.GetHeight(); |
||||
|
} |
||||
|
|
||||
|
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
=> new Av1VerticalPredictor(transformSize).PredictScalar(destination, stride, above, left); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SVT: highbd_v_predictor
|
||||
|
/// </summary>
|
||||
|
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
||||
|
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
||||
|
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
||||
|
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
||||
|
ref byte aboveRef = ref above[0]; |
||||
|
ref byte destinationRef = ref destination[0]; |
||||
|
uint width = (uint)this.blockWidth; |
||||
|
for (nuint r = 0; r < this.blockHeight; ++r) |
||||
|
{ |
||||
|
Unsafe.CopyBlock(ref destinationRef, ref aboveRef, width); |
||||
|
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Globalization; |
||||
|
using System.Security.Cryptography; |
||||
|
using System.Text; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
||||
|
|
||||
|
internal class Av1IntraPredictionMemory |
||||
|
{ |
||||
|
public const int Padding = 16; |
||||
|
private const int TopPadding = 7; |
||||
|
private const int MaxBlockSizeTimes2 = 1 << Av1Constants.MaxSuperBlockSizeLog2; |
||||
|
private const int TotalPixelCount = 4096; |
||||
|
private readonly byte[] destination = new byte[TotalPixelCount]; |
||||
|
private readonly byte[] referenceSource = new byte[TotalPixelCount]; |
||||
|
private readonly byte[] leftMemory = new byte[MaxBlockSizeTimes2 + Padding]; |
||||
|
private readonly byte[] topMemory = new byte[MaxBlockSizeTimes2 + Padding + TopPadding]; |
||||
|
private readonly int bitDepth; |
||||
|
|
||||
|
public Av1IntraPredictionMemory(int bitDepth) => this.bitDepth = bitDepth; |
||||
|
|
||||
|
public Span<byte> Destination => this.destination; |
||||
|
|
||||
|
public Span<byte> Left => this.leftMemory; |
||||
|
|
||||
|
public Span<byte> Top => this.topMemory; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets referenceSource, left and top to <paramref name="value"/>.
|
||||
|
/// </summary>
|
||||
|
public void Set(byte value) |
||||
|
{ |
||||
|
for (int r = 0; r < this.referenceSource.Length; r++) |
||||
|
{ |
||||
|
this.referenceSource[r] = value; |
||||
|
} |
||||
|
|
||||
|
// Upsampling in the directional predictors extends left/top[-1] to [-2].
|
||||
|
for (int i = Padding - 2; i < Padding + MaxBlockSizeTimes2; ++i) |
||||
|
{ |
||||
|
this.leftMemory[i] = this.topMemory[i] = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Scramble(Random rnd) |
||||
|
{ |
||||
|
for (int i = 0; i < this.referenceSource.Length; i++) |
||||
|
{ |
||||
|
this.referenceSource[i] = this.GetRandomValue(rnd); |
||||
|
} |
||||
|
|
||||
|
for (int i = Padding; i < (MaxBlockSizeTimes2 >> 1) + Padding; ++i) |
||||
|
{ |
||||
|
this.leftMemory[i] = this.GetRandomValue(rnd); |
||||
|
} |
||||
|
|
||||
|
for (int i = Padding - 1; i < (MaxBlockSizeTimes2 >> 1) + Padding; ++i) |
||||
|
{ |
||||
|
this.topMemory[i] = this.GetRandomValue(rnd); |
||||
|
} |
||||
|
|
||||
|
// Some directional predictors require top-right, bottom-left.
|
||||
|
for (int i = MaxBlockSizeTimes2 >> 1; i < MaxBlockSizeTimes2; ++i) |
||||
|
{ |
||||
|
this.leftMemory[Padding + i] = this.GetRandomValue(rnd); |
||||
|
this.topMemory[Padding + i] = this.GetRandomValue(rnd); |
||||
|
} |
||||
|
|
||||
|
// TODO(jzern): reorder this and regenerate the digests after switching
|
||||
|
// random number generators.
|
||||
|
// Upsampling in the directional predictors extends left/top[-1] to [-2].
|
||||
|
this.leftMemory[Padding - 1] = this.GetRandomValue(rnd); |
||||
|
this.leftMemory[Padding - 2] = this.GetRandomValue(rnd); |
||||
|
this.topMemory[Padding - 2] = this.GetRandomValue(rnd); |
||||
|
this.leftMemory.AsSpan(0, Padding - 2).Clear(); |
||||
|
this.topMemory.AsSpan(0, Padding - 2).Clear(); |
||||
|
this.topMemory.AsSpan(MaxBlockSizeTimes2 + Padding, TopPadding).Clear(); |
||||
|
} |
||||
|
|
||||
|
public void CopySourceToDestination() => this.referenceSource.CopyTo(this.destination.AsSpan()); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Return a hash string of a block of bytes.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>This test design is used extensively in 'libgav1' tests, some of which are ported here.</remarks>
|
||||
|
public static string GetDigest(byte[] input) |
||||
|
{ |
||||
|
byte[] hash = MD5.HashData(input); |
||||
|
StringBuilder sb = new(); |
||||
|
for (int i = 0; i < hash.Length; i++) |
||||
|
{ |
||||
|
sb.Append(hash[i].ToString("X2", CultureInfo.InvariantCulture)); |
||||
|
} |
||||
|
|
||||
|
return sb.ToString(); |
||||
|
} |
||||
|
|
||||
|
public string GetDestinationDigest() => GetDigest(this.destination); |
||||
|
|
||||
|
private byte GetRandomValue(Random random) => (byte)(random.Next(1 << 16) & ((1 << this.bitDepth) - 1)); |
||||
|
} |
||||
Loading…
Reference in new issue