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