mirror of https://github.com/SixLabors/ImageSharp
7 changed files with 833 additions and 18 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,416 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; |
||||
|
|
||||
|
/// <content>
|
||||
|
/// Provides luma palette mode decisions for intra encoding.
|
||||
|
/// </content>
|
||||
|
internal static partial class Av1IntraSuperblockEncoder |
||||
|
{ |
||||
|
internal partial struct ModeDecision<TSample, TOperator> |
||||
|
where TSample : unmanaged |
||||
|
where TOperator : struct, IBlockEncodingOperator<TSample> |
||||
|
{ |
||||
|
private bool SelectLumaPalette( |
||||
|
Av1SymbolEncoder writer, |
||||
|
Av1MacroBlockD macroBlock, |
||||
|
Buffer2DRegion<TSample> sourcePlane, |
||||
|
Buffer2DRegion<TSample> reconstructionPlane, |
||||
|
Point blockOrigin, |
||||
|
ushort tileIndex, |
||||
|
Av1TransformSetType transformSetType, |
||||
|
Av1TransformBlockContext blockContext, |
||||
|
Span<TSample> candidateReconstruction, |
||||
|
Span<int> candidateCoefficients, |
||||
|
Span<int> retainedCoefficients, |
||||
|
ref Av1EncoderTransformBlockState retainedState, |
||||
|
ref long bestCost, |
||||
|
ref Av1EncoderPaletteInfo paletteInfo) |
||||
|
{ |
||||
|
const Av1BlockSize BlockSize = Av1BlockSize.Block8x8; |
||||
|
const int BlockLength = 8; |
||||
|
const int SampleCapacity = BlockLength * BlockLength; |
||||
|
ObuFrameSize frameSize = this.picture.Parent.FrameHeader.FrameSize; |
||||
|
int rows = Math.Min(BlockLength, frameSize.FrameHeight - blockOrigin.Y); |
||||
|
int columns = Math.Min(BlockLength, frameSize.FrameWidth - blockOrigin.X); |
||||
|
int sampleCount = rows * columns; |
||||
|
Span<short> samples = stackalloc short[SampleCapacity]; |
||||
|
samples = samples[..sampleCount]; |
||||
|
TOperator.CopyPaletteSamples(sourcePlane, blockOrigin, rows, columns, samples); |
||||
|
|
||||
|
Span<short> uniqueColors = stackalloc short[SampleCapacity]; |
||||
|
Span<int> colorCounts = stackalloc int[SampleCapacity]; |
||||
|
int uniqueColorCount = 0; |
||||
|
short minimum = samples[0]; |
||||
|
short maximum = samples[0]; |
||||
|
foreach (short sample in samples) |
||||
|
{ |
||||
|
int colorIndex = uniqueColors[..uniqueColorCount].IndexOf(sample); |
||||
|
if (colorIndex >= 0) |
||||
|
{ |
||||
|
colorCounts[colorIndex]++; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
uniqueColors[uniqueColorCount] = sample; |
||||
|
colorCounts[uniqueColorCount] = 1; |
||||
|
uniqueColorCount++; |
||||
|
} |
||||
|
|
||||
|
minimum = Math.Min(minimum, sample); |
||||
|
maximum = Math.Max(maximum, sample); |
||||
|
} |
||||
|
|
||||
|
if (uniqueColorCount < 2) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
int maximumPaletteSize = Math.Min(uniqueColorCount, Av1Constants.PaletteMaxSize); |
||||
|
Span<byte> dominantOrder = stackalloc byte[SampleCapacity]; |
||||
|
for (int index = 0; index < uniqueColorCount; index++) |
||||
|
{ |
||||
|
dominantOrder[index] = (byte)index; |
||||
|
} |
||||
|
|
||||
|
// Count order chooses the colors that explain most samples first; sample value resolves equal counts.
|
||||
|
for (int index = 1; index < uniqueColorCount; index++) |
||||
|
{ |
||||
|
byte current = dominantOrder[index]; |
||||
|
int destination = index; |
||||
|
while (destination > 0) |
||||
|
{ |
||||
|
byte preceding = dominantOrder[destination - 1]; |
||||
|
bool precedes = colorCounts[current] > colorCounts[preceding] || |
||||
|
(colorCounts[current] == colorCounts[preceding] && uniqueColors[current] < uniqueColors[preceding]); |
||||
|
|
||||
|
if (!precedes) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
dominantOrder[destination] = preceding; |
||||
|
destination--; |
||||
|
} |
||||
|
|
||||
|
dominantOrder[destination] = current; |
||||
|
} |
||||
|
|
||||
|
Av1NeighborArrayUnit<Av1EncoderPaletteInfo> paletteContexts = this.picture.PaletteContexts[tileIndex]; |
||||
|
int blockSizeContext = Av1TileWriter.GetPaletteBlockSizeContext(BlockSize); |
||||
|
int neighborContext = Av1TileWriter.GetPaletteYModeContext(paletteContexts, macroBlock, blockOrigin); |
||||
|
Span<ushort> colorCache = stackalloc ushort[2 * Av1Constants.PaletteMaxSize]; |
||||
|
int colorCacheSize = Av1TileWriter.GetPaletteCache( |
||||
|
paletteContexts, |
||||
|
macroBlock, |
||||
|
blockOrigin, |
||||
|
Av1Plane.Y, |
||||
|
colorCache); |
||||
|
|
||||
|
Buffer2DRegion<byte> colorIndexMap = this.superblock.Workspace |
||||
|
.GetPaletteMaps() |
||||
|
.GetMap(Av1PlaneType.Y, BlockLength, BlockLength); |
||||
|
|
||||
|
Span<byte> retainedColorIndexMap = stackalloc byte[SampleCapacity]; |
||||
|
Span<short> centroids = stackalloc short[Av1Constants.PaletteMaxSize]; |
||||
|
bool paletteSelected = false; |
||||
|
|
||||
|
// Exhaustive ascending size search avoids the reference encoder's speed-dependent pruning.
|
||||
|
for (int paletteSize = 2; paletteSize <= maximumPaletteSize; paletteSize++) |
||||
|
{ |
||||
|
for (int index = 0; index < paletteSize; index++) |
||||
|
{ |
||||
|
centroids[index] = uniqueColors[dominantOrder[index]]; |
||||
|
} |
||||
|
|
||||
|
this.EvaluateLumaPaletteCandidate( |
||||
|
writer, |
||||
|
macroBlock, |
||||
|
blockOrigin, |
||||
|
transformSetType, |
||||
|
blockContext, |
||||
|
samples, |
||||
|
rows, |
||||
|
columns, |
||||
|
colorCache[..colorCacheSize], |
||||
|
blockSizeContext, |
||||
|
neighborContext, |
||||
|
centroids[..paletteSize], |
||||
|
colorIndexMap, |
||||
|
candidateReconstruction, |
||||
|
candidateCoefficients, |
||||
|
retainedCoefficients, |
||||
|
retainedColorIndexMap, |
||||
|
reconstructionPlane, |
||||
|
ref retainedState, |
||||
|
ref bestCost, |
||||
|
ref paletteInfo, |
||||
|
ref paletteSelected); |
||||
|
} |
||||
|
|
||||
|
if (uniqueColorCount == 2) |
||||
|
{ |
||||
|
centroids[0] = minimum; |
||||
|
centroids[1] = maximum; |
||||
|
this.EvaluateLumaPaletteCandidate( |
||||
|
writer, |
||||
|
macroBlock, |
||||
|
blockOrigin, |
||||
|
transformSetType, |
||||
|
blockContext, |
||||
|
samples, |
||||
|
rows, |
||||
|
columns, |
||||
|
colorCache[..colorCacheSize], |
||||
|
blockSizeContext, |
||||
|
neighborContext, |
||||
|
centroids[..2], |
||||
|
colorIndexMap, |
||||
|
candidateReconstruction, |
||||
|
candidateCoefficients, |
||||
|
retainedCoefficients, |
||||
|
retainedColorIndexMap, |
||||
|
reconstructionPlane, |
||||
|
ref retainedState, |
||||
|
ref bestCost, |
||||
|
ref paletteInfo, |
||||
|
ref paletteSelected); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Span<byte> clusterIndices = stackalloc byte[SampleCapacity]; |
||||
|
clusterIndices = clusterIndices[..sampleCount]; |
||||
|
for (int paletteSize = 2; paletteSize <= maximumPaletteSize; paletteSize++) |
||||
|
{ |
||||
|
Span<short> candidateCentroids = centroids[..paletteSize]; |
||||
|
Av1PaletteKMeans.InitializeCentroids(minimum, maximum, candidateCentroids); |
||||
|
Av1PaletteKMeans.Cluster(samples, candidateCentroids, clusterIndices); |
||||
|
this.EvaluateLumaPaletteCandidate( |
||||
|
writer, |
||||
|
macroBlock, |
||||
|
blockOrigin, |
||||
|
transformSetType, |
||||
|
blockContext, |
||||
|
samples, |
||||
|
rows, |
||||
|
columns, |
||||
|
colorCache[..colorCacheSize], |
||||
|
blockSizeContext, |
||||
|
neighborContext, |
||||
|
candidateCentroids, |
||||
|
colorIndexMap, |
||||
|
candidateReconstruction, |
||||
|
candidateCoefficients, |
||||
|
retainedCoefficients, |
||||
|
retainedColorIndexMap, |
||||
|
reconstructionPlane, |
||||
|
ref retainedState, |
||||
|
ref bestCost, |
||||
|
ref paletteInfo, |
||||
|
ref paletteSelected); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (paletteSelected) |
||||
|
{ |
||||
|
for (int row = 0; row < BlockLength; row++) |
||||
|
{ |
||||
|
retainedColorIndexMap.Slice(row * BlockLength, BlockLength) |
||||
|
.CopyTo(colorIndexMap.DangerousGetRowSpan(row)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return paletteSelected; |
||||
|
} |
||||
|
|
||||
|
private void EvaluateLumaPaletteCandidate( |
||||
|
Av1SymbolEncoder writer, |
||||
|
Av1MacroBlockD macroBlock, |
||||
|
Point blockOrigin, |
||||
|
Av1TransformSetType transformSetType, |
||||
|
Av1TransformBlockContext blockContext, |
||||
|
ReadOnlySpan<short> samples, |
||||
|
int rows, |
||||
|
int columns, |
||||
|
ReadOnlySpan<ushort> colorCache, |
||||
|
int blockSizeContext, |
||||
|
int neighborContext, |
||||
|
Span<short> centroids, |
||||
|
Buffer2DRegion<byte> colorIndexMap, |
||||
|
Span<TSample> candidateReconstruction, |
||||
|
Span<int> candidateCoefficients, |
||||
|
Span<int> retainedCoefficients, |
||||
|
Span<byte> retainedColorIndexMap, |
||||
|
Buffer2DRegion<TSample> reconstructionPlane, |
||||
|
ref Av1EncoderTransformBlockState retainedState, |
||||
|
ref long bestCost, |
||||
|
ref Av1EncoderPaletteInfo paletteInfo, |
||||
|
ref bool paletteSelected) |
||||
|
{ |
||||
|
const Av1BlockSize BlockSize = Av1BlockSize.Block8x8; |
||||
|
const Av1TransformSize TransformSize = Av1TransformSize.Size8x8; |
||||
|
const int BlockLength = 8; |
||||
|
const int SampleCount = BlockLength * BlockLength; |
||||
|
int bitDepth = this.bitDepth.GetBitCount(); |
||||
|
int cacheThreshold = 4 << (bitDepth - 8); |
||||
|
for (int colorIndex = 0; colorIndex < centroids.Length && !colorCache.IsEmpty; colorIndex++) |
||||
|
{ |
||||
|
int minimumDifference = Math.Abs(centroids[colorIndex] - colorCache[0]); |
||||
|
int nearestCacheIndex = 0; |
||||
|
for (int cacheIndex = 1; cacheIndex < colorCache.Length; cacheIndex++) |
||||
|
{ |
||||
|
int difference = Math.Abs(centroids[colorIndex] - colorCache[cacheIndex]); |
||||
|
if (difference < minimumDifference) |
||||
|
{ |
||||
|
minimumDifference = difference; |
||||
|
nearestCacheIndex = cacheIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (minimumDifference <= cacheThreshold) |
||||
|
{ |
||||
|
centroids[colorIndex] = (short)colorCache[nearestCacheIndex]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
centroids.Sort(); |
||||
|
int paletteSize = 1; |
||||
|
for (int colorIndex = 1; colorIndex < centroids.Length; colorIndex++) |
||||
|
{ |
||||
|
if (centroids[colorIndex] != centroids[colorIndex - 1]) |
||||
|
{ |
||||
|
centroids[paletteSize++] = centroids[colorIndex]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (paletteSize < 2) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
ReadOnlySpan<short> paletteCentroids = centroids[..paletteSize]; |
||||
|
Span<ushort> paletteColors = stackalloc ushort[Av1Constants.PaletteMaxSize]; |
||||
|
paletteColors = paletteColors[..paletteSize]; |
||||
|
for (int colorIndex = 0; colorIndex < paletteSize; colorIndex++) |
||||
|
{ |
||||
|
paletteColors[colorIndex] = (ushort)paletteCentroids[colorIndex]; |
||||
|
} |
||||
|
|
||||
|
Span<byte> colorIndices = stackalloc byte[SampleCount]; |
||||
|
Av1PaletteKMeans.AssignIndices(samples, paletteCentroids, colorIndices); |
||||
|
for (int row = 0; row < rows; row++) |
||||
|
{ |
||||
|
Span<byte> mapRow = colorIndexMap.DangerousGetRowSpan(row)[..BlockLength]; |
||||
|
colorIndices.Slice(row * columns, columns).CopyTo(mapRow); |
||||
|
mapRow[columns..].Fill(mapRow[columns - 1]); |
||||
|
} |
||||
|
|
||||
|
// Padding repeats the last active edge so transform prediction matches coded-frame edge extension.
|
||||
|
for (int row = rows; row < BlockLength; row++) |
||||
|
{ |
||||
|
colorIndexMap.DangerousGetRowSpan(rows - 1)[..BlockLength] |
||||
|
.CopyTo(colorIndexMap.DangerousGetRowSpan(row)); |
||||
|
} |
||||
|
|
||||
|
Span<TSample> prediction = stackalloc TSample[SampleCount]; |
||||
|
Span<short> residual = stackalloc short[SampleCount]; |
||||
|
TOperator.PreparePalette( |
||||
|
this.source.GetPlane(Av1Plane.Y), |
||||
|
blockOrigin, |
||||
|
paletteColors, |
||||
|
colorIndexMap, |
||||
|
prediction, |
||||
|
residual, |
||||
|
TransformSize); |
||||
|
|
||||
|
int rate = Av1TileWriter.GetLumaModeCost( |
||||
|
writer, |
||||
|
macroBlock, |
||||
|
BlockSize, |
||||
|
Av1PredictionMode.DC, |
||||
|
0); |
||||
|
|
||||
|
rate += writer.GetPaletteYModeCost(true, blockSizeContext, neighborContext); |
||||
|
rate += writer.GetPaletteSizeCost(paletteSize, blockSizeContext, Av1PlaneType.Y); |
||||
|
rate += Av1SymbolEncoder.GetPaletteYColorCost(colorCache, paletteColors, bitDepth); |
||||
|
rate += writer.GetPaletteColorMapCost( |
||||
|
paletteSize, |
||||
|
Av1PlaneType.Y, |
||||
|
rows, |
||||
|
columns, |
||||
|
colorIndexMap); |
||||
|
|
||||
|
for (Av1TransformType transformType = Av1TransformType.DctDct; |
||||
|
transformType < Av1TransformType.AllTransformTypes; |
||||
|
transformType++) |
||||
|
{ |
||||
|
if (!transformType.IsExtendedSetUsed(transformSetType)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
Av1EncoderTransformBlockState candidateState = default; |
||||
|
long distortion = TOperator.EncodePredictionCandidate( |
||||
|
this.blockWorkspace, |
||||
|
this.source.GetPlane(Av1Plane.Y), |
||||
|
blockOrigin, |
||||
|
prediction, |
||||
|
residual, |
||||
|
candidateReconstruction, |
||||
|
candidateCoefficients, |
||||
|
TransformSize, |
||||
|
transformType, |
||||
|
Av1Plane.Y, |
||||
|
this.quantization.QIndex[0], |
||||
|
this.quantization.DeltaQDc[(int)Av1Plane.Y], |
||||
|
this.quantization.DeltaQAc[(int)Av1Plane.Y], |
||||
|
this.bitDepth, |
||||
|
ref candidateState); |
||||
|
|
||||
|
int candidateRate = rate + writer.GetCoefficientCost( |
||||
|
TransformSize, |
||||
|
transformType, |
||||
|
Av1PredictionMode.DC, |
||||
|
candidateCoefficients, |
||||
|
Av1ComponentType.Luminance, |
||||
|
blockContext, |
||||
|
candidateState.EndOfBlock, |
||||
|
this.picture.Parent.FrameHeader.UseReducedTransformSet, |
||||
|
Av1FilterIntraMode.AllFilterIntraModes); |
||||
|
|
||||
|
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, candidateRate, distortion); |
||||
|
if (candidateCost < bestCost) |
||||
|
{ |
||||
|
CopyCandidate( |
||||
|
candidateReconstruction, |
||||
|
candidateCoefficients, |
||||
|
reconstructionPlane, |
||||
|
blockOrigin, |
||||
|
retainedCoefficients, |
||||
|
TransformSize, |
||||
|
candidateState, |
||||
|
ref retainedState); |
||||
|
|
||||
|
for (int row = 0; row < BlockLength; row++) |
||||
|
{ |
||||
|
colorIndexMap.DangerousGetRowSpan(row)[..BlockLength] |
||||
|
.CopyTo(retainedColorIndexMap[(row * BlockLength)..]); |
||||
|
} |
||||
|
|
||||
|
paletteInfo.PaletteSizes[0] = (byte)paletteSize; |
||||
|
paletteInfo.SetColors(Av1Plane.Y, paletteColors); |
||||
|
bestCost = candidateCost; |
||||
|
paletteSelected = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue