Browse Source

WIP: Parse further VP8 header: SegmentHeader, FilterHeader, implement BitReader for VP8 (unfinished)

pull/1552/head
Brian Popow 7 years ago
parent
commit
023a64ce12
  1. 13
      src/ImageSharp/Formats/WebP/BitReaderBase.cs
  2. 127
      src/ImageSharp/Formats/WebP/Vp8BitReader.cs
  3. 35
      src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs
  4. 34
      src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
  5. 6
      src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs
  6. 21
      src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs
  7. 110
      src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
  8. 26
      src/ImageSharp/Formats/WebP/WebPImageInfo.cs

13
src/ImageSharp/Formats/WebP/BitReaderBase.cs

@ -19,19 +19,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
protected byte[] Data { get; private set; }
/// <summary>
/// Reads a single bit from the stream.
/// </summary>
/// <returns>True if the bit read was 1, false otherwise.</returns>
public abstract bool ReadBit();
/// <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="nBits">The number of bits to read (should not exceed 16).</param>
/// <returns>A ushort value.</returns>
public abstract uint ReadValue(int nBits);
/// <summary>
/// Copies the raw encoded image data from the stream into a byte array.
/// </summary>

127
src/ImageSharp/Formats/WebP/Vp8BitReader.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.IO;
using SixLabors.Memory;
@ -13,15 +14,17 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
internal class Vp8BitReader : BitReaderBase
{
private const int BitsCount = 56;
/// <summary>
/// Current value.
/// </summary>
private long value;
private ulong value;
/// <summary>
/// Current range minus 1. In [127, 254] interval.
/// </summary>
private int range;
private uint range;
/// <summary>
/// Number of valid bits left.
@ -36,18 +39,23 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary>
/// End of read buffer.
/// </summary>
private byte bufEnd;
// private byte bufEnd;
/// <summary>
/// Max packed-read position on buffer.
/// </summary>
private byte bufMax;
// private byte bufMax;
/// <summary>
/// True if input is exhausted.
/// </summary>
private bool eof;
/// <summary>
/// Byte position in buffer.
/// </summary>
private long pos;
/// <summary>
/// Initializes a new instance of the <see cref="Vp8BitReader"/> class.
/// </summary>
@ -57,27 +65,126 @@ namespace SixLabors.ImageSharp.Formats.WebP
public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator)
{
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator);
this.range = 255 - 1;
this.value = 0;
this.bits = -8; // to load the very first 8bits;
this.eof = false;
this.pos = 0;
this.LoadNewBytes();
}
/// <inheritdoc/>
public override bool ReadBit()
public int GetBit(int prob)
{
throw new NotImplementedException();
Guard.MustBeGreaterThan(prob, 0, nameof(prob));
uint range = this.range;
if (this.bits < 0)
{
this.LoadNewBytes();
}
int pos = this.bits;
uint split = (uint)((range * prob) >> 8);
bool bit = this.value > split;
if (bit)
{
range -= split;
this.value -= (ulong)(split + 1) << pos;
}
else
{
range = split + 1;
}
int shift = 7 ^ this.BitsLog2Floor(range);
range <<= shift;
this.bits -= shift;
this.range = range - 1;
return bit ? 1 : 0;
}
public bool ReadBool()
{
return this.ReadValue(1) is 1;
}
/// <inheritdoc/>
public override uint ReadValue(int nBits)
public uint ReadValue(int nBits)
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
throw new NotImplementedException();
uint v = 0;
while (this.bits-- > 0)
{
v |= (uint)this.GetBit(0x80) << this.bits;
}
return v;
}
public int ReadSignedValue(int nBits)
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
int value = (int)this.ReadValue(nBits);
return this.ReadValue(1) != 0 ? -value : value;
}
private void LoadNewBytes()
{
if (this.pos < this.Data.Length)
{
ulong bits;
ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan().Slice((int)this.pos, 8));
this.pos += BitsCount >> 3;
this.buf = this.Data[BitsCount >> 3];
bits = this.ByteSwap64(inBits);
bits >>= 64 - BitsCount;
this.value = bits | (this.value << BitsCount);
this.bits += BitsCount;
}
else
{
this.LoadFinalBytes();
}
}
private void LoadFinalBytes()
{
// Only read 8bits at a time.
if (this.pos < this.Data.Length)
{
this.bits += 8;
this.value = this.Data[this.pos++] | (this.value << 8);
}
else if (!this.eof)
{
this.value <<= 8;
this.bits += 8;
this.eof = true;
}
else
{
this.bits = 0; // This is to avoid undefined behaviour with shifts.
}
}
private ulong ByteSwap64(ulong x)
{
x = ((x & 0xffffffff00000000ul) >> 32) | ((x & 0x00000000fffffffful) << 32);
x = ((x & 0xffff0000ffff0000ul) >> 16) | ((x & 0x0000ffff0000fffful) << 16);
x = ((x & 0xff00ff00ff00ff00ul) >> 8) | ((x & 0x00ff00ff00ff00fful) << 8);
return x;
}
private int BitsLog2Floor(uint n)
{
long firstSetBit;
throw new NotImplementedException();
// BitScanReverse(firstSetBit, n);
}
}
}

35
src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.WebP
{
internal class Vp8FilterHeader
{
private const int NumRefLfDeltas = 4;
private const int NumModeLfDeltas = 4;
public Vp8FilterHeader()
{
this.RefLfDelta = new int[NumRefLfDeltas];
this.ModeLfDelta = new int[NumModeLfDeltas];
}
/// <summary>
/// Gets or sets the loop filter.
/// </summary>
public LoopFilter LoopFilter { get; set; }
// [0..63]
public int Level { get; set; }
// [0..7]
public int Sharpness { get; set; }
public bool UseLfDelta { get; set; }
public int[] RefLfDelta { get; private set; }
public int[] ModeLfDelta { get; private set; }
}
}

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

@ -95,8 +95,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.pos = length;
}
/// <inheritdoc/>
public override uint ReadValue(int nBits)
/// <summary>
/// Reads a unsigned short value from the buffer. The bits of each byte are read in least-significant-bit-first order.
/// </summary>
/// <param name="nBits">The number of bits to read (should not exceed 16).</param>
/// <returns>A ushort value.</returns>
public uint ReadValue(int nBits)
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
@ -113,23 +117,37 @@ namespace SixLabors.ImageSharp.Formats.WebP
return 0;
}
/// <inheritdoc/>
public override bool ReadBit()
/// <summary>
/// Reads a single bit from the stream.
/// </summary>
/// <returns>True if the bit read was 1, false otherwise.</returns>
public bool ReadBit()
{
uint bit = this.ReadValue(1);
return bit != 0;
}
public void AdvanceBitPosition(int bitPosition)
/// <summary>
/// For jumping over a number of bits in the bit stream when accessed with PrefetchBits and FillBitWindow.
/// </summary>
/// <param name="numberOfBits">The number of bits to advance the position.</param>
public void AdvanceBitPosition(int numberOfBits)
{
this.bitPos += bitPosition;
this.bitPos += numberOfBits;
}
/// <summary>
/// Return the pre-fetched bits, so they can be looked up.
/// </summary>
/// <returns>Prefetched bits.</returns>
public ulong PrefetchBits()
{
return this.value >> (this.bitPos & (Vp8LLbits - 1));
}
/// <summary>
/// Advances the read buffer by 4 bytes to make room for reading next 32 bits.
/// </summary>
public void FillBitWindow()
{
if (this.bitPos >= Vp8LWbits)
@ -138,6 +156,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
/// <summary>
/// Returns true if there was an attempt at reading bit past the end of the buffer.
/// </summary>
/// <returns>True, if end of buffer was reached.</returns>
public bool IsEndOfStream()
{
return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits));

6
src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs

@ -26,12 +26,16 @@ namespace SixLabors.ImageSharp.Formats.WebP
public sbyte YScale { get; set; }
/// <summary>
/// Gets or sets the colorspace. 0 = YCbCr.
/// Gets or sets the colorspace.
/// 0 - YUV color space similar to the YCrCb color space defined in.
/// 1 - Reserved for future use.
/// </summary>
public sbyte ColorSpace { get; set; }
/// <summary>
/// Gets or sets the clamp type.
/// 0 - Decoders are required to clamp the reconstructed pixel values to between 0 and 255 (inclusive).
/// 1 - Reconstructed pixel values are guaranteed to be between 0 and 255; no clamping is necessary.
/// </summary>
public sbyte ClampType { get; set; }
}

