diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs new file mode 100644 index 0000000000..84832bb7b8 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs @@ -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]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs new file mode 100644 index 0000000000..53ab655220 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs @@ -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; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs new file mode 100644 index 0000000000..6ff2807ece --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs @@ -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); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs new file mode 100644 index 0000000000..7012a973c4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs @@ -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 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 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; + } + + /// + /// Decode a single binary value. + /// + /// The probability that the bit is one, scaled by 32768. + 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; + } + + /// + /// Decodes a symbol given an inverse cumulative distribution function(CDF) table in Q15. + /// + /// + /// 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. + /// + /// + /// The number of symbols in the alphabet. + /// This should be at most 16. + /// + /// The decoded symbol. + 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; + } + + /// + /// Takes updated dif and range values, renormalizes them so that + /// has value between 32768 and 65536 (reading more bytes from the stream into dif if + /// necessary), and stores them back in the decoder context. + /// + 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); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs new file mode 100644 index 0000000000..424bda8761 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs @@ -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); + } + } + + /// + /// Encode a single binary value. + /// + /// The value to encode. + /// The probability that the value is true, scaled by 32768. + 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); + } + + /// + /// Encodes a symbol given an inverse cumulative distribution function(CDF) table in Q15. + /// + /// The value to encode. + /// + /// 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. + /// + /// + /// The number of symbols in the alphabet. + /// This should be at most 16. + /// + 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); + } + + /// + /// Takes updated low and range values, renormalizes them so that + /// lies between 32768 and 65536 (flushing bytes from low to the pre-carry buffer if necessary), + /// and stores them back in the encoder context. + /// + /// The new value of . + /// The new value of . + 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; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs new file mode 100644 index 0000000000..5ad969c3f6 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs @@ -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 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); + } +}