mirror of https://github.com/SixLabors/ImageSharp
14 changed files with 850 additions and 24 deletions
@ -0,0 +1,81 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; |
|||
|
|||
/// <content>
|
|||
/// Defines the sample-storage operations used by single-tile intra encoding.
|
|||
/// </content>
|
|||
internal sealed partial class Av1IntraTileWriter |
|||
{ |
|||
/// <summary>
|
|||
/// Defines type-specific superblock encoding without coupling tile traversal to sample storage width.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSample">The native unsigned sample storage type.</typeparam>
|
|||
private interface ITileEncodingOperator<TSample> |
|||
where TSample : unmanaged |
|||
{ |
|||
/// <summary>
|
|||
/// Encodes and reconstructs one superblock.
|
|||
/// </summary>
|
|||
/// <param name="source">The coded source frame.</param>
|
|||
/// <param name="reconstruction">The reconstructed frame updated by the block transforms.</param>
|
|||
/// <param name="picture">The frame coding and mode-information state.</param>
|
|||
/// <param name="superblock">The reusable partition and final-block decisions.</param>
|
|||
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
|||
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
|||
public static abstract void EncodeSuperblock( |
|||
Av1EncoderFrame<TSample> source, |
|||
Av1EncoderFrame<TSample> reconstruction, |
|||
Av1PictureControlSet picture, |
|||
Av1Superblock superblock, |
|||
Av1EncoderCoefficientBuffer coefficientBuffer, |
|||
Av1EncoderBlockWorkspace blockWorkspace); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes tiles stored as eight-bit samples.
|
|||
/// </summary>
|
|||
private readonly struct ByteOperator : ITileEncodingOperator<byte> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void EncodeSuperblock( |
|||
Av1EncoderFrame<byte> source, |
|||
Av1EncoderFrame<byte> reconstruction, |
|||
Av1PictureControlSet picture, |
|||
Av1Superblock superblock, |
|||
Av1EncoderCoefficientBuffer coefficientBuffer, |
|||
Av1EncoderBlockWorkspace blockWorkspace) |
|||
=> Av1IntraSuperblockEncoder.Encode( |
|||
source, |
|||
reconstruction, |
|||
picture, |
|||
superblock, |
|||
coefficientBuffer, |
|||
blockWorkspace); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes tiles stored as high-bit-depth samples.
|
|||
/// </summary>
|
|||
private readonly struct UInt16Operator : ITileEncodingOperator<ushort> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void EncodeSuperblock( |
|||
Av1EncoderFrame<ushort> source, |
|||
Av1EncoderFrame<ushort> reconstruction, |
|||
Av1PictureControlSet picture, |
|||
Av1Superblock superblock, |
|||
Av1EncoderCoefficientBuffer coefficientBuffer, |
|||
Av1EncoderBlockWorkspace blockWorkspace) |
|||
=> Av1IntraSuperblockEncoder.Encode( |
|||
source, |
|||
reconstruction, |
|||
picture, |
|||
superblock, |
|||
coefficientBuffer, |
|||
blockWorkspace); |
|||
} |
|||
} |
|||
@ -0,0 +1,176 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; |
|||
|
|||
/// <summary>
|
|||
/// Encodes and owns one range-coded all-intra tile payload.
|
|||
/// </summary>
|
|||
internal sealed partial class Av1IntraTileWriter : IAv1TileWriter, IDisposable |
|||
{ |
|||
private IMemoryOwner<byte>? tileData; |
|||
private readonly int tileDataLength; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1IntraTileWriter"/> class for eight-bit samples.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing tile output memory.</param>
|
|||
/// <param name="source">The coded source frame.</param>
|
|||
/// <param name="reconstruction">The reconstructed frame updated during encoding.</param>
|
|||
/// <param name="picture">The frame coding and mode-information state.</param>
|
|||
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
|||
/// <param name="superblockWorkspace">The reusable partition and final-block decision workspace.</param>
|
|||
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
|||
/// <param name="initialSize">The estimated encoded tile size in bytes.</param>
|
|||
public Av1IntraTileWriter( |
|||
Configuration configuration, |
|||
Av1EncoderFrame<byte> source, |
|||
Av1EncoderFrame<byte> reconstruction, |
|||
Av1PictureControlSet picture, |
|||
Av1EncoderCoefficientBuffer coefficientBuffer, |
|||
Av1EncoderSuperblockWorkspace superblockWorkspace, |
|||
Av1EncoderBlockWorkspace blockWorkspace, |
|||
int initialSize) |
|||
{ |
|||
this.tileData = Encode<byte, ByteOperator>( |
|||
configuration, |
|||
source, |
|||
reconstruction, |
|||
picture, |
|||
coefficientBuffer, |
|||
superblockWorkspace, |
|||
blockWorkspace, |
|||
initialSize, |
|||
out this.tileDataLength); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1IntraTileWriter"/> class for high-bit-depth samples.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing tile output memory.</param>
|
|||
/// <param name="source">The coded source frame.</param>
|
|||
/// <param name="reconstruction">The reconstructed frame updated during encoding.</param>
|
|||
/// <param name="picture">The frame coding and mode-information state.</param>
|
|||
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
|||
/// <param name="superblockWorkspace">The reusable partition and final-block decision workspace.</param>
|
|||
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
|||
/// <param name="initialSize">The estimated encoded tile size in bytes.</param>
|
|||
public Av1IntraTileWriter( |
|||
Configuration configuration, |
|||
Av1EncoderFrame<ushort> source, |
|||
Av1EncoderFrame<ushort> reconstruction, |
|||
Av1PictureControlSet picture, |
|||
Av1EncoderCoefficientBuffer coefficientBuffer, |
|||
Av1EncoderSuperblockWorkspace superblockWorkspace, |
|||
Av1EncoderBlockWorkspace blockWorkspace, |
|||
int initialSize) |
|||
{ |
|||
this.tileData = Encode<ushort, UInt16Operator>( |
|||
configuration, |
|||
source, |
|||
reconstruction, |
|||
picture, |
|||
coefficientBuffer, |
|||
superblockWorkspace, |
|||
blockWorkspace, |
|||
initialSize, |
|||
out this.tileDataLength); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public ReadOnlySpan<byte> GetTileData(int tileNum) |
|||
{ |
|||
ObjectDisposedException.ThrowIf(this.tileData is null, this); |
|||
return this.tileData.Memory.Span[..this.tileDataLength]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the detached range-coded tile allocation to the configured allocator.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.tileData?.Dispose(); |
|||
this.tileData = null; |
|||
} |
|||
|
|||
private static IMemoryOwner<byte> Encode<TSample, TOperator>( |
|||
Configuration configuration, |
|||
Av1EncoderFrame<TSample> source, |
|||
Av1EncoderFrame<TSample> reconstruction, |
|||
Av1PictureControlSet picture, |
|||
Av1EncoderCoefficientBuffer coefficientBuffer, |
|||
Av1EncoderSuperblockWorkspace superblockWorkspace, |
|||
Av1EncoderBlockWorkspace blockWorkspace, |
|||
int initialSize, |
|||
out int tileDataLength) |
|||
where TSample : unmanaged |
|||
where TOperator : struct, ITileEncodingOperator<TSample> |
|||
{ |
|||
ObuFrameHeader frameHeader = picture.Parent.FrameHeader; |
|||
ObuSequenceHeader sequenceHeader = picture.Sequence.SequenceHeader; |
|||
const ushort TileIndex = 0; |
|||
Av1TileInfo tile = new(0, 0, frameHeader); |
|||
Av1Superblock superblock = new() |
|||
{ |
|||
Workspace = superblockWorkspace, |
|||
TileInfo = tile |
|||
}; |
|||
|
|||
Point firstModeInfoPosition = new(tile.ModeInfoColumnStart, tile.ModeInfoRowStart); |
|||
Av1TileWriter.Av1EntropyCodingContext entropyContext = new() |
|||
{ |
|||
MacroBlock = new Av1MacroBlockD { Tile = tile }, |
|||
MacroBlockModeInfo = picture.GetMacroBlockModeInfo(firstModeInfoPosition) |
|||
}; |
|||
|
|||
using Av1SymbolEncoder writer = new( |
|||
configuration, |
|||
initialSize, |
|||
frameHeader.QuantizationParameters.BaseQIndex, |
|||
updateCdf: !frameHeader.DisableCdfUpdate); |
|||
|
|||
int superblockModeInfoSize = sequenceHeader.SuperblockModeInfoSize; |
|||
int superblockShift = sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; |
|||
for (int modeInfoRow = tile.ModeInfoRowStart; |
|||
modeInfoRow < tile.ModeInfoRowEnd; |
|||
modeInfoRow += superblockModeInfoSize) |
|||
{ |
|||
for (int modeInfoColumn = tile.ModeInfoColumnStart; |
|||
modeInfoColumn < tile.ModeInfoColumnEnd; |
|||
modeInfoColumn += superblockModeInfoSize) |
|||
{ |
|||
int superblockRow = modeInfoRow >> superblockShift; |
|||
int superblockColumn = modeInfoColumn >> superblockShift; |
|||
superblock.Index = (superblockRow * coefficientBuffer.SuperblockColumnCount) + superblockColumn; |
|||
entropyContext.SuperblockOrigin = new Point( |
|||
modeInfoColumn << Av1Constants.ModeInfoSizeLog2, |
|||
modeInfoRow << Av1Constants.ModeInfoSizeLog2); |
|||
|
|||
// Analyze immediately before entropy coding so the reusable decision workspace and reconstructed
|
|||
// neighbors remain synchronized without a second superblock-sized decision allocation.
|
|||
TOperator.EncodeSuperblock( |
|||
source, |
|||
reconstruction, |
|||
picture, |
|||
superblock, |
|||
coefficientBuffer, |
|||
blockWorkspace); |
|||
|
|||
Av1TileWriter.WriteSuperblock( |
|||
picture, |
|||
entropyContext, |
|||
writer, |
|||
superblock, |
|||
coefficientBuffer, |
|||
TileIndex); |
|||
} |
|||
} |
|||
|
|||
return writer.Exit(out tileDataLength); |
|||
} |
|||
} |
|||
@ -0,0 +1,200 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
/// <summary>
|
|||
/// Owns mode information, segmentation data, and tile-neighbor contexts for one encoded AV1 picture.
|
|||
/// </summary>
|
|||
internal sealed class Av1EncoderPictureBuffer : IDisposable |
|||
{ |
|||
private readonly Av1EncoderModeInfoBuffer modeInfo; |
|||
private readonly IMemoryOwner<byte> stateStorage; |
|||
private readonly ByteMemoryManager<Av1PartitionContext> partitionContextMemory; |
|||
private readonly Av1NeighborArrayUnit<Av1PartitionContext>[] partitionContexts; |
|||
private readonly Av1NeighborArrayUnit<byte>[] lumaCoefficientContexts; |
|||
private readonly Av1NeighborArrayUnit<byte>[] blueCoefficientContexts; |
|||
private readonly Av1NeighborArrayUnit<byte>[] redCoefficientContexts; |
|||
private readonly Av1NeighborArrayUnit<byte>[] transformContexts; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1EncoderPictureBuffer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing picture-lifetime memory.</param>
|
|||
/// <param name="sequenceHeader">The sequence header defining superblock and chroma geometry.</param>
|
|||
/// <param name="frameHeader">The frame header defining dimensions and tiles.</param>
|
|||
/// <param name="width">The visible luma width.</param>
|
|||
/// <param name="height">The visible luma height.</param>
|
|||
public Av1EncoderPictureBuffer( |
|||
Configuration configuration, |
|||
ObuSequenceHeader sequenceHeader, |
|||
ObuFrameHeader frameHeader, |
|||
int width, |
|||
int height) |
|||
{ |
|||
const int ContextAlignmentLog2 = Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2; |
|||
this.modeInfo = new Av1EncoderModeInfoBuffer( |
|||
configuration, |
|||
width, |
|||
height, |
|||
disallow4x4AllFrames: true); |
|||
|
|||
int alignedModeInfoRowCount = Av1Math.AlignPowerOf2(this.modeInfo.ModeInfoRowCount, ContextAlignmentLog2); |
|||
int lumaLeftLength = alignedModeInfoRowCount; |
|||
int lumaTopLength = this.modeInfo.ModeInfoStride; |
|||
ObuColorConfig colorConfig = sequenceHeader.ColorConfig; |
|||
int chromaLeftLength = colorConfig.IsMonochrome |
|||
? 0 |
|||
: lumaLeftLength >> (colorConfig.SubSamplingY ? 1 : 0); |
|||
|
|||
int chromaTopLength = colorConfig.IsMonochrome |
|||
? 0 |
|||
: lumaTopLength >> (colorConfig.SubSamplingX ? 1 : 0); |
|||
|
|||
int tileCount = frameHeader.TilesInfo.TileColumnCount * frameHeader.TilesInfo.TileRowCount; |
|||
int lumaContextLength = checked(lumaLeftLength + lumaTopLength); |
|||
int chromaContextLength = checked(chromaLeftLength + chromaTopLength); |
|||
int byteContextLengthPerTile = checked((2 * lumaContextLength) + (2 * chromaContextLength)); |
|||
int partitionContextLength = checked(tileCount * lumaContextLength); |
|||
int segmentationLength = checked(this.modeInfo.ModeInfoColumnCount * this.modeInfo.ModeInfoRowCount); |
|||
int partitionContextSize = Unsafe.SizeOf<Av1PartitionContext>(); |
|||
int partitionStorageOffset = checked( |
|||
((segmentationLength + partitionContextSize - 1) / partitionContextSize) * partitionContextSize); |
|||
|
|||
int partitionStorageLength = checked( |
|||
partitionContextLength * Unsafe.SizeOf<Av1PartitionContext>()); |
|||
|
|||
int byteContextStorageOffset = checked(partitionStorageOffset + partitionStorageLength); |
|||
int stateStorageLength = checked(byteContextStorageOffset + (tileCount * byteContextLengthPerTile)); |
|||
|
|||
// Segmentation and every tile edge share one clean picture lifetime. The partition region begins at its
|
|||
// native alignment, while typed views keep the entropy writer independent from the packed byte owner.
|
|||
this.stateStorage = configuration.MemoryAllocator.Allocate<byte>( |
|||
stateStorageLength, |
|||
AllocationOptions.Clean); |
|||
|
|||
Memory<byte> stateStorage = this.stateStorage.Memory[..stateStorageLength]; |
|||
this.partitionContextMemory = new ByteMemoryManager<Av1PartitionContext>( |
|||
stateStorage.Slice(partitionStorageOffset, partitionStorageLength)); |
|||
|
|||
Memory<Av1PartitionContext> partitionStorage = this.partitionContextMemory.Memory; |
|||
Memory<byte> byteContextStorage = stateStorage[byteContextStorageOffset..]; |
|||
this.partitionContexts = new Av1NeighborArrayUnit<Av1PartitionContext>[tileCount]; |
|||
this.lumaCoefficientContexts = new Av1NeighborArrayUnit<byte>[tileCount]; |
|||
this.blueCoefficientContexts = new Av1NeighborArrayUnit<byte>[tileCount]; |
|||
this.redCoefficientContexts = new Av1NeighborArrayUnit<byte>[tileCount]; |
|||
this.transformContexts = new Av1NeighborArrayUnit<byte>[tileCount]; |
|||
int[][] cdefPreset = new int[tileCount][]; |
|||
int[] previousQIndex = new int[tileCount]; |
|||
for (int tileIndex = 0; tileIndex < tileCount; tileIndex++) |
|||
{ |
|||
this.partitionContexts[tileIndex] = new Av1NeighborArrayUnit<Av1PartitionContext>( |
|||
partitionStorage.Slice(tileIndex * lumaContextLength, lumaContextLength), |
|||
lumaLeftLength, |
|||
lumaTopLength) |
|||
{ |
|||
GranularityNormalLog2 = Av1Constants.ModeInfoSizeLog2 |
|||
}; |
|||
|
|||
int byteContextOffset = tileIndex * byteContextLengthPerTile; |
|||
this.lumaCoefficientContexts[tileIndex] = new Av1NeighborArrayUnit<byte>( |
|||
byteContextStorage.Slice(byteContextOffset, lumaContextLength), |
|||
lumaLeftLength, |
|||
lumaTopLength) |
|||
{ |
|||
GranularityNormalLog2 = Av1Constants.ModeInfoSizeLog2 |
|||
}; |
|||
|
|||
byteContextOffset += lumaContextLength; |
|||
this.blueCoefficientContexts[tileIndex] = new Av1NeighborArrayUnit<byte>( |
|||
byteContextStorage.Slice(byteContextOffset, chromaContextLength), |
|||
chromaLeftLength, |
|||
chromaTopLength) |
|||
{ |
|||
GranularityNormalLog2 = Av1Constants.ModeInfoSizeLog2 |
|||
}; |
|||
|
|||
byteContextOffset += chromaContextLength; |
|||
this.redCoefficientContexts[tileIndex] = new Av1NeighborArrayUnit<byte>( |
|||
byteContextStorage.Slice(byteContextOffset, chromaContextLength), |
|||
chromaLeftLength, |
|||
chromaTopLength) |
|||
{ |
|||
GranularityNormalLog2 = Av1Constants.ModeInfoSizeLog2 |
|||
}; |
|||
|
|||
byteContextOffset += chromaContextLength; |
|||
this.transformContexts[tileIndex] = new Av1NeighborArrayUnit<byte>( |
|||
byteContextStorage.Slice(byteContextOffset, lumaContextLength), |
|||
lumaLeftLength, |
|||
lumaTopLength) |
|||
{ |
|||
GranularityNormalLog2 = Av1Constants.ModeInfoSizeLog2 |
|||
}; |
|||
|
|||
cdefPreset[tileIndex] = [-1, -1, -1, -1]; |
|||
previousQIndex[tileIndex] = frameHeader.QuantizationParameters.BaseQIndex; |
|||
} |
|||
|
|||
this.Picture = new Av1PictureControlSet |
|||
{ |
|||
PartitionContexts = this.partitionContexts, |
|||
LuminanceDcSignLevelCoefficientNeighbors = this.lumaCoefficientContexts, |
|||
CbDcSignLevelCoefficientNeighbors = this.blueCoefficientContexts, |
|||
CrDcSignLevelCoefficientNeighbors = this.redCoefficientContexts, |
|||
TransformFunctionContexts = this.transformContexts, |
|||
Sequence = new Av1SequenceControlSet { SequenceHeader = sequenceHeader }, |
|||
Parent = new Av1PictureParentControlSet |
|||
{ |
|||
Common = new Av1EncoderCommon |
|||
{ |
|||
ModeInfoColumnCount = this.modeInfo.ModeInfoColumnCount, |
|||
ModeInfoRowCount = this.modeInfo.ModeInfoRowCount, |
|||
ModeInfoStride = this.modeInfo.ModeInfoStride, |
|||
FrameSize = frameHeader.FrameSize, |
|||
TilesInfo = frameHeader.TilesInfo |
|||
}, |
|||
FrameHeader = frameHeader, |
|||
PreviousQIndex = previousQIndex |
|||
}, |
|||
SegmentationNeighborMap = stateStorage[..segmentationLength], |
|||
ModeInfoGrid = this.modeInfo.Grid, |
|||
ModeInfoAllocation = this.modeInfo.Allocation, |
|||
ModeInfoStride = this.modeInfo.ModeInfoStride, |
|||
Disallow4x4AllFrames = this.modeInfo.Disallow4x4AllFrames, |
|||
CdefPreset = cdefPreset |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the non-owning picture state consumed by superblock analysis and tile writing.
|
|||
/// </summary>
|
|||
public Av1PictureControlSet Picture { get; } |
|||
|
|||
/// <summary>
|
|||
/// Returns every picture-lifetime allocation to the configured allocator.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
foreach (Av1NeighborArrayUnit<Av1PartitionContext> context in this.partitionContexts) |
|||
{ |
|||
context.Dispose(); |
|||
} |
|||
|
|||
for (int tileIndex = 0; tileIndex < this.lumaCoefficientContexts.Length; tileIndex++) |
|||
{ |
|||
this.lumaCoefficientContexts[tileIndex].Dispose(); |
|||
this.blueCoefficientContexts[tileIndex].Dispose(); |
|||
this.redCoefficientContexts[tileIndex].Dispose(); |
|||
this.transformContexts[tileIndex].Dispose(); |
|||
} |
|||
|
|||
this.stateStorage.Dispose(); |
|||
this.modeInfo.Dispose(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue