Browse Source

Overhaul bitmap reader to be similar to libwebp bitreader

pull/1147/head
Brian Popow 7 years ago
parent
commit
8eccf53345
  1. 57
      src/ImageSharp/Formats/WebP/Vp8BitReader.cs
  2. 179
      src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
  3. 27
      src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
  4. 4
      src/ImageSharp/Formats/WebP/WebPImageInfo.cs
  5. 42
      src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
  6. 2
      src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs

57
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
{
/// <summary>
/// Current value.
/// </summary>
private long value;
/// <summary>
/// Current range minus 1. In [127, 254] interval.
/// </summary>
private int range;
/// <summary>
/// Number of valid bits left.
/// </summary>
private int bits;
/// <summary>
/// The next byte to be read.
/// </summary>
private byte buf;
/// <summary>
/// End of read buffer.
/// </summary>
private byte bufEnd;
/// <summary>
/// Max packed-read position on buffer.
/// </summary>
private byte bufMax;
/// <summary>
/// True if input is exhausted.
/// </summary>
private bool eof;
/// <summary>
/// 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.
/// </summary>
/// <param name="nBits">The number of bits to read.</param>
public int ReadBits(int nBits)
{
throw new NotImplementedException();
}
}
}

179
src/ImageSharp/Formats/WebP/Vp8LBitReader.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.IO; using System.IO;
namespace SixLabors.ImageSharp.Formats.WebP namespace SixLabors.ImageSharp.Formats.WebP
@ -8,9 +9,35 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary> /// <summary>
/// A bit reader for VP8 streams. /// A bit reader for VP8 streams.
/// </summary> /// </summary>
public class Vp8LBitReader internal class Vp8LBitReader
{ {
private readonly Stream stream; /// <summary>
/// Maximum number of bits (inclusive) the bit-reader can handle.
/// </summary>
private const int VP8L_MAX_NUM_BIT_READ = 24;
/// <summary>
/// Number of bits prefetched (= bit-size of vp8l_val_t).
/// </summary>
private const int VP8L_LBITS = 64;
/// <summary>
/// Minimum number of bytes ready after VP8LFillBitWindow.
/// </summary>
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;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Vp8LBitReader"/> class. /// Initializes a new instance of the <see cref="Vp8LBitReader"/> class.
@ -18,75 +45,137 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <param name="inputStream">The input stream to read from.</param> /// <param name="inputStream">The input stream to read from.</param>
public Vp8LBitReader(Stream inputStream) public Vp8LBitReader(Stream inputStream)
{ {
this.stream = inputStream; long length = inputStream.Length - inputStream.Position;
this.Offset = inputStream.Position;
this.Bit = 0;
}
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;
/// <summary> if (length > sizeof(long))
/// Gets a value indicating whether the offset is inside the inputStream length.
/// </summary>
private bool ValidPosition
{
get
{ {
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;
} }
/// <summary>
/// Pre-fetched bits.
/// </summary>
private ulong value;
/// <summary>
/// Buffer length.
/// </summary>
private long len;
/// <summary>
/// Byte position in buffer.
/// </summary>
private long pos;
/// <summary>
/// Current bit-reading position in value.
/// </summary>
private int bitPos;
/// <summary>
/// True if a bit was read past the end of buffer.
/// </summary>
private bool eos;
/// <summary> /// <summary>
/// Reads a unsigned short value from the inputStream. The bits of each byte are read in least-significant-bit-first order. /// Reads a unsigned short value from the inputStream. The bits of each byte are read in least-significant-bit-first order.
/// </summary> /// </summary>
/// <param name="count">The number of bits to read (should not exceed 16).</param> /// <param name="nBits">The number of bits to read (should not exceed 16).</param>
/// <returns>A ushort value.</returns> /// <returns>A ushort value.</returns>
public uint Read(int count) public uint ReadBits(int nBits)
{ {
uint readValue = 0; Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
for (int bitPos = 0; bitPos < count; bitPos++)
if (!this.eos && nBits <= VP8L_MAX_NUM_BIT_READ)
{ {
bool bitRead = this.ReadBit(); ulong val = this.PrefetchBits() & this.kBitMask[nBits];
if (bitRead) int newBits = this.bitPos + nBits;
{ this.bitPos = newBits;
readValue = (uint)(readValue | (1 << bitPos)); this.ShiftBytes();
} return (uint)val;
}
else
{
this.SetEndOfStream();
return 0;
} }
return readValue;
} }
/// <summary>
/// Reads one bit.
/// </summary>
/// <returns>True, if the bit is one, otherwise false.</returns>
public bool ReadBit() 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); public void DoFillBitWindow()
byte value = (byte)((this.stream.ReadByte() >> this.Bit) & 1); {
this.AdvanceBit(); this.ShiftBytes();
this.stream.Seek(this.Offset, SeekOrigin.Begin);
return value == 1;
} }
/// <summary> private void ShiftBytes()
/// Advances the inputStream by one Bit.
/// </summary>
public void AdvanceBit()
{ {
this.Bit = (this.Bit + 1) % 8; while (this.bitPos >= 8 && this.pos < this.len)
if (this.Bit == 0) {
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.
}
} }
} }

