mirror of https://github.com/SixLabors/ImageSharp
6 changed files with 718 additions and 0 deletions
@ -0,0 +1,27 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; |
|||
|
|||
internal static class Av1DefaultDistributions |
|||
{ |
|||
public static uint[] YMode => [Av1SymbolReader.CdfProbabilityTop, 0]; |
|||
|
|||
public static uint[] UvModeCflNotAllowed => [Av1SymbolReader.CdfProbabilityTop, 0]; |
|||
|
|||
public static uint[] UvModeCflAllowed => [Av1SymbolReader.CdfProbabilityTop, 0]; |
|||
|
|||
public static uint[] AngleDelta => [Av1SymbolReader.CdfProbabilityTop, 0]; |
|||
|
|||
public static uint[] IntraBlockCopy => [30531, 0, 0]; |
|||
|
|||
public static uint[] PartitionWidth8 => [Av1SymbolReader.CdfProbabilityTop, 0]; |
|||
|
|||
public static uint[] PartitionWidth16 => [Av1SymbolReader.CdfProbabilityTop, 0]; |
|||
|
|||
public static uint[] PartitionWidth32 => [Av1SymbolReader.CdfProbabilityTop, 0]; |
|||
|
|||
public static uint[] PartitionWidth64 => [Av1SymbolReader.CdfProbabilityTop, 0]; |
|||
|
|||
public static uint[] SegmentId => [Av1SymbolReader.CdfProbabilityTop, 0]; |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; |
|||
|
|||
internal class Av1SymbolDecoder |
|||
{ |
|||
private readonly uint[] tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; |
|||
|
|||
public bool ReadUseIntraBlockCopySymbol(ref Av1SymbolReader reader) |
|||
=> reader.ReadSymbol(this.tileIntraBlockCopy, 2) > 0; |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; |
|||
|
|||
internal class Av1SymbolEncoder |
|||
{ |
|||
private readonly uint[] tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; |
|||
|
|||
public void WriteUseIntraBlockCopySymbol(Av1SymbolWriter writer, bool value) |
|||
=> writer.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy, 2); |
|||
} |
|||
@ -0,0 +1,243 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; |
|||
|
|||
internal ref struct Av1SymbolReader |
|||
{ |
|||
internal const int CdfProbabilityTop = 1 << CdfProbabilityBitCount; |
|||
internal const int ProbabilityMinimum = 4; |
|||
internal const int CdfShift = 15 - CdfProbabilityBitCount; |
|||
internal const int ProbabilityShift = 6; |
|||
internal static readonly int[] NumberOfSymbols2Speed = [0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]; |
|||
|
|||
private const int CdfProbabilityBitCount = 15; |
|||
private const int DecoderWindowsSize = 32; |
|||
private const int LotsOfBits = 0x4000; |
|||
|
|||
private readonly Span<byte> buffer; |
|||
private int position; |
|||
|
|||
/* |
|||
* The difference between the high end of the current range, (low + rng), and |
|||
* the coded value, minus 1. |
|||
* This stores up to OD_EC_WINDOW_SIZE bits of that difference, but the |
|||
* decoder only uses the top 16 bits of the window to decode the next symbol. |
|||
* As we shift up during renormalization, if we don't have enough bits left in |
|||
* the window to fill the top 16, we'll read in more bits of the coded |
|||
* value. |
|||
*/ |
|||
private uint difference; |
|||
|
|||
// The number of values in the current range.
|
|||
private uint range; |
|||
|
|||
// The number of bits in the current value.
|
|||
private int count; |
|||
|
|||
public Av1SymbolReader(Span<byte> span) |
|||
{ |
|||
this.buffer = span; |
|||
this.position = 0; |
|||
this.difference = (1U << (DecoderWindowsSize - 1)) - 1; |
|||
this.range = 0x8000; |
|||
this.count = -15; |
|||
this.Refill(); |
|||
} |
|||
|
|||
public int ReadSymbol(uint[] probabilities, int numberOfSymbols) |
|||
{ |
|||
int value = this.DecodeIntegerQ15(probabilities, numberOfSymbols); |
|||
UpdateCdf(probabilities, value, numberOfSymbols); |
|||
return value; |
|||
} |
|||
|
|||
public int ReadLiteral(int bitCount) |
|||
{ |
|||
const uint prob = (0x7FFFFFU - (128 << 15) + 128) >> 8; |
|||
int literal = 0; |
|||
for (int bit = bitCount - 1; bit >= 0; bit--) |
|||
{ |
|||
if (this.DecodeBoolQ15(prob)) |
|||
{ |
|||
literal |= 1 << bit; |
|||
} |
|||
} |
|||
|
|||
return literal; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decode a single binary value.
|
|||
/// </summary>
|
|||
/// <param name="frequency">The probability that the bit is one, scaled by 32768.</param>
|
|||
private bool DecodeBoolQ15(uint frequency) |
|||
{ |
|||
uint dif; |
|||
uint vw; |
|||
uint range; |
|||
uint newRange; |
|||
uint v; |
|||
bool ret; |
|||
|
|||
// assert(0 < f);
|
|||
// assert(f < 32768U);
|
|||
dif = this.difference; |
|||
range = this.range; |
|||
|
|||
// assert(dif >> (DecoderWindowsSize - 16) < r);
|
|||
// assert(32768U <= r);
|
|||
v = ((range >> 8) * (frequency >> ProbabilityShift)) >> (7 - ProbabilityShift); |
|||
v += ProbabilityMinimum; |
|||
vw = v << (DecoderWindowsSize - 16); |
|||
ret = true; |
|||
newRange = v; |
|||
if (dif >= vw) |
|||
{ |
|||
newRange = range - v; |
|||
dif -= vw; |
|||
ret = false; |
|||
} |
|||
|
|||
this.Normalize(dif, newRange); |
|||
return ret; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a symbol given an inverse cumulative distribution function(CDF) table in Q15.
|
|||
/// </summary>
|
|||
/// <param name="probabilities">
|
|||
/// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range
|
|||
/// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]).
|
|||
/// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0.
|
|||
/// </param>
|
|||
/// <param name="numberOfSymbols">
|
|||
/// The number of symbols in the alphabet.
|
|||
/// This should be at most 16.
|
|||
/// </param>
|
|||
/// <returns>The decoded symbol.</returns>
|
|||
private int DecodeIntegerQ15(uint[] probabilities, int numberOfSymbols) |
|||
{ |
|||
uint c; |
|||
uint u; |
|||
uint v; |
|||
int ret; |
|||
|
|||
uint dif = this.difference; |
|||
uint r = this.range; |
|||
int n = numberOfSymbols - 1; |
|||
|
|||
DebugGuard.MustBeLessThan(dif >> (DecoderWindowsSize - 16), r, nameof(r)); |
|||
DebugGuard.IsTrue(probabilities[numberOfSymbols - 1] == 0, "Last value in probability array needs to be zero."); |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r)); |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(7 - ProbabilityShift - CdfShift, 0, nameof(CdfShift)); |
|||
c = dif >> (DecoderWindowsSize - 16); |
|||
v = r; |
|||
ret = -1; |
|||
do |
|||
{ |
|||
u = v; |
|||
v = ((r >> 8) * (probabilities[++ret] >> ProbabilityShift)) >> (7 - ProbabilityShift - CdfShift); |
|||
v += (uint)(ProbabilityMinimum * (n - ret)); |
|||
} |
|||
while (c < v); |
|||
|
|||
DebugGuard.MustBeLessThan(v, u, nameof(v)); |
|||
DebugGuard.MustBeLessThan(u, r, nameof(u)); |
|||
r = u - v; |
|||
dif -= v << (DecoderWindowsSize - 16); |
|||
this.Normalize(dif, r); |
|||
return ret; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Takes updated dif and range values, renormalizes them so that
|
|||
/// <paramref name="rng"/> has value between 32768 and 65536 (reading more bytes from the stream into dif if
|
|||
/// necessary), and stores them back in the decoder context.
|
|||
/// </summary>
|
|||
private void Normalize(uint dif, uint rng) |
|||
{ |
|||
int d; |
|||
|
|||
// assert(rng <= 65535U);
|
|||
/*The number of leading zeros in the 16-bit binary representation of rng.*/ |
|||
d = 15 - Av1Math.MostSignificantBit(rng); |
|||
/*d bits in dec->dif are consumed.*/ |
|||
this.count -= d; |
|||
/*This is equivalent to shifting in 1's instead of 0's.*/ |
|||
this.difference = ((dif + 1) << d) - 1; |
|||
this.range = rng << d; |
|||
if (this.count < 0) |
|||
{ |
|||
this.Refill(); |
|||
} |
|||
} |
|||
|
|||
private void Refill() |
|||
{ |
|||
int s; |
|||
uint dif = this.difference; |
|||
int cnt = this.count; |
|||
int position = this.position; |
|||
int end = this.buffer.Length; |
|||
s = DecoderWindowsSize - 9 - (cnt + 15); |
|||
for (; s >= 0 && position < end; s -= 8, position++) |
|||
{ |
|||
/*Each time a byte is inserted into the window (dif), bptr advances and cnt |
|||
is incremented by 8, so the total number of consumed bits (the return |
|||
value of od_ec_dec_tell) does not change.*/ |
|||
DebugGuard.MustBeLessThan(s, DecoderWindowsSize - 8, nameof(s)); |
|||
dif ^= (uint)this.buffer[0] << s; |
|||
cnt += 8; |
|||
} |
|||
|
|||
if (position >= end) |
|||
{ |
|||
/* |
|||
* We've reached the end of the buffer. It is perfectly valid for us to need |
|||
* to fill the window with additional bits past the end of the buffer (and |
|||
* this happens in normal operation). These bits should all just be taken |
|||
* as zero. But we cannot increment bptr past 'end' (this is undefined |
|||
* behavior), so we start to increment dec->tell_offs. We also don't want |
|||
* to keep testing bptr against 'end', so we set cnt to OD_EC_LOTS_OF_BITS |
|||
* and adjust dec->tell_offs so that the total number of unconsumed bits in |
|||
* the window (dec->cnt - dec->tell_offs) does not change. This effectively |
|||
* puts lots of zero bits into the window, and means we won't try to refill |
|||
* it from the buffer for a very long time (at which point we'll put lots |
|||
* of zero bits into the window again). |
|||
*/ |
|||
cnt = LotsOfBits; |
|||
} |
|||
|
|||
this.difference = dif; |
|||
this.count = cnt; |
|||
this.position = position; |
|||
} |
|||
|
|||
internal static void UpdateCdf(uint[] probabilities, int value, int numberOfSymbols) |
|||
{ |
|||
DebugGuard.MustBeLessThan(numberOfSymbols, 17, nameof(numberOfSymbols)); |
|||
int rate15 = probabilities[numberOfSymbols] > 15 ? 1 : 0; |
|||
int rate31 = probabilities[numberOfSymbols] > 31 ? 1 : 0; |
|||
int rate = 3 + rate15 + rate31 + NumberOfSymbols2Speed[numberOfSymbols]; // + get_msb(nsymbs);
|
|||
int tmp = CdfProbabilityTop; |
|||
|
|||
// Single loop (faster)
|
|||
for (int i = 0; i < numberOfSymbols - 1; i++) |
|||
{ |
|||
tmp = (i == value) ? 0 : tmp; |
|||
if (tmp < probabilities[i]) |
|||
{ |
|||
probabilities[i] -= (ushort)((probabilities[i] - tmp) >> rate); |
|||
} |
|||
else |
|||
{ |
|||
probabilities[i] += (ushort)((tmp - probabilities[i]) >> rate); |
|||
} |
|||
} |
|||
|
|||
probabilities[numberOfSymbols] = Math.Min(probabilities[numberOfSymbols]++, 32); |
|||
} |
|||
} |
|||
@ -0,0 +1,190 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; |
|||
|
|||
internal class Av1SymbolWriter |
|||
{ |
|||
private uint low; |
|||
private uint rng = 0x8000U; |
|||
|
|||
// Count is initialized to -9 so that it crosses zero after we've accumulated one byte + one carry bit.
|
|||
private int cnt = -9; |
|||
private readonly Stream stream; |
|||
|
|||
public Av1SymbolWriter(Stream stream) => this.stream = stream; |
|||
|
|||
public void WriteSymbol(int symbol, uint[] probabilities, int numberOfSymbols) |
|||
{ |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(symbol, 0, nameof(symbol)); |
|||
DebugGuard.MustBeLessThan(symbol, numberOfSymbols, nameof(symbol)); |
|||
DebugGuard.IsTrue(probabilities[numberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero."); |
|||
|
|||
this.EncodeIntegerQ15(symbol, probabilities, numberOfSymbols); |
|||
Av1SymbolReader.UpdateCdf(probabilities, symbol, numberOfSymbols); |
|||
} |
|||
|
|||
public void WriteLiteral(uint value, int bitCount) |
|||
{ |
|||
const uint p = 0x4000U; // (0x7FFFFFU - (128 << 15) + 128) >> 8;
|
|||
for (int bit = bitCount - 1; bit >= 0; bit--) |
|||
{ |
|||
bool bitValue = ((value >> bit) & 0x1) > 0; |
|||
this.EncodeBoolQ15(bitValue, p); |
|||
} |
|||
} |
|||
|
|||
public void Exit() |
|||
{ |
|||
uint m; |
|||
uint e; |
|||
uint l; |
|||
int c; |
|||
int s; |
|||
|
|||
// We output the minimum number of bits that ensures that the symbols encoded
|
|||
// thus far will be decoded correctly regardless of the bits that follow.
|
|||
l = this.low; |
|||
c = this.cnt; |
|||
s = 10; |
|||
m = 0x3FFFU; |
|||
e = ((l + m) & ~m) | (m + 1); |
|||
s += c; |
|||
if (s > 0) |
|||
{ |
|||
uint n = (1U << (c + 16)) - 1; |
|||
do |
|||
{ |
|||
this.stream.WriteByte((byte)(e >> (c + 16))); |
|||
e &= n; |
|||
s -= 8; |
|||
c -= 8; |
|||
n >>= 8; |
|||
} |
|||
while (s > 0); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encode a single binary value.
|
|||
/// </summary>
|
|||
/// <param name="val">The value to encode.</param>
|
|||
/// <param name="frequency">The probability that the value is true, scaled by 32768.</param>
|
|||
private void EncodeBoolQ15(bool val, uint frequency) |
|||
{ |
|||
uint l; |
|||
uint r; |
|||
uint v; |
|||
DebugGuard.MustBeGreaterThan(frequency, 0U, nameof(frequency)); |
|||
DebugGuard.MustBeLessThanOrEqualTo(frequency, 32768U, nameof(frequency)); |
|||
l = this.low; |
|||
r = this.rng; |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r)); |
|||
v = ((r >> 8) * (frequency >> Av1SymbolReader.ProbabilityShift)) >> (7 - Av1SymbolReader.ProbabilityShift); |
|||
v += Av1SymbolReader.ProbabilityMinimum; |
|||
if (val) |
|||
{ |
|||
l += r - v; |
|||
r = v; |
|||
} |
|||
else |
|||
{ |
|||
r -= v; |
|||
} |
|||
|
|||
this.Normalize(l, r); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes a symbol given an inverse cumulative distribution function(CDF) table in Q15.
|
|||
/// </summary>
|
|||
/// <param name="symbol">The value to encode.</param>
|
|||
/// <param name="probabilities">
|
|||
/// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range
|
|||
/// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]).
|
|||
/// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0.
|
|||
/// </param>
|
|||
/// <param name="numberOfSymbols">
|
|||
/// The number of symbols in the alphabet.
|
|||
/// This should be at most 16.
|
|||
/// </param>
|
|||
private void EncodeIntegerQ15(int symbol, uint[] probabilities, int numberOfSymbols) |
|||
=> this.EncodeIntegerQ15(symbol > 0 ? probabilities[symbol - 1] : Av1SymbolReader.CdfProbabilityTop, probabilities[symbol], symbol, numberOfSymbols); |
|||
|
|||
private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, int numberOfSymbols) |
|||
{ |
|||
uint l = this.low; |
|||
uint r = this.rng; |
|||
int totalShift = 7 - Av1SymbolReader.ProbabilityShift - Av1SymbolReader.CdfShift; |
|||
DebugGuard.MustBeLessThanOrEqualTo(32768U, r, nameof(r)); |
|||
DebugGuard.MustBeLessThanOrEqualTo(highFrequency, lowFrequency, nameof(highFrequency)); |
|||
DebugGuard.MustBeLessThanOrEqualTo(lowFrequency, 32768U, nameof(lowFrequency)); |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(totalShift, 0, string.Empty); |
|||
int n = numberOfSymbols - 1; |
|||
if (lowFrequency < Av1SymbolReader.CdfProbabilityTop) |
|||
{ |
|||
uint u; |
|||
uint v; |
|||
u = (uint)((((r >> 8) * (lowFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + |
|||
(Av1SymbolReader.ProbabilityMinimum * (n - (symbol - 1)))); |
|||
v = (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + |
|||
(Av1SymbolReader.ProbabilityMinimum * (n - (symbol + 0)))); |
|||
l += r - u; |
|||
r = u - v; |
|||
} |
|||
else |
|||
{ |
|||
r -= (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + |
|||
(Av1SymbolReader.ProbabilityMinimum * (n - (symbol + 0)))); |
|||
} |
|||
|
|||
this.Normalize(l, r); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Takes updated low and range values, renormalizes them so that <paramref name="rng"/>
|
|||
/// lies between 32768 and 65536 (flushing bytes from low to the pre-carry buffer if necessary),
|
|||
/// and stores them back in the encoder context.
|
|||
/// </summary>
|
|||
/// <param name="low">The new value of <see cref="low"/>.</param>
|
|||
/// <param name="rng">The new value of <see cref="rng"/>.</param>
|
|||
private void Normalize(uint low, uint rng) |
|||
{ |
|||
int d; |
|||
int c; |
|||
int s; |
|||
c = this.cnt; |
|||
DebugGuard.MustBeLessThanOrEqualTo(rng, 65535U, nameof(rng)); |
|||
d = 15 - Av1Math.MostSignificantBit(rng); |
|||
s = c + d; |
|||
/*TODO: Right now we flush every time we have at least one byte available. |
|||
Instead we should use an OdEcWindow and flush right before we're about to |
|||
shift bits off the end of the window. |
|||
For a 32-bit window this is about the same amount of work, but for a 64-bit |
|||
window it should be a fair win.*/ |
|||
if (s >= 0) |
|||
{ |
|||
uint m; |
|||
|
|||
c += 16; |
|||
m = (1U << c) - 1; |
|||
if (s >= 8) |
|||
{ |
|||
this.stream.WriteByte((byte)(low >> c)); |
|||
low &= m; |
|||
c -= 8; |
|||
m >>= 8; |
|||
} |
|||
|
|||
this.stream.WriteByte((byte)(low >> c)); |
|||
s = c + d - 24; |
|||
low &= m; |
|||
} |
|||
|
|||
this.low = low << d; |
|||
this.rng = rng << d; |
|||
this.cnt = s; |
|||
} |
|||
} |
|||
@ -0,0 +1,234 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System; |
|||
using Newtonsoft.Json.Linq; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
|||
|
|||
[Trait("Format", "Avif")] |
|||
public class SymbolTest |
|||
{ |
|||
[Fact] |
|||
public void ReadRandomLiteral() |
|||
{ |
|||
// Assign
|
|||
const int bitCount = 4; |
|||
Random rand = new(bitCount); |
|||
byte[] values = Enumerable.Range(0, 100).Select(x => (byte)rand.Next(1 << bitCount)).ToArray(); |
|||
Av1SymbolReader reader = new(values); |
|||
List<int> actuals = []; |
|||
|
|||
// Act
|
|||
for (int i = 0; i < values.Length; i++) |
|||
{ |
|||
actuals.Add(reader.ReadLiteral(bitCount)); |
|||
} |
|||
|
|||
// Assert
|
|||
Assert.True(values.Length > bitCount); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WriteRandomLiteral() |
|||
{ |
|||
// Assign
|
|||
const int bitCount = 4; |
|||
Random rand = new(bitCount); |
|||
uint[] values = Enumerable.Range(0, 100).Select(x => (uint)rand.Next(1 << bitCount)).ToArray(); |
|||
MemoryStream output = new(); |
|||
Av1SymbolWriter writer = new(output); |
|||
|
|||
// Act
|
|||
for (int i = 0; i < values.Length; i++) |
|||
{ |
|||
writer.WriteLiteral(values[i], bitCount); |
|||
} |
|||
|
|||
// Assert
|
|||
Assert.True(output.Position > 0); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 128)] |
|||
[InlineData(1, 255, 128)] |
|||
public void RawBytesFromWriteLiteral1Bit(uint value, byte exp0, byte exp1) |
|||
{ |
|||
byte[] expected = [exp0, exp1]; |
|||
AssertRawBytesWritten(1, value, expected); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 128)] |
|||
[InlineData(1, 85, 118, 192)] |
|||
[InlineData(2, 170, 165, 128)] |
|||
[InlineData(3, 255, 255, 128)] |
|||
public void RawBytesFromWriteLiteral2Bits(uint value, byte exp0, byte exp1, byte exp2) |
|||
{ |
|||
byte[] expected = [exp0, exp1, exp2]; |
|||
AssertRawBytesWritten(2, value, expected); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0, 128)] |
|||
[InlineData(1, 36, 198, 146, 128)] |
|||
[InlineData(2, 73, 81, 182, 192)] |
|||
[InlineData(3, 109, 192, 146, 64)] |
|||
[InlineData(4, 146, 66, 73, 128)] |
|||
[InlineData(5, 182, 214, 219, 128)] |
|||
[InlineData(6, 219, 107, 109, 128)] |
|||
[InlineData(7, 255, 255, 255, 128)] |
|||
public void RawBytesFromWriteLiteral3Bits(uint value, byte exp0, byte exp1, byte exp2, byte exp3) |
|||
{ |
|||
byte[] expected = [exp0, exp1, exp2, exp3]; |
|||
AssertRawBytesWritten(3, value, expected); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0, 0, 128)] |
|||
[InlineData(1, 17, 68, 34, 34, 128)] |
|||
[InlineData(2, 34, 86, 68, 68, 128)] |
|||
[InlineData(3, 51, 104, 102, 102, 128)] |
|||
[InlineData(4, 68, 118, 34, 34, 64)] |
|||
[InlineData(5, 85, 118, 170, 170, 64)] |
|||
[InlineData(6, 102, 119, 51, 51, 64)] |
|||
[InlineData(7, 119, 119, 187, 187, 192)] |
|||
[InlineData(8, 136, 129, 17, 17, 128)] |
|||
[InlineData(9, 153, 147, 51, 51, 128)] |
|||
[InlineData(10, 170, 165, 85, 85, 128)] |
|||
[InlineData(11, 187, 183, 119, 119, 128)] |
|||
[InlineData(12, 204, 201, 153, 153, 128)] |
|||
[InlineData(13, 221, 219, 187, 187, 128)] |
|||
[InlineData(14, 238, 237, 221, 221, 128)] |
|||
[InlineData(15, 255, 255, 255, 255, 128)] |
|||
public void RawBytesFromWriteLiteral4Bits(uint value, byte exp0, byte exp1, byte exp2, byte exp3, byte exp4) |
|||
{ |
|||
byte[] expected = [exp0, exp1, exp2, exp3, exp4]; |
|||
AssertRawBytesWritten(4, value, expected); |
|||
} |
|||
|
|||
private static void AssertRawBytesWritten(int bitCount, uint value, byte[] expected) |
|||
{ |
|||
// Assign
|
|||
uint[] values = new uint[8]; |
|||
Array.Fill(values, value); |
|||
MemoryStream output = new(); |
|||
Av1SymbolWriter writer = new(output); |
|||
|
|||
// Act
|
|||
for (int i = 0; i < 8; i++) |
|||
{ |
|||
writer.WriteLiteral(value, bitCount); |
|||
} |
|||
|
|||
writer.Exit(); |
|||
|
|||
// Assert
|
|||
Assert.Equal(expected, output.ToArray()); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 128)] |
|||
[InlineData(1, 255, 128)] |
|||
public void RawBytesReadLiteral1Bit(int value, byte exp0, byte exp1) |
|||
{ |
|||
byte[] buffer = [exp0, exp1]; |
|||
AssertRawBytesRead(1, buffer, value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 128)] |
|||
[InlineData(1, 85, 118, 192)] |
|||
[InlineData(2, 170, 165, 128)] |
|||
[InlineData(3, 255, 255, 128)] |
|||
public void RawBytesReadLiteral2Bits(int value, byte exp0, byte exp1, byte exp2) |
|||
{ |
|||
byte[] buffer = [exp0, exp1, exp2]; |
|||
AssertRawBytesRead(2, buffer, value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0, 128)] |
|||
[InlineData(1, 36, 198, 146, 128)] |
|||
[InlineData(2, 73, 81, 182, 192)] |
|||
[InlineData(3, 109, 192, 146, 64)] |
|||
[InlineData(4, 146, 66, 73, 128)] |
|||
[InlineData(5, 182, 214, 219, 128)] |
|||
[InlineData(6, 219, 107, 109, 128)] |
|||
[InlineData(7, 255, 255, 255, 128)] |
|||
public void RawBytesReadLiteral3Bits(int value, byte exp0, byte exp1, byte exp2, byte exp3) |
|||
{ |
|||
byte[] buffer = [exp0, exp1, exp2, exp3]; |
|||
AssertRawBytesRead(3, buffer, value); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0, 0, 128)] |
|||
[InlineData(1, 17, 68, 34, 34, 128)] |
|||
[InlineData(2, 34, 86, 68, 68, 128)] |
|||
[InlineData(3, 51, 104, 102, 102, 128)] |
|||
[InlineData(4, 68, 118, 34, 34, 64)] |
|||
[InlineData(5, 85, 118, 170, 170, 64)] |
|||
[InlineData(6, 102, 119, 51, 51, 64)] |
|||
[InlineData(7, 119, 119, 187, 187, 192)] |
|||
[InlineData(8, 136, 129, 17, 17, 128)] |
|||
[InlineData(9, 153, 147, 51, 51, 128)] |
|||
[InlineData(10, 170, 165, 85, 85, 128)] |
|||
[InlineData(11, 187, 183, 119, 119, 128)] |
|||
[InlineData(12, 204, 201, 153, 153, 128)] |
|||
[InlineData(13, 221, 219, 187, 187, 128)] |
|||
[InlineData(14, 238, 237, 221, 221, 128)] |
|||
[InlineData(15, 255, 255, 255, 255, 128)] |
|||
public void RawBytesReadLiteral4Bits(int value, byte exp0, byte exp1, byte exp2, byte exp3, byte exp4) |
|||
{ |
|||
byte[] buffer = [exp0, exp1, exp2, exp3, exp4]; |
|||
AssertRawBytesRead(4, buffer, value); |
|||
} |
|||
|
|||
private static void AssertRawBytesRead(int bitCount, byte[] buffer, int expected) |
|||
{ |
|||
// Assign
|
|||
int[] values = new int[8]; |
|||
int[] expectedValues = new int[8]; |
|||
Array.Fill(expectedValues, expected); |
|||
Av1SymbolReader reader = new(buffer); |
|||
|
|||
// Act
|
|||
for (int i = 0; i < 8; i++) |
|||
{ |
|||
values[i] = reader.ReadLiteral(bitCount); |
|||
} |
|||
|
|||
// Assert
|
|||
Assert.Equal(expectedValues, values); |
|||
} |
|||
|
|||
[Fact] |
|||
public void RoundTripUseIntraBlockCopy() |
|||
{ |
|||
// Assign
|
|||
bool[] values = [true, true, false, true, false, false, false]; |
|||
MemoryStream output = new(100); |
|||
Av1SymbolWriter writer = new(output); |
|||
Av1SymbolEncoder encoder = new(); |
|||
Av1SymbolDecoder decoder = new(); |
|||
bool[] actuals = new bool[values.Length]; |
|||
|
|||
// Act
|
|||
foreach (bool value in values) |
|||
{ |
|||
encoder.WriteUseIntraBlockCopySymbol(writer, value); |
|||
} |
|||
|
|||
Av1SymbolReader reader = new(output.ToArray()); |
|||
for (int i = 0; i < values.Length; i++) |
|||
{ |
|||
actuals[i] = decoder.ReadUseIntraBlockCopySymbol(ref reader); |
|||
} |
|||
|
|||
// Assert
|
|||
Assert.Equal(values, actuals); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue