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