From e45a41b54e2d3754f5ad60297d19c5318414ae3b Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 12 Jan 2024 12:16:04 +0100 Subject: [PATCH] Av1 bitstream tests --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 23 ++--- .../Formats/Heif/Av1/Av1BitStreamWriter.cs | 56 ++++++++++++ .../Formats/Heif/Av1/Av1BitStreamTests.cs | 86 +++++++++++++++++++ 3 files changed, 149 insertions(+), 16 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index e16553121c..8804c31626 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -1,11 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers.Binary; + namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal ref struct Av1BitStreamReader(Span data) { - private const int WordSize = sizeof(byte); + private const int WordSize = sizeof(byte) * 8; private const int DoubleWordSize = 2 * WordSize; private readonly Span data = data; private int wordPosition = 0; @@ -33,25 +35,14 @@ internal ref struct Av1BitStreamReader(Span data) public uint ReadLiteral(int bitCount) { - uint bits = (uint)(this.data[this.wordPosition] << this.bitOffset) >> (WordSize - bitCount); - this.bitOffset += bitCount; - while (this.bitOffset > WordSize) - { - uint nextWord = this.data[this.wordPosition + 1]; - bits |= nextWord << (DoubleWordSize - bitCount); - } - - if (this.bitOffset >= WordSize) - { - this.bitOffset -= WordSize; - } - - return bits; + uint bits = BinaryPrimitives.ReadUInt32BigEndian(this.data[this.wordPosition..]); + this.Skip(bitCount); + return bits >> (32 - bitCount); } internal bool ReadBoolean() { - bool bit = (this.data[this.wordPosition] & (1 << (WordSize - this.bitOffset))) > 0; + bool bit = (this.data[this.wordPosition] & (1 << (WordSize - this.bitOffset - 1))) > 0; this.Skip(1); return bit; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs new file mode 100644 index 0000000000..4459c01a00 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal ref struct Av1BitStreamWriter(Stream stream) +{ + private const int WordSize = sizeof(byte) * 8; + private readonly Stream stream = stream; + private byte buffer = 0; + private int bitOffset = 0; + + public readonly int BitPosition => (int)(this.stream.Position * WordSize) + this.bitOffset; + + public readonly int Length => (int)this.stream.Length; + + public void Skip(int bitCount) + { + this.bitOffset += bitCount; + while (this.bitOffset >= WordSize) + { + this.bitOffset -= WordSize; + this.stream.WriteByte(this.buffer); + this.buffer = 0; + } + } + + public void Flush() + { + this.stream.WriteByte(this.buffer); + this.bitOffset = 0; + } + + public void WriteLiteral(uint value, int bitCount) + { + int shift = 24; + uint padded = value << ((32 - bitCount) - this.bitOffset); + while (bitCount >= 8) + { + byte current = (byte)(((padded >> shift) & 0xff) | this.buffer); + this.stream.WriteByte(current); + shift -= 8; + bitCount -= 8; + this.buffer = 0; + this.bitOffset = 0; + } + + if (bitCount > 0) + { + this.buffer = (byte)(((padded >> shift) & 0xff) | this.buffer); + this.bitOffset += bitCount; + } + } + + internal void WriteBoolean(bool value) => this.WriteLiteral(value ? 1U : 0U, 1); +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs new file mode 100644 index 0000000000..7d6c75dded --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; +using SixLabors.ImageSharp.Formats.Heif.Av1; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class Av1BitsStreamTests +{ + [Theory] + [InlineData(42, new bool[] { false, false, true, false, true, false, true, false })] + [InlineData(52, new bool[] { false, false, true, true, false, true, false, false })] + public void ReadAsBoolean(int value, bool[] bits) + { + int bitCount = bits.Length; + byte[] buffer = new byte[4]; + BinaryPrimitives.WriteInt32BigEndian(buffer, value << (32 - bitCount)); + Av1BitStreamReader reader = new(buffer); + bool[] actual = new bool[bitCount]; + for (int i = 0; i < bitCount; i++) + { + actual[i] = reader.ReadBoolean(); + } + + Assert.Equal(bits, actual); + } + + [Theory] + [InlineData(6, 4)] + [InlineData(42, 8)] + [InlineData(52, 8)] + [InlineData(4050, 16)] + public void ReadAsLiteral(uint expected, int bitCount) + { + byte[] buffer = new byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(buffer, expected << (32 - bitCount)); + Av1BitStreamReader reader = new(buffer); + uint actual = reader.ReadLiteral(bitCount); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(new bool[] { false, false, true, false, true, false, true, false })] + [InlineData(new bool[] { false, true, false, true })] + public void WriteAsBoolean(bool[] booleans) + { + using MemoryStream stream = new(4); + Av1BitStreamWriter writer = new(stream); + for (int i = 0; i < booleans.Length; i++) + { + writer.WriteBoolean(booleans[i]); + } + + writer.Flush(); + + // Read the written value back. + Av1BitStreamReader reader = new(stream.GetBuffer()); + bool[] actual = new bool[booleans.Length]; + for (int i = 0; i < booleans.Length; i++) + { + actual[i] = reader.ReadBoolean(); + } + + Assert.Equal(booleans, actual); + } + + [Theory] + [InlineData(6, 4)] + [InlineData(42, 8)] + [InlineData(52, 8)] + [InlineData(4050, 16)] + public void WriteAsLiteral(uint value, int bitCount) + { + using MemoryStream stream = new(4); + Av1BitStreamWriter writer = new(stream); + writer.WriteLiteral(value, bitCount); + writer.Flush(); + + // Read the written value back. + Av1BitStreamReader reader = new(stream.GetBuffer()); + uint actual = reader.ReadLiteral(bitCount); + Assert.Equal(value, actual); + } +}