diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs new file mode 100644 index 0000000000..bdb1d0e033 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal class Vp8BitReader + { + /// + /// Current value. + /// + private long value; + + /// + /// Current range minus 1. In [127, 254] interval. + /// + private int range; + + /// + /// Number of valid bits left. + /// + private int bits; + + /// + /// The next byte to be read. + /// + private byte buf; + + /// + /// End of read buffer. + /// + private byte bufEnd; + + /// + /// Max packed-read position on buffer. + /// + private byte bufMax; + + /// + /// True if input is exhausted. + /// + private bool eof; + + /// + /// Reads the specified number of bits from read buffer. + /// Flags an error in case end_of_stream or n_bits is more than the allowed limit + /// of VP8L_MAX_NUM_BIT_READ (inclusive). + /// Flags eos_ if this read attempt is going to cross the read buffer. + /// + /// The number of bits to read. + public int ReadBits(int nBits) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs index bf21a6282b..9bcad284c5 100644 --- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; namespace SixLabors.ImageSharp.Formats.WebP @@ -8,9 +9,35 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// A bit reader for VP8 streams. /// - public class Vp8LBitReader + internal class Vp8LBitReader { - private readonly Stream stream; + /// + /// Maximum number of bits (inclusive) the bit-reader can handle. + /// + private const int VP8L_MAX_NUM_BIT_READ = 24; + + /// + /// Number of bits prefetched (= bit-size of vp8l_val_t). + /// + private const int VP8L_LBITS = 64; + + /// + /// Minimum number of bytes ready after VP8LFillBitWindow. + /// + private const int VP8L_WBITS = 32; + + private uint[] kBitMask = + { + 0, + 0x000001, 0x000003, 0x000007, 0x00000f, + 0x00001f, 0x00003f, 0x00007f, 0x0000ff, + 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, + 0x001fff, 0x003fff, 0x007fff, 0x00ffff, + 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff, + 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff + }; + + private readonly byte[] data; /// /// Initializes a new instance of the class. @@ -18,75 +45,137 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The input stream to read from. public Vp8LBitReader(Stream inputStream) { - this.stream = inputStream; - this.Offset = inputStream.Position; - this.Bit = 0; - } + long length = inputStream.Length - inputStream.Position; - private long Offset { get; set; } + using (var ms = new MemoryStream()) + { + inputStream.CopyTo(ms); + this.data = ms.ToArray(); + } - private int Bit { get; set; } + this.len = length; + this.value = 0; + this.bitPos = 0; + this.eos = false; - /// - /// Gets a value indicating whether the offset is inside the inputStream length. - /// - private bool ValidPosition - { - get + if (length > sizeof(long)) { - return this.Offset < this.stream.Length; + length = sizeof(long); } + + ulong currentValue = 0; + for (int i = 0; i < length; ++i) + { + currentValue |= (ulong)this.data[i] << (8 * i); + } + + this.value = currentValue; + this.pos = length; } + /// + /// Pre-fetched bits. + /// + private ulong value; + + /// + /// Buffer length. + /// + private long len; + + /// + /// Byte position in buffer. + /// + private long pos; + + /// + /// Current bit-reading position in value. + /// + private int bitPos; + + /// + /// True if a bit was read past the end of buffer. + /// + private bool eos; + /// /// Reads a unsigned short value from the inputStream. The bits of each byte are read in least-significant-bit-first order. /// - /// The number of bits to read (should not exceed 16). + /// The number of bits to read (should not exceed 16). /// A ushort value. - public uint Read(int count) + public uint ReadBits(int nBits) { - uint readValue = 0; - for (int bitPos = 0; bitPos < count; bitPos++) + Guard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + + if (!this.eos && nBits <= VP8L_MAX_NUM_BIT_READ) { - bool bitRead = this.ReadBit(); - if (bitRead) - { - readValue = (uint)(readValue | (1 << bitPos)); - } + ulong val = this.PrefetchBits() & this.kBitMask[nBits]; + int newBits = this.bitPos + nBits; + this.bitPos = newBits; + this.ShiftBytes(); + return (uint)val; + } + else + { + this.SetEndOfStream(); + return 0; } - - return readValue; } - /// - /// Reads one bit. - /// - /// True, if the bit is one, otherwise false. public bool ReadBit() { - if (!this.ValidPosition) + uint bit = this.ReadBits(1); + return bit != 0; + } + + public void AdvanceBitPosition(int bitPosition) + { + this.bitPos += bitPosition; + } + + public ulong PrefetchBits() + { + return this.value >> (this.bitPos & (VP8L_LBITS - 1)); + } + + public void FillBitWindow() + { + if (this.bitPos >= VP8L_WBITS) { - WebPThrowHelper.ThrowImageFormatException("The image inputStream does not contain enough data"); + this.DoFillBitWindow(); } + } - this.stream.Seek(this.Offset, SeekOrigin.Begin); - byte value = (byte)((this.stream.ReadByte() >> this.Bit) & 1); - this.AdvanceBit(); - this.stream.Seek(this.Offset, SeekOrigin.Begin); - - return value == 1; + public void DoFillBitWindow() + { + this.ShiftBytes(); } - /// - /// Advances the inputStream by one Bit. - /// - public void AdvanceBit() + private void ShiftBytes() { - this.Bit = (this.Bit + 1) % 8; - if (this.Bit == 0) + while (this.bitPos >= 8 && this.pos < this.len) + { + this.value >>= 8; + this.value |= (ulong)this.data[this.pos] << (VP8L_LBITS - 8); + ++this.pos; + this.bitPos -= 8; + } + + if (this.IsEndOfStream()) { - this.Offset++; + this.SetEndOfStream(); } } + + private bool IsEndOfStream() + { + return this.eos || ((this.pos == this.len) && (this.bitPos > VP8L_LBITS)); + } + + private void SetEndOfStream() + { + this.eos = true; + this.bitPos = 0; // To avoid undefined behaviour with shifts. + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs index 70cfe6ac72..5d61035437 100644 --- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.WebP Buffer2D pixels = image.GetRootFramePixelBuffer(); if (imageInfo.IsLossLess) { - var losslessDecoder = new WebPLosslessDecoder(this.currentStream, (int)imageInfo.ImageDataSize); + var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp9LBitReader, (int)imageInfo.ImageDataSize); losslessDecoder.Decode(pixels, image.Width, image.Height); } else @@ -298,16 +298,16 @@ namespace SixLabors.ImageSharp.Formats.WebP uint dataSize = this.ReadChunkSize(); // One byte signature, should be 0x2f. - byte signature = (byte)this.currentStream.ReadByte(); + var bitReader = new Vp8LBitReader(this.currentStream); + uint signature = bitReader.ReadBits(8); if (signature != WebPConstants.Vp8LMagicByte) { WebPThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); } // The first 28 bits of the bitstream specify the width and height of the image. - var bitReader = new Vp8LBitReader(this.currentStream); - uint width = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; - uint height = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; + uint width = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1; + uint height = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1; // The alpha_is_used flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. bool alphaIsUsed = bitReader.ReadBit(); @@ -315,7 +315,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // The next 3 bits are the version. The version_number is a 3 bit code that must be set to 0. // Any other value should be treated as an error. // TODO: should we throw here when version number is != 0? - uint version = bitReader.Read(WebPConstants.Vp8LVersionBits); + uint version = bitReader.ReadBits(WebPConstants.Vp8LVersionBits); return new WebPImageInfo() { @@ -323,22 +323,11 @@ namespace SixLabors.ImageSharp.Formats.WebP Height = (int)height, IsLossLess = true, ImageDataSize = dataSize, - Features = features + Features = features, + Vp9LBitReader = bitReader }; } - private void ReadSimpleLossy(Buffer2D pixels, int width, int height, int imageDataSize) - where TPixel : struct, IPixel - { - - } - - private void ReadExtended(Buffer2D pixels, int width, int height) - where TPixel : struct, IPixel - { - // TODO: implement decoding - } - private void ParseOptionalChunks(WebPFeatures features) { if (features.ExifProfile == false && features.XmpMetaData == false) diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs index 651c8d895b..f68cc00b13 100644 --- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs +++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs @@ -29,5 +29,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The bytes of the image payload. /// public uint ImageDataSize { get; set; } + + // TODO: not sure if the bitreader is in the right place here, but for the sake of simplicity it will stay here for now. + // Will be refactored later. + public Vp8LBitReader Vp9LBitReader { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs index a24e784636..aa186739f5 100644 --- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs @@ -23,9 +23,9 @@ namespace SixLabors.ImageSharp.Formats.WebP private readonly int imageDataSize; - public WebPLosslessDecoder(Stream stream, int imageDataSize) + public WebPLosslessDecoder(Vp8LBitReader bitReader, int imageDataSize) { - this.bitReader = new Vp8LBitReader(stream); + this.bitReader = bitReader; this.imageDataSize = imageDataSize; // TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bool colorCachePresent = this.bitReader.ReadBit(); if (colorCachePresent) { - colorCacheBits = (int)this.bitReader.Read(4); + colorCacheBits = (int)this.bitReader.ReadBits(4); int colorCacheSize = 1 << colorCacheBits; if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits)) { @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bool isEntropyImage = this.bitReader.ReadBit(); if (isEntropyImage) { - uint huffmanPrecision = this.bitReader.Read(3) + 2; + uint huffmanPrecision = this.bitReader.ReadBits(3) + 2; int huffmanXSize = SubSampleSize(xsize, (int)huffmanPrecision); int huffmanYSize = SubSampleSize(ysize, (int)huffmanPrecision); @@ -129,17 +129,17 @@ namespace SixLabors.ImageSharp.Formats.WebP // and are in the range of[0, 255].All other Huffman code lengths are implicitly zeros. // Read symbols, codes & code lengths directly. - uint numSymbols = this.bitReader.Read(1) + 1; - uint firstSymbolLenCode = this.bitReader.Read(1); + uint numSymbols = this.bitReader.ReadBits(1) + 1; + uint firstSymbolLenCode = this.bitReader.ReadBits(1); // The first code is either 1 bit or 8 bit code. - uint symbol = this.bitReader.Read((firstSymbolLenCode == 0) ? 1 : 8); + uint symbol = this.bitReader.ReadBits((firstSymbolLenCode == 0) ? 1 : 8); codeLengths[symbol] = 1; // The second code (if present), is always 8 bit long. if (numSymbols == 2) { - symbol = this.bitReader.Read(8); + symbol = this.bitReader.ReadBits(8); codeLengths[symbol] = 1; } } @@ -149,7 +149,7 @@ namespace SixLabors.ImageSharp.Formats.WebP // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. var codeLengthCodeLengths = new int[WebPConstants.NumCodeLengthCodes]; - uint numCodes = this.bitReader.Read(4) + 4; + uint numCodes = this.bitReader.ReadBits(4) + 4; if (numCodes > WebPConstants.NumCodeLengthCodes) { WebPThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Formats.WebP for (int i = 0; i < numCodes; i++) { - codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.Read(3); + codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.ReadBits(3); } this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); @@ -171,6 +171,7 @@ namespace SixLabors.ImageSharp.Formats.WebP private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) { + Span tableSpan = table.AsSpan(); int maxSymbol; int symbol = 0; int prevCodeLen = WebPConstants.DefaultCodeLength; @@ -182,8 +183,8 @@ namespace SixLabors.ImageSharp.Formats.WebP if (this.bitReader.ReadBit()) { - int lengthNBits = 2 + (2 * (int)this.bitReader.Read(3)); - maxSymbol = 2 + (int)this.bitReader.Read(lengthNBits); + int lengthNBits = 2 + (2 * (int)this.bitReader.ReadBits(3)); + maxSymbol = 2 + (int)this.bitReader.ReadBits(lengthNBits); } else { @@ -198,7 +199,12 @@ namespace SixLabors.ImageSharp.Formats.WebP break; } - codeLen = int.MaxValue; // TODO: this is wrong + this.bitReader.FillBitWindow(); + ulong prefetchBits = this.bitReader.PrefetchBits(); + ulong idx = prefetchBits & 127; + HuffmanCode huffmanCode = table[idx]; + this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); + codeLen = huffmanCode.Value; if (codeLen < WebPConstants.kCodeLengthLiterals) { codeLengths[symbol++] = codeLen; @@ -213,7 +219,7 @@ namespace SixLabors.ImageSharp.Formats.WebP int slot = codeLen - WebPConstants.kCodeLengthLiterals; int extraBits = WebPConstants.kCodeLengthExtraBits[slot]; int repeatOffset = WebPConstants.kCodeLengthRepeatOffsets[slot]; - int repeat = (int)(this.bitReader.Read(extraBits) + repeatOffset); + int repeat = (int)(this.bitReader.ReadBits(extraBits) + repeatOffset); if (symbol + repeat > numSymbols) { return; @@ -238,7 +244,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var transforms = new List(WebPConstants.MaxNumberOfTransforms); while (transformPresent) { - var transformType = (WebPTransformType)this.bitReader.Read(2); + var transformType = (WebPTransformType)this.bitReader.ReadBits(2); transforms.Add(transformType); switch (transformType) { @@ -248,7 +254,7 @@ namespace SixLabors.ImageSharp.Formats.WebP case WebPTransformType.ColorIndexingTransform: // The transform data contains color table size and the entries in the color table. // 8 bit value for color table size. - uint colorTableSize = this.bitReader.Read(8) + 1; + uint colorTableSize = this.bitReader.ReadBits(8) + 1; // TODO: color table should follow here? break; @@ -257,7 +263,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { // The first 3 bits of prediction data define the block width and height in number of bits. // The number of block columns, block_xsize, is used in indexing two-dimensionally. - uint sizeBits = this.bitReader.Read(3) + 2; + uint sizeBits = this.bitReader.ReadBits(3) + 2; int blockWidth = 1 << (int)sizeBits; int blockHeight = 1 << (int)sizeBits; @@ -268,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.WebP { // The first 3 bits of the color transform data contain the width and height of the image block in number of bits, // just like the predictor transform: - uint sizeBits = this.bitReader.Read(3) + 2; + uint sizeBits = this.bitReader.ReadBits(3) + 2; int blockWidth = 1 << (int)sizeBits; int blockHeight = 1 << (int)sizeBits; break; diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs index f09be2faff..32d38ba332 100644 --- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.WebP bool isShowFrame = bitReader.ReadBit(); - uint firstPartitionSize = (bitReader.Read(16) << 3) | bitReader.Read(3); + uint firstPartitionSize = (bitReader.ReadBits(16) << 3) | bitReader.ReadBits(3); } private (ReconstructionFilter, LoopFilter) DecodeVersion(byte version)