mirror of https://github.com/SixLabors/ImageSharp
5 changed files with 570 additions and 4 deletions
@ -0,0 +1,423 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Implements AV1 intra prediction for 10-bit and 12-bit sample buffers.
|
||||
|
/// </summary>
|
||||
|
internal static class Av1HighBitDepthPredictor |
||||
|
{ |
||||
|
private const int FilterBufferStride = 33; |
||||
|
private const int FilterBufferLength = FilterBufferStride * FilterBufferStride; |
||||
|
private const int FilterTapsPerPixel = 7; |
||||
|
private const int FilterPixelsPerGroup = 8; |
||||
|
private const int FilterTapsPerMode = FilterTapsPerPixel * FilterPixelsPerGroup; |
||||
|
|
||||
|
internal static void DcPredictor( |
||||
|
bool hasLeft, |
||||
|
bool hasAbove, |
||||
|
Av1TransformSize transformSize, |
||||
|
Span<short> destination, |
||||
|
nuint destinationStride, |
||||
|
Span<short> above, |
||||
|
Span<short> left, |
||||
|
int bitDepth) |
||||
|
{ |
||||
|
int width = transformSize.GetWidth(); |
||||
|
int height = transformSize.GetHeight(); |
||||
|
int sum = 0; |
||||
|
int count = 0; |
||||
|
|
||||
|
if (hasAbove) |
||||
|
{ |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
sum += above[column]; |
||||
|
} |
||||
|
|
||||
|
count += width; |
||||
|
} |
||||
|
|
||||
|
if (hasLeft) |
||||
|
{ |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
sum += left[row]; |
||||
|
} |
||||
|
|
||||
|
count += height; |
||||
|
} |
||||
|
|
||||
|
short prediction = count == 0 |
||||
|
? (short)(1 << (bitDepth - 1)) |
||||
|
: (short)((sum + (count >> 1)) / count); |
||||
|
|
||||
|
Fill(destination, destinationStride, width, height, prediction); |
||||
|
} |
||||
|
|
||||
|
internal static void GeneralPredictor( |
||||
|
Av1PredictionMode mode, |
||||
|
Av1TransformSize transformSize, |
||||
|
Span<short> destination, |
||||
|
nuint destinationStride, |
||||
|
Span<short> above, |
||||
|
Span<short> left) |
||||
|
{ |
||||
|
int width = transformSize.GetWidth(); |
||||
|
int height = transformSize.GetHeight(); |
||||
|
|
||||
|
switch (mode) |
||||
|
{ |
||||
|
case Av1PredictionMode.Horizontal: |
||||
|
PredictHorizontal(destination, destinationStride, left, width, height); |
||||
|
break; |
||||
|
case Av1PredictionMode.Vertical: |
||||
|
PredictVertical(destination, destinationStride, above, width, height); |
||||
|
break; |
||||
|
case Av1PredictionMode.Paeth: |
||||
|
PredictPaeth(destination, destinationStride, above, left, width, height); |
||||
|
break; |
||||
|
case Av1PredictionMode.Smooth: |
||||
|
PredictSmooth(destination, destinationStride, above, left, width, height); |
||||
|
break; |
||||
|
case Av1PredictionMode.SmoothHorizontal: |
||||
|
PredictSmoothHorizontal(destination, destinationStride, above, left, width, height); |
||||
|
break; |
||||
|
case Av1PredictionMode.SmoothVertical: |
||||
|
PredictSmoothVertical(destination, destinationStride, above, left, width, height); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal static void DirectionalPredictor( |
||||
|
Span<short> destination, |
||||
|
nuint destinationStride, |
||||
|
Av1TransformSize transformSize, |
||||
|
Span<short> above, |
||||
|
Span<short> left, |
||||
|
bool upsampleAbove, |
||||
|
bool upsampleLeft, |
||||
|
int angle, |
||||
|
int bitDepth) |
||||
|
{ |
||||
|
Guard.MustBeBetweenOrEqualTo(angle, 1, 269, nameof(angle)); |
||||
|
int dx = Av1PredictorFactory.GetDeltaX(angle); |
||||
|
int dy = Av1PredictorFactory.GetDeltaY(angle); |
||||
|
int width = transformSize.GetWidth(); |
||||
|
int height = transformSize.GetHeight(); |
||||
|
|
||||
|
if (angle is > 0 and < 90) |
||||
|
{ |
||||
|
PredictDirectionalZone1(destination, destinationStride, above, upsampleAbove, dx, width, height, bitDepth); |
||||
|
} |
||||
|
else if (angle is > 90 and < 180) |
||||
|
{ |
||||
|
PredictDirectionalZone2(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, dx, dy, width, height, bitDepth); |
||||
|
} |
||||
|
else if (angle is > 180 and < 270) |
||||
|
{ |
||||
|
PredictDirectionalZone3(destination, destinationStride, left, upsampleLeft, dx, dy, width, height, bitDepth); |
||||
|
} |
||||
|
else if (angle == 90) |
||||
|
{ |
||||
|
PredictVertical(destination, destinationStride, above, width, height); |
||||
|
} |
||||
|
else if (angle == 180) |
||||
|
{ |
||||
|
PredictHorizontal(destination, destinationStride, left, width, height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal static void FilterIntraPredictor( |
||||
|
Span<short> destination, |
||||
|
nuint destinationStride, |
||||
|
Av1TransformSize transformSize, |
||||
|
Span<short> above, |
||||
|
Span<short> left, |
||||
|
Av1FilterIntraMode mode, |
||||
|
int bitDepth) |
||||
|
{ |
||||
|
int width = transformSize.GetWidth(); |
||||
|
int height = transformSize.GetHeight(); |
||||
|
int maximum = (1 << bitDepth) - 1; |
||||
|
DebugGuard.MustBeLessThanOrEqualTo(width, 32, nameof(width)); |
||||
|
DebugGuard.MustBeLessThanOrEqualTo(height, 32, nameof(height)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(destinationStride, (nuint)width, nameof(destinationStride)); |
||||
|
Guard.MustBeSizedAtLeast(destination, (int)destinationStride * height, nameof(destination)); |
||||
|
Guard.MustBeSizedAtLeast(above, width, nameof(above)); |
||||
|
Guard.MustBeSizedAtLeast(left, height, nameof(left)); |
||||
|
|
||||
|
Span<short> buffer = stackalloc short[FilterBufferLength]; |
||||
|
ref short bufferRef = ref buffer[0]; |
||||
|
ref short aboveRef = ref above[0]; |
||||
|
ref short leftRef = ref left[0]; |
||||
|
|
||||
|
// Row zero includes the top-left sample followed by the top neighbors.
|
||||
|
bufferRef = Unsafe.Subtract(ref aboveRef, 1); |
||||
|
above[..width].CopyTo(buffer[1..]); |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Unsafe.Add(ref bufferRef, (row + 1) * FilterBufferStride) = Unsafe.Add(ref leftRef, row); |
||||
|
} |
||||
|
|
||||
|
int modeOffset = (int)mode * FilterTapsPerMode; |
||||
|
ref sbyte tapsRef = ref Av1FilterIntraPredictor.Taps[modeOffset]; |
||||
|
for (int row = 1; row <= height; row += 2) |
||||
|
{ |
||||
|
for (int column = 1; column <= width; column += 4) |
||||
|
{ |
||||
|
int sourceOffset = ((row - 1) * FilterBufferStride) + column - 1; |
||||
|
int p0 = Unsafe.Add(ref bufferRef, sourceOffset); |
||||
|
int p1 = Unsafe.Add(ref bufferRef, sourceOffset + 1); |
||||
|
int p2 = Unsafe.Add(ref bufferRef, sourceOffset + 2); |
||||
|
int p3 = Unsafe.Add(ref bufferRef, sourceOffset + 3); |
||||
|
int p4 = Unsafe.Add(ref bufferRef, sourceOffset + 4); |
||||
|
int p5 = Unsafe.Add(ref bufferRef, sourceOffset + FilterBufferStride); |
||||
|
int p6 = Unsafe.Add(ref bufferRef, sourceOffset + (2 * FilterBufferStride)); |
||||
|
|
||||
|
for (int pixel = 0; pixel < FilterPixelsPerGroup; pixel++) |
||||
|
{ |
||||
|
int tapOffset = pixel * FilterTapsPerPixel; |
||||
|
int prediction = |
||||
|
(Unsafe.Add(ref tapsRef, tapOffset) * p0) |
||||
|
+ (Unsafe.Add(ref tapsRef, tapOffset + 1) * p1) |
||||
|
+ (Unsafe.Add(ref tapsRef, tapOffset + 2) * p2) |
||||
|
+ (Unsafe.Add(ref tapsRef, tapOffset + 3) * p3) |
||||
|
+ (Unsafe.Add(ref tapsRef, tapOffset + 4) * p4) |
||||
|
+ (Unsafe.Add(ref tapsRef, tapOffset + 5) * p5) |
||||
|
+ (Unsafe.Add(ref tapsRef, tapOffset + 6) * p6); |
||||
|
|
||||
|
int rowOffset = pixel >> 2; |
||||
|
int columnOffset = pixel & 3; |
||||
|
int destinationOffset = ((row + rowOffset) * FilterBufferStride) + column + columnOffset; |
||||
|
Unsafe.Add(ref bufferRef, destinationOffset) = |
||||
|
(short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 4), 0, maximum); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
buffer.Slice(((row + 1) * FilterBufferStride) + 1, width).CopyTo( |
||||
|
destination.Slice(row * (int)destinationStride, width)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void PredictHorizontal(Span<short> destination, nuint stride, Span<short> left, int width, int height) |
||||
|
{ |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
destination.Slice(row * (int)stride, width).Fill(left[row]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void PredictVertical(Span<short> destination, nuint stride, Span<short> above, int width, int height) |
||||
|
{ |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
above[..width].CopyTo(destination.Slice(row * (int)stride, width)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void PredictPaeth(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height) |
||||
|
{ |
||||
|
int topLeft = Unsafe.Subtract(ref above[0], 1); |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
short leftValue = left[row]; |
||||
|
short topValue = above[column]; |
||||
|
int basis = topValue + leftValue - topLeft; |
||||
|
int leftDistance = Av1Math.AbsoluteDifference(basis, leftValue); |
||||
|
int topDistance = Av1Math.AbsoluteDifference(basis, topValue); |
||||
|
int topLeftDistance = Av1Math.AbsoluteDifference(basis, topLeft); |
||||
|
destinationRow[column] = leftDistance <= topDistance && leftDistance <= topLeftDistance |
||||
|
? leftValue |
||||
|
: topDistance <= topLeftDistance ? topValue : (short)topLeft; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void PredictSmooth(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height) |
||||
|
{ |
||||
|
int below = left[height - 1]; |
||||
|
int right = above[width - 1]; |
||||
|
Span<int> widthWeights = Av1SmoothPredictor.Weights.AsSpan(width, width); |
||||
|
Span<int> heightWeights = Av1SmoothPredictor.Weights.AsSpan(height, height); |
||||
|
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale; |
||||
|
int log2Scale = Av1SmoothPredictor.WeightLog2Scale + 1; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
int rowWeight = heightWeights[row]; |
||||
|
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
int columnWeight = widthWeights[column]; |
||||
|
int prediction = (above[column] * rowWeight) + (below * (scale - rowWeight)); |
||||
|
prediction += (left[row] * columnWeight) + (right * (scale - columnWeight)); |
||||
|
destinationRow[column] = (short)Av1Math.DivideRound(prediction, log2Scale); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void PredictSmoothHorizontal(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height) |
||||
|
{ |
||||
|
int right = above[width - 1]; |
||||
|
Span<int> weights = Av1SmoothPredictor.Weights.AsSpan(width, width); |
||||
|
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
int weight = weights[column]; |
||||
|
int prediction = (left[row] * weight) + (right * (scale - weight)); |
||||
|
destinationRow[column] = (short)Av1Math.DivideRound(prediction, Av1SmoothPredictor.WeightLog2Scale); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void PredictSmoothVertical(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height) |
||||
|
{ |
||||
|
int below = left[height - 1]; |
||||
|
Span<int> weights = Av1SmoothPredictor.Weights.AsSpan(height, height); |
||||
|
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
int weight = weights[row]; |
||||
|
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
int prediction = (above[column] * weight) + (below * (scale - weight)); |
||||
|
destinationRow[column] = (short)Av1Math.DivideRound(prediction, Av1SmoothPredictor.WeightLog2Scale); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void PredictDirectionalZone1(Span<short> destination, nuint stride, Span<short> above, bool upsample, int dx, int width, int height, int bitDepth) |
||||
|
{ |
||||
|
int upsampleAbove = upsample ? 1 : 0; |
||||
|
int maxBasisX = (width + height - 1) << upsampleAbove; |
||||
|
int fractionBitCount = 6 - upsampleAbove; |
||||
|
int basisIncrement = 1 << upsampleAbove; |
||||
|
int maximum = (1 << bitDepth) - 1; |
||||
|
int x = dx; |
||||
|
ref short aboveRef = ref above[0]; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
||||
|
int basis = x >> fractionBitCount; |
||||
|
int shift = ((x << upsampleAbove) & 0x3F) >> 1; |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
if (basis < maxBasisX) |
||||
|
{ |
||||
|
int prediction = (Unsafe.Add(ref aboveRef, basis) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basis + 1) * shift); |
||||
|
destinationRow[column] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
destinationRow[column] = Unsafe.Add(ref aboveRef, maxBasisX); |
||||
|
} |
||||
|
|
||||
|
basis += basisIncrement; |
||||
|
} |
||||
|
|
||||
|
x += dx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void PredictDirectionalZone2(Span<short> destination, nuint stride, Span<short> above, Span<short> left, bool doUpsampleAbove, bool doUpsampleLeft, int dx, int dy, int width, int height, int bitDepth) |
||||
|
{ |
||||
|
int upsampleAbove = doUpsampleAbove ? 1 : 0; |
||||
|
int upsampleLeft = doUpsampleLeft ? 1 : 0; |
||||
|
int minBasisX = -(1 << upsampleAbove); |
||||
|
int fractionBitCountX = 6 - upsampleAbove; |
||||
|
int fractionBitCountY = 6 - upsampleLeft; |
||||
|
int basisIncrementX = 1 << upsampleAbove; |
||||
|
int maximum = (1 << bitDepth) - 1; |
||||
|
int x = -dx; |
||||
|
ref short aboveRef = ref above[0]; |
||||
|
ref short leftRef = ref left[0]; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
||||
|
int basisX = x >> fractionBitCountX; |
||||
|
int y = (row << 6) - dy; |
||||
|
for (int column = 0; column < width; column++, basisX += basisIncrementX, y -= dy) |
||||
|
{ |
||||
|
int prediction; |
||||
|
if (basisX >= minBasisX) |
||||
|
{ |
||||
|
int shift = ((x * (1 << upsampleAbove)) & 0x3F) >> 1; |
||||
|
prediction = (Unsafe.Add(ref aboveRef, basisX) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basisX + 1) * shift); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
int basisY = y >> fractionBitCountY; |
||||
|
int shift = ((y * (1 << upsampleLeft)) & 0x3F) >> 1; |
||||
|
prediction = (Unsafe.Add(ref leftRef, basisY) * (32 - shift)) + (Unsafe.Add(ref leftRef, basisY + 1) * shift); |
||||
|
} |
||||
|
|
||||
|
destinationRow[column] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum); |
||||
|
} |
||||
|
|
||||
|
x -= dx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void PredictDirectionalZone3(Span<short> destination, nuint stride, Span<short> left, bool upsample, int dx, int dy, int width, int height, int bitDepth) |
||||
|
{ |
||||
|
int upsampleLeft = upsample ? 1 : 0; |
||||
|
int maxBasisY = (width + height - 1) << upsampleLeft; |
||||
|
int fractionBitCount = 6 - upsampleLeft; |
||||
|
int basisIncrement = 1 << upsampleLeft; |
||||
|
int maximum = (1 << bitDepth) - 1; |
||||
|
int y = dy; |
||||
|
ref short leftRef = ref left[0]; |
||||
|
Guard.IsTrue(dx == 1, nameof(dx), "Dx expected to be always equal to 1 for directional Zone 3 prediction."); |
||||
|
|
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
int basis = y >> fractionBitCount; |
||||
|
int shift = ((y << upsampleLeft) & 0x3F) >> 1; |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
int destinationOffset = (row * (int)stride) + column; |
||||
|
if (basis < maxBasisY) |
||||
|
{ |
||||
|
int prediction = (Unsafe.Add(ref leftRef, basis) * (32 - shift)) + (Unsafe.Add(ref leftRef, basis + 1) * shift); |
||||
|
destination[destinationOffset] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
destination[destinationOffset] = Unsafe.Add(ref leftRef, maxBasisY); |
||||
|
} |
||||
|
|
||||
|
basis += basisIncrement; |
||||
|
} |
||||
|
|
||||
|
y += dy; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void Fill(Span<short> destination, nuint stride, int width, int height, short value) |
||||
|
{ |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
destination.Slice(row * (int)stride, width).Fill(value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue