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.
// 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
/// <summary>
/// A bit reader for VP8 streams.
/// </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>
/// 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>
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;
/// <summary>
/// Gets a value indicating whether the offset is inside the inputStream length.
/// </summary>
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;
}
/// <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>
/// Reads a unsigned short value from the inputStream. The bits of each byte are read in least-significant-bit-first order.
/// </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>
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;
}
/// <summary>
/// Reads one bit.
/// </summary>
/// <returns>True, if the bit is one, otherwise false.</returns>
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();
}
/// <summary>
/// Advances the inputStream by one Bit.
/// </summary>
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.
}
}
}

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

@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
Buffer2D<TPixel> 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<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)
{
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.
/// </summary>
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;
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<HuffmanCode> 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<WebPTransformType>(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;

2
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)

Loading…
Cancel
Save