21
src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs

@ -8,6 +8,14 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
internal class Vp8SegmentHeader
{
private const int NumMbSegments = 4;
public Vp8SegmentHeader()
{
this.Quantizer = new byte[NumMbSegments];
this.FilterStrength = new byte[NumMbSegments];
}
public bool UseSegment { get; set; }
/// <summary>
@ -16,18 +24,19 @@ namespace SixLabors.ImageSharp.Formats.WebP
public bool UpdateMap { get; set; }
/// <summary>
/// Gets or sets the absolute or delta values for quantizer and filter.
/// Gets or sets a value indicating whether to use delta values for quantizer and filter.
/// If this value is false, absolute values are used.
/// </summary>
public int AbsoluteOrDelta { get; set; }
public bool Delta { get; set; }
/// <summary>
/// Gets or sets quantization changes.
/// Gets quantization changes.
/// </summary>
public byte[] Quantizer { get; set; }
public byte[] Quantizer { get; private set; }
/// <summary>
/// Gets or sets the filter strength for segments.
/// Gets the filter strength for segments.
/// </summary>
public byte[] FilterStrength { get; set; }
public byte[] FilterStrength { get; private set; }
}
}

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

@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
WebPThrowHelper.ThrowNotSupportedException("Animations are not supported");
}
var image = new Image<TPixel>(this.configuration, imageInfo.Width, imageInfo.Height, this.Metadata);
var image = new Image<TPixel>(this.configuration, (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (imageInfo.IsLossLess)
{
@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.ReadImageHeader();
WebPImageInfo imageInfo = this.ReadVp8Info();
return new ImageInfo(new PixelTypeInfo((int)imageInfo.BitsPerPixel), imageInfo.Width, imageInfo.Height, this.Metadata);
return new ImageInfo(new PixelTypeInfo((int)imageInfo.BitsPerPixel), (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata);
}
/// <summary>
@ -200,12 +200,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
// 3 bytes for the width.
this.currentStream.Read(this.buffer, 0, 3);
this.buffer[3] = 0;
int width = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1;
uint width = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1;
// 3 bytes for the height.
this.currentStream.Read(this.buffer, 0, 3);
this.buffer[3] = 0;
int height = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1;
uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1;
// Optional chunks ICCP, ALPH and ANIM can follow here.
WebPChunkType chunkType;
@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.currentStream.Read(this.buffer, 0, 4);
uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
// https://tools.ietf.org/html/rfc6386#page-30
// See paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30
// Frame tag that contains four fields:
// - A 1-bit frame type (0 for key frames, 1 for interframes).
// - A 3-bit version number.
@ -328,15 +328,103 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
this.currentStream.Read(this.buffer, 0, 4);
int width = BinaryPrimitives.ReadInt16LittleEndian(this.buffer) & 0x3fff;
int height = BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2)) & 0x3fff;
uint tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer);
uint width = tmp & 0x3fff;
sbyte xScale = (sbyte)(tmp >> 6);
tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2));
uint height = tmp & 0x3fff;
sbyte yScale = (sbyte)(tmp >> 6);
if (width is 0 || height is 0)
{
WebPThrowHelper.ThrowImageFormatException("width or height can not be zero");
}
var vp8FrameHeader = new Vp8FrameHeader()
{
KeyFrame = true,
Profile = (sbyte)version,
PartitionLength = partitionLength
};
var bitReader = new Vp8BitReader(this.currentStream, dataSize - 10, this.memoryAllocator);
// Paragraph 9.2: color space and clamp type follow
sbyte colorSpace = (sbyte)bitReader.ReadValue(1);
sbyte clampType = (sbyte)bitReader.ReadValue(1);
var vp8PictureHeader = new Vp8PictureHeader()
{
Width = width,
Height = height,
XScale = xScale,
YScale = yScale,
ColorSpace = colorSpace,
ClampType = clampType
};
// Paragraph 9.3: Parse the segment header.
var vp8SegmentHeader = new Vp8SegmentHeader();
vp8SegmentHeader.UseSegment = bitReader.ReadBool();
if (vp8SegmentHeader.UseSegment)
{
vp8SegmentHeader.UpdateMap = bitReader.ReadBool();
bool updateData = bitReader.ReadBool();
if (updateData)
{
vp8SegmentHeader.Delta = bitReader.ReadBool();
for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++)
{
bool hasValue = bitReader.ReadBool();
uint quantizeValue = hasValue ? bitReader.ReadValue(7) : 0;
vp8SegmentHeader.Quantizer[i] = (byte)quantizeValue;
}
for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++)
{
bool hasValue = bitReader.ReadBool();
uint filterStrengthValue = hasValue ? bitReader.ReadValue(6) : 0;
vp8SegmentHeader.FilterStrength[i] = (byte)filterStrengthValue;
}
if (vp8SegmentHeader.UpdateMap)
{
// TODO: Read VP8Proba
}
}
}
// Paragraph 9.4: Parse the filter specs.
var vp8FilterHeader = new Vp8FilterHeader();
vp8FilterHeader.LoopFilter = bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Normal;
vp8FilterHeader.Level = (int)bitReader.ReadValue(6);
vp8FilterHeader.Sharpness = (int)bitReader.ReadValue(3);
vp8FilterHeader.UseLfDelta = bitReader.ReadBool();
if (vp8FilterHeader.UseLfDelta)
{
// Update lf-delta?
if (bitReader.ReadBool())
{
for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++)
{
bool hasValue = bitReader.ReadBool();
if (hasValue)
{
vp8FilterHeader.RefLfDelta[i] = bitReader.ReadSignedValue(6);
}
}
for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++)
{
bool hasValue = bitReader.ReadBool();
if (hasValue)
{
vp8FilterHeader.ModeLfDelta[i] = bitReader.ReadSignedValue(6);
}
}
}
}
// TODO: ParsePartitions
return new WebPImageInfo()
{
Width = width,
@ -345,6 +433,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
IsLossLess = false,
Features = features,
Vp8Profile = (sbyte)version,
Vp8FrameHeader = vp8FrameHeader,
Vp8SegmentHeader = vp8SegmentHeader,
Vp8FilterHeader = vp8FilterHeader,
Vp8PictureHeader = vp8PictureHeader,
Vp8BitReader = bitReader
};
}
@ -392,8 +484,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
return new WebPImageInfo()
{
Width = (int)width,
Height = (int)height,
Width = width,
Height = height,
BitsPerPixel = WebPBitsPerPixel.Pixel32,
IsLossLess = true,
Features = features,

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

@ -8,12 +8,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary>
/// Gets or sets the bitmap width in pixels (signed integer).
/// </summary>
public int Width { get; set; }
public uint Width { get; set; }
/// <summary>
/// Gets or sets the bitmap height in pixels (signed integer).
/// </summary>
public int Height { get; set; }
public uint Height { get; set; }
/// <summary>
/// Gets or sets the bits per pixel.
@ -36,7 +36,27 @@ namespace SixLabors.ImageSharp.Formats.WebP
public int Vp8Profile { get; set; } = -1;
/// <summary>
/// Gets or sets the Vp8L bitreader. Will be null, if its not lossless image.
/// Gets or sets the VP8 frame header.
/// </summary>
public Vp8FrameHeader Vp8FrameHeader { get; set; }
/// <summary>
/// Gets or sets the VP8 picture header.
/// </summary>
public Vp8PictureHeader Vp8PictureHeader { get; set; }
/// <summary>
/// Gets or sets the VP8 segment header.
/// </summary>
public Vp8SegmentHeader Vp8SegmentHeader { get; set; }
/// <summary>
/// Gets or sets the VP8 filter header.
/// </summary>
public Vp8FilterHeader Vp8FilterHeader { get; set; }
/// <summary>
/// Gets or sets the VP8L bitreader. Will be null, if its not lossless image.
/// </summary>
public Vp8LBitReader Vp8LBitReader { get; set; } = null;

Loading…
Cancel
Save