27
src/ImageSharp/Formats/WebP/WebPDecoderCore.cs

@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (imageInfo.IsLossLess) 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); losslessDecoder.Decode(pixels, image.Width, image.Height);
} }
else else
@ -298,16 +298,16 @@ namespace SixLabors.ImageSharp.Formats.WebP
uint dataSize = this.ReadChunkSize(); uint dataSize = this.ReadChunkSize();
// One byte signature, should be 0x2f. // 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) if (signature != WebPConstants.Vp8LMagicByte)
{ {
WebPThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); WebPThrowHelper.ThrowImageFormatException("Invalid VP8L signature");
} }
// The first 28 bits of the bitstream specify the width and height of the image. // The first 28 bits of the bitstream specify the width and height of the image.
var bitReader = new Vp8LBitReader(this.currentStream); uint width = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1;
uint width = bitReader.Read(WebPConstants.Vp8LImageSizeBits) + 1; uint height = bitReader.ReadBits(WebPConstants.Vp8LImageSizeBits) + 1;
uint height = bitReader.Read(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. // 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(); 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. // 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. // Any other value should be treated as an error.
// TODO: should we throw here when version number is != 0? // 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() return new WebPImageInfo()
{ {
@ -323,22 +323,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
Height = (int)height, Height = (int)height,
IsLossLess = true, IsLossLess = true,
ImageDataSize = dataSize, ImageDataSize = dataSize,
Features = features Features = features,
Vp9LBitReader = bitReader
}; };
} }
private void ReadSimpleLossy<TPixel>(Buffer2D<TPixel> pixels, int width, int height, int imageDataSize)
where TPixel : struct, IPixel<TPixel>
{
}
private void ReadExtended<TPixel>(Buffer2D<TPixel> pixels, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
// TODO: implement decoding
}
private void ParseOptionalChunks(WebPFeatures features) private void ParseOptionalChunks(WebPFeatures features)
{ {
if (features.ExifProfile == false && features.XmpMetaData == false) if (features.ExifProfile == false && features.XmpMetaData == false)

4
src/ImageSharp/Formats/WebP/WebPImageInfo.cs

@ -29,5 +29,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// The bytes of the image payload. /// The bytes of the image payload.
/// </summary> /// </summary>
public uint ImageDataSize { get; set; } 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; }
} }
} }

42
src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs

@ -23,9 +23,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
private readonly int imageDataSize; 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; this.imageDataSize = imageDataSize;
// TODO: implement decoding. For simulating the decoding: skipping the chunk size bytes. // 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(); bool colorCachePresent = this.bitReader.ReadBit();
if (colorCachePresent) if (colorCachePresent)
{ {
colorCacheBits = (int)this.bitReader.Read(4); colorCacheBits = (int)this.bitReader.ReadBits(4);
int colorCacheSize = 1 << colorCacheBits; int colorCacheSize = 1 << colorCacheBits;
if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits)) if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits))
{ {
@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
bool isEntropyImage = this.bitReader.ReadBit(); bool isEntropyImage = this.bitReader.ReadBit();
if (isEntropyImage) if (isEntropyImage)
{ {
uint huffmanPrecision = this.bitReader.Read(3) + 2; uint huffmanPrecision = this.bitReader.ReadBits(3) + 2;
int huffmanXSize = SubSampleSize(xsize, (int)huffmanPrecision); int huffmanXSize = SubSampleSize(xsize, (int)huffmanPrecision);
int huffmanYSize = SubSampleSize(ysize, (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. // and are in the range of[0, 255].All other Huffman code lengths are implicitly zeros.
// Read symbols, codes & code lengths directly. // Read symbols, codes & code lengths directly.
uint numSymbols = this.bitReader.Read(1) + 1; uint numSymbols = this.bitReader.ReadBits(1) + 1;
uint firstSymbolLenCode = this.bitReader.Read(1); uint firstSymbolLenCode = this.bitReader.ReadBits(1);
// The first code is either 1 bit or 8 bit code. // 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; codeLengths[symbol] = 1;
// The second code (if present), is always 8 bit long. // The second code (if present), is always 8 bit long.
if (numSymbols == 2) if (numSymbols == 2)
{ {
symbol = this.bitReader.Read(8); symbol = this.bitReader.ReadBits(8);
codeLengths[symbol] = 1; 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 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. // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros.
var codeLengthCodeLengths = new int[WebPConstants.NumCodeLengthCodes]; var codeLengthCodeLengths = new int[WebPConstants.NumCodeLengthCodes];
uint numCodes = this.bitReader.Read(4) + 4; uint numCodes = this.bitReader.ReadBits(4) + 4;
if (numCodes > WebPConstants.NumCodeLengthCodes) if (numCodes > WebPConstants.NumCodeLengthCodes)
{ {
WebPThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); 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++) 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); 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) private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths)
{ {
Span<HuffmanCode> tableSpan = table.AsSpan();
int maxSymbol; int maxSymbol;
int symbol = 0; int symbol = 0;
int prevCodeLen = WebPConstants.DefaultCodeLength; int prevCodeLen = WebPConstants.DefaultCodeLength;
@ -182,8 +183,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
if (this.bitReader.ReadBit()) if (this.bitReader.ReadBit())
{ {
int lengthNBits = 2 + (2 * (int)this.bitReader.Read(3)); int lengthNBits = 2 + (2 * (int)this.bitReader.ReadBits(3));
maxSymbol = 2 + (int)this.bitReader.Read(lengthNBits); maxSymbol = 2 + (int)this.bitReader.ReadBits(lengthNBits);
} }
else else
{ {
@ -198,7 +199,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
break; 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) if (codeLen < WebPConstants.kCodeLengthLiterals)
{ {
codeLengths[symbol++] = codeLen; codeLengths[symbol++] = codeLen;
@ -213,7 +219,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
int slot = codeLen - WebPConstants.kCodeLengthLiterals; int slot = codeLen - WebPConstants.kCodeLengthLiterals;
int extraBits = WebPConstants.kCodeLengthExtraBits[slot]; int extraBits = WebPConstants.kCodeLengthExtraBits[slot];
int repeatOffset = WebPConstants.kCodeLengthRepeatOffsets[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) if (symbol + repeat > numSymbols)
{ {
return; return;
@ -238,7 +244,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
var transforms = new List<WebPTransformType>(WebPConstants.MaxNumberOfTransforms); var transforms = new List<WebPTransformType>(WebPConstants.MaxNumberOfTransforms);
while (transformPresent) while (transformPresent)
{ {
var transformType = (WebPTransformType)this.bitReader.Read(2); var transformType = (WebPTransformType)this.bitReader.ReadBits(2);
transforms.Add(transformType); transforms.Add(transformType);
switch (transformType) switch (transformType)
{ {
@ -248,7 +254,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
case WebPTransformType.ColorIndexingTransform: case WebPTransformType.ColorIndexingTransform:
// The transform data contains color table size and the entries in the color table. // The transform data contains color table size and the entries in the color table.
// 8 bit value for color table size. // 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? // TODO: color table should follow here?
break; 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 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. // 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 blockWidth = 1 << (int)sizeBits;
int blockHeight = 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, // 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: // 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 blockWidth = 1 << (int)sizeBits;
int blockHeight = 1 << (int)sizeBits; int blockHeight = 1 << (int)sizeBits;
break; break;

2
src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs

@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
bool isShowFrame = bitReader.ReadBit(); 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) private (ReconstructionFilter, LoopFilter) DecodeVersion(byte version)

Loading…
Cancel
Save