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)