mirror of https://github.com/SixLabors/ImageSharp
5 changed files with 568 additions and 9 deletions
@ -0,0 +1,268 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs AV1 palette-predicted sample blocks from decoded color-index maps.
|
|||
/// </summary>
|
|||
internal static class Av1PalettePredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The repeated byte offsets that select both bytes of eight 16-bit palette entries.
|
|||
/// </summary>
|
|||
private const ushort PaletteByteOffsetMultiplier = 0x0202; |
|||
|
|||
/// <summary>
|
|||
/// The high-byte increment that selects the second byte of each 16-bit palette entry.
|
|||
/// </summary>
|
|||
private const ushort PaletteHighByteOffset = 0x0100; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit palette-predicted block.
|
|||
/// </summary>
|
|||
/// <param name="paletteColors">The decoded palette colors in prediction-index order.</param>
|
|||
/// <param name="colorIndexMap">The color-index map beginning at the prediction block origin.</param>
|
|||
/// <param name="colorIndexMapStride">The distance, in indices, between map rows.</param>
|
|||
/// <param name="destination">The destination beginning at the prediction block origin.</param>
|
|||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|||
/// <param name="width">The prediction width in samples.</param>
|
|||
/// <param name="height">The prediction height in samples.</param>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> paletteColors, |
|||
ReadOnlySpan<byte> colorIndexMap, |
|||
int colorIndexMapStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
ref byte mapBase = ref MemoryMarshal.GetReference(colorIndexMap); |
|||
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
// An AV1 palette contains at most eight colors. Packing it once into the low 64 bits and repeating it in
|
|||
// every 128-bit lane turns reconstruction into the lane-local table lookup implemented by ShuffleNative.
|
|||
ulong packedPalette = 0; |
|||
for (int index = 0; index < paletteColors.Length; index++) |
|||
{ |
|||
packedPalette |= (ulong)(byte)paletteColors[index] << (index * 8); |
|||
} |
|||
|
|||
Vector128<byte> palette128 = Vector128.Create(packedPalette, packedPalette).AsByte(); |
|||
|
|||
if (Vector512.IsHardwareAccelerated && width >= Vector512<byte>.Count) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
Vector512<byte> palette512 = Vector512.Create(palette256, palette256); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < width; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> indices = Vector512.LoadUnsafe(ref mapRow, (nuint)column); |
|||
Vector512.ShuffleNative(palette512, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated && width >= Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < width; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> indices = Vector256.LoadUnsafe(ref mapRow, (nuint)column); |
|||
Vector256.ShuffleNative(palette256, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
int column = 0; |
|||
|
|||
for (; column <= width - Vector128<byte>.Count; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> indices = Vector128.LoadUnsafe(ref mapRow, (nuint)column); |
|||
Vector128.ShuffleNative(palette128, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
|
|||
if (column < width) |
|||
{ |
|||
// Transform widths are powers of two. After complete vector chunks, only a four- or eight-byte
|
|||
// row tail remains, so an exact-width load and store keeps neighboring transforms untouched.
|
|||
int remaining = width - column; |
|||
ulong packedIndices = remaining == 4 |
|||
? Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref mapRow, column)) |
|||
: Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref mapRow, column)); |
|||
|
|||
Vector128<byte> result = Vector128.ShuffleNative(palette128, Vector128.CreateScalarUnsafe(packedIndices).AsByte()); |
|||
|
|||
if (remaining == 4) |
|||
{ |
|||
Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationRow, column), result.AsUInt32().ToScalar()); |
|||
} |
|||
else |
|||
{ |
|||
Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationRow, column), result.AsUInt64().ToScalar()); |
|||
} |
|||
|
|||
column = width; |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = (byte)paletteColors[Unsafe.Add(ref mapRow, column)]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth palette-predicted block.
|
|||
/// </summary>
|
|||
/// <param name="paletteColors">The decoded palette colors in prediction-index order.</param>
|
|||
/// <param name="colorIndexMap">The color-index map beginning at the prediction block origin.</param>
|
|||
/// <param name="colorIndexMapStride">The distance, in indices, between map rows.</param>
|
|||
/// <param name="destination">The destination beginning at the prediction block origin.</param>
|
|||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|||
/// <param name="width">The prediction width in samples.</param>
|
|||
/// <param name="height">The prediction height in samples.</param>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> paletteColors, |
|||
ReadOnlySpan<byte> colorIndexMap, |
|||
int colorIndexMapStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
ref byte mapBase = ref MemoryMarshal.GetReference(colorIndexMap); |
|||
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
InlineArray8<ushort> paletteStorage = default; |
|||
|
|||
paletteColors.CopyTo(paletteStorage); |
|||
|
|||
// Each index is expanded to the byte offsets 2n and 2n+1. Repeating the complete 16-byte palette in every
|
|||
// 128-bit lane then permits the same native byte-table shuffle on x86, Arm, and WebAssembly.
|
|||
Vector128<byte> palette128 = Vector128.LoadUnsafe(ref paletteStorage[0]).AsByte(); |
|||
|
|||
if (Vector512.IsHardwareAccelerated && width >= Vector512<ushort>.Count) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
Vector512<byte> palette512 = Vector512.Create(palette256, palette256); |
|||
Vector512<ushort> multiplier = Vector512.Create(PaletteByteOffsetMultiplier); |
|||
Vector512<ushort> increment = Vector512.Create(PaletteHighByteOffset); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < width; column += Vector512<ushort>.Count) |
|||
{ |
|||
(Vector256<ushort> lower, Vector256<ushort> upper) = Vector256.Widen(Vector256.LoadUnsafe(ref mapRow, (nuint)column)); |
|||
|
|||
Vector512<ushort> indices = Vector512.Create(lower, upper); |
|||
Vector512<byte> controls = ((indices * multiplier) + increment).AsByte(); |
|||
Vector512.ShuffleNative(palette512, controls).AsInt16().StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated && width >= Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
Vector256<ushort> multiplier = Vector256.Create(PaletteByteOffsetMultiplier); |
|||
Vector256<ushort> increment = Vector256.Create(PaletteHighByteOffset); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < width; column += Vector256<ushort>.Count) |
|||
{ |
|||
(Vector128<ushort> lower, Vector128<ushort> upper) = Vector128.Widen(Vector128.LoadUnsafe(ref mapRow, (nuint)column)); |
|||
|
|||
Vector256<ushort> indices = Vector256.Create(lower, upper); |
|||
Vector256<byte> controls = ((indices * multiplier) + increment).AsByte(); |
|||
Vector256.ShuffleNative(palette256, controls).AsInt16().StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<ushort> multiplier = Vector128.Create(PaletteByteOffsetMultiplier); |
|||
Vector128<ushort> increment = Vector128.Create(PaletteHighByteOffset); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
int column = 0; |
|||
|
|||
for (; column <= width - Vector128<ushort>.Count; column += Vector128<ushort>.Count) |
|||
{ |
|||
ulong packedIndices = Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref mapRow, column)); |
|||
Vector128<ushort> indices = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packedIndices).AsByte()); |
|||
Vector128<byte> controls = ((indices * multiplier) + increment).AsByte(); |
|||
Vector128.ShuffleNative(palette128, controls).AsInt16().StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
|
|||
if (column < width) |
|||
{ |
|||
uint packedIndices = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref mapRow, column)); |
|||
Vector128<ushort> indices = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packedIndices).AsByte()); |
|||
Vector128<byte> controls = ((indices * multiplier) + increment).AsByte(); |
|||
Vector128<short> result = Vector128.ShuffleNative(palette128, controls).AsInt16(); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<short, byte>(ref Unsafe.Add(ref destinationRow, column)), result.AsUInt64().ToScalar()); |
|||
column += 4; |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = (short)paletteColors[Unsafe.Add(ref mapRow, column)]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,149 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
using BenchmarkDotNet.Columns; |
|||
using BenchmarkDotNet.Configs; |
|||
using BenchmarkDotNet.Jobs; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif; |
|||
|
|||
/// <summary>
|
|||
/// Measures frame-wide AV1 palette reconstruction at each available intrinsic tier.
|
|||
/// </summary>
|
|||
[Config(typeof(Configuration))] |
|||
[MemoryDiagnoser(displayGenColumns: false)] |
|||
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] |
|||
[CategoriesColumn] |
|||
public class Av1PalettePredictionBenchmarks |
|||
{ |
|||
/// <summary>
|
|||
/// The coded frame width, which is an exact multiple of the maximum palette block side.
|
|||
/// </summary>
|
|||
private const int Width = 1920; |
|||
|
|||
/// <summary>
|
|||
/// The coded frame height including the final padded coding-tree row for a 1080-line presentation.
|
|||
/// </summary>
|
|||
private const int Height = 1088; |
|||
|
|||
/// <summary>
|
|||
/// The maximum AV1 palette block side in samples.
|
|||
/// </summary>
|
|||
private const int BlockSize = 64; |
|||
|
|||
/// <summary>
|
|||
/// The eight-entry 8-bit palette reused by each benchmark block.
|
|||
/// </summary>
|
|||
private readonly ushort[] palette8 = [3, 37, 71, 109, 143, 181, 217, 251]; |
|||
|
|||
/// <summary>
|
|||
/// The eight-entry 12-bit palette reused by each benchmark block.
|
|||
/// </summary>
|
|||
private readonly ushort[] palette12 = [17, 509, 1001, 1493, 1985, 2477, 2969, 4095]; |
|||
|
|||
/// <summary>
|
|||
/// The decoded color-index map for one maximum-size palette block.
|
|||
/// </summary>
|
|||
private readonly byte[] colorIndexMap = new byte[BlockSize * BlockSize]; |
|||
|
|||
/// <summary>
|
|||
/// The frame-wide 8-bit reconstruction surface.
|
|||
/// </summary>
|
|||
private readonly byte[] destination8 = new byte[Width * Height]; |
|||
|
|||
/// <summary>
|
|||
/// The frame-wide 12-bit reconstruction surface.
|
|||
/// </summary>
|
|||
private readonly short[] destination12 = new short[Width * Height]; |
|||
|
|||
/// <summary>
|
|||
/// Populates a deterministic, spatially varying color-index map outside the measured traversal.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
for (int row = 0; row < BlockSize; row++) |
|||
{ |
|||
for (int column = 0; column < BlockSize; column++) |
|||
{ |
|||
this.colorIndexMap[(row * BlockSize) + column] = (byte)(((row * 5) + (column * 3)) & 7); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures frame-wide 8-bit palette reconstruction.
|
|||
/// </summary>
|
|||
/// <returns>The final reconstructed sample, keeping the frame output observable.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("8Bit")] |
|||
public byte Predict8BitFrame() |
|||
{ |
|||
for (int row = 0; row < Height; row += BlockSize) |
|||
{ |
|||
for (int column = 0; column < Width; column += BlockSize) |
|||
{ |
|||
Av1PalettePredictor.Predict(this.palette8, this.colorIndexMap, BlockSize, this.destination8.AsSpan((row * Width) + column), Width, BlockSize, BlockSize); |
|||
} |
|||
} |
|||
|
|||
return this.destination8[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures frame-wide 12-bit palette reconstruction.
|
|||
/// </summary>
|
|||
/// <returns>The final reconstructed sample, keeping the frame output observable.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("12Bit")] |
|||
public short Predict12BitFrame() |
|||
{ |
|||
for (int row = 0; row < Height; row += BlockSize) |
|||
{ |
|||
for (int column = 0; column < Width; column += BlockSize) |
|||
{ |
|||
Av1PalettePredictor.Predict(this.palette12, this.colorIndexMap, BlockSize, this.destination12.AsSpan((row * Width) + column), Width, BlockSize, BlockSize); |
|||
} |
|||
} |
|||
|
|||
return this.destination12[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Configures production-process measurements for hardware, forced Vector512, AVX2, Vector128, and scalar paths.
|
|||
/// </summary>
|
|||
public sealed class Configuration : ManualConfig |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Configuration"/> class.
|
|||
/// </summary>
|
|||
public Configuration() |
|||
{ |
|||
this.AddJob(Job.ShortRun.WithId("Hardware").AsBaseline()); |
|||
|
|||
this.AddJob( |
|||
Job.ShortRun |
|||
.WithId("Vector512") |
|||
.WithEnvironmentVariable("DOTNET_PreferredVectorBitWidth", "512") |
|||
.WithEnvironmentVariable("COMPlus_PreferredVectorBitWidth", "512")); |
|||
|
|||
this.AddJob( |
|||
Job.ShortRun |
|||
.WithId("Avx2") |
|||
.WithEnvironmentVariable("DOTNET_EnableAVX512F", "0")); |
|||
|
|||
this.AddJob( |
|||
Job.ShortRun |
|||
.WithId("Vector128") |
|||
.WithEnvironmentVariable("DOTNET_EnableAVX512F", "0") |
|||
.WithEnvironmentVariable("DOTNET_EnableAVX2", "0")); |
|||
|
|||
this.AddJob( |
|||
Job.ShortRun |
|||
.WithId("Scalar") |
|||
.WithEnvironmentVariable("DOTNET_EnableHWIntrinsic", "0")); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
|||
|
|||
/// <summary>
|
|||
/// Verifies AV1 palette reconstruction across every supported sample precision and intrinsic tier.
|
|||
/// </summary>
|
|||
[Trait("Format", "Avif")] |
|||
public class Av1PalettePredictorTests |
|||
{ |
|||
/// <summary>
|
|||
/// The hardware configurations required to exercise each packed width and the scalar fallback.
|
|||
/// </summary>
|
|||
private const HwIntrinsics Configurations = |
|||
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic; |
|||
|
|||
/// <summary>
|
|||
/// Verifies exact indexed reconstruction and destination-padding preservation for every palette size.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void PredictMatchesIndependentDefinitionAcrossIntrinsicWidths() |
|||
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePredictors, Configurations); |
|||
|
|||
/// <summary>
|
|||
/// Exercises all palette sizes and transform widths at 8, 10, and 12 bits.
|
|||
/// </summary>
|
|||
private static void ValidatePredictors() |
|||
{ |
|||
int[] widths = [4, 8, 16, 32, 64]; |
|||
foreach (int paletteSize in Enumerable.Range(2, Av1Constants.PaletteMaxSize - 1)) |
|||
{ |
|||
foreach (int width in widths) |
|||
{ |
|||
int height = width == 64 ? 16 : width; |
|||
int mapStride = width + 5; |
|||
int destinationStride = width + 9; |
|||
byte[] colorIndexMap = CreateColorIndexMap(mapStride, height, width, paletteSize); |
|||
ushort[] bytePalette = CreatePalette(paletteSize, 8); |
|||
byte[] expectedBytes = Enumerable.Repeat((byte)251, destinationStride * height).ToArray(); |
|||
byte[] actualBytes = (byte[])expectedBytes.Clone(); |
|||
|
|||
ApplyReference(bytePalette, colorIndexMap, mapStride, expectedBytes, destinationStride, width, height); |
|||
Av1PalettePredictor.Predict(bytePalette, colorIndexMap, mapStride, actualBytes, destinationStride, width, height); |
|||
Assert.Equal(expectedBytes, actualBytes); |
|||
|
|||
foreach (int bitDepth in new[] { 10, 12 }) |
|||
{ |
|||
ushort[] palette = CreatePalette(paletteSize, bitDepth); |
|||
short[] expected = Enumerable.Repeat((short)-1, destinationStride * height).ToArray(); |
|||
short[] actual = (short[])expected.Clone(); |
|||
|
|||
ApplyReference(palette, colorIndexMap, mapStride, expected, destinationStride, width, height); |
|||
Av1PalettePredictor.Predict(palette, colorIndexMap, mapStride, actual, destinationStride, width, height); |
|||
Assert.Equal(expected, actual); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a deterministic palette spanning the legal range for the requested bit depth.
|
|||
/// </summary>
|
|||
private static ushort[] CreatePalette(int paletteSize, int bitDepth) |
|||
{ |
|||
ushort[] result = new ushort[paletteSize]; |
|||
int maximum = (1 << bitDepth) - 1; |
|||
for (int index = 0; index < result.Length; index++) |
|||
{ |
|||
result[index] = (ushort)(((index * 977) + 37) & maximum); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates deterministic active indices and invalid padding indices for each map row.
|
|||
/// </summary>
|
|||
private static byte[] CreateColorIndexMap(int stride, int height, int width, int paletteSize) |
|||
{ |
|||
byte[] result = Enumerable.Repeat((byte)Av1Constants.PaletteMaxSize, stride * height).ToArray(); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
result[(row * stride) + column] = (byte)(((row * 5) + (column * 3)) % paletteSize); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies independent scalar palette lookup to an 8-bit destination.
|
|||
/// </summary>
|
|||
private static void ApplyReference( |
|||
ReadOnlySpan<ushort> palette, |
|||
ReadOnlySpan<byte> colorIndexMap, |
|||
int mapStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
destination[(row * destinationStride) + column] = (byte)palette[colorIndexMap[(row * mapStride) + column]]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies independent scalar palette lookup to a high-bit-depth destination.
|
|||
/// </summary>
|
|||
private static void ApplyReference( |
|||
ReadOnlySpan<ushort> palette, |
|||
ReadOnlySpan<byte> colorIndexMap, |
|||
int mapStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
destination[(row * destinationStride) + column] = (short)palette[colorIndexMap[(row * mapStride) + column]]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue