Browse Source

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

pull/1147/head
Brian Popow 7 years ago
parent
commit
458fa2f42c
  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> /// </summary>
protected byte[] Data { get; private set; } 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> /// <summary>
/// Copies the raw encoded image data from the stream into a byte array. /// Copies the raw encoded image data from the stream into a byte array.
/// </summary> /// </summary>

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers.Binary;
using System.IO; using System.IO;
using SixLabors.Memory; using SixLabors.Memory;
@ -13,15 +14,17 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary> /// </summary>
internal class Vp8BitReader : BitReaderBase internal class Vp8BitReader : BitReaderBase
{ {
private const int BitsCount = 56;
/// <summary> /// <summary>
/// Current value. /// Current value.
/// </summary> /// </summary>
private long value; private ulong value;
/// <summary> /// <summary>
/// Current range minus 1. In [127, 254] interval. /// Current range minus 1. In [127, 254] interval.
/// </summary> /// </summary>
private int range; private uint range;
/// <summary> /// <summary>
/// Number of valid bits left. /// Number of valid bits left.
@ -36,18 +39,23 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary> /// <summary>
/// End of read buffer. /// End of read buffer.
/// </summary> /// </summary>
private byte bufEnd; // private byte bufEnd;
/// <summary> /// <summary>
/// Max packed-read position on buffer. /// Max packed-read position on buffer.
/// </summary> /// </summary>
private byte bufMax; // private byte bufMax;
/// <summary> /// <summary>
/// True if input is exhausted. /// True if input is exhausted.
/// </summary> /// </summary>
private bool eof; private bool eof;
/// <summary>
/// Byte position in buffer.
/// </summary>
private long pos;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Vp8BitReader"/> class. /// Initializes a new instance of the <see cref="Vp8BitReader"/> class.
/// </summary> /// </summary>
@ -57,27 +65,126 @@ namespace SixLabors.ImageSharp.Formats.WebP
public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator) public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator)
{ {
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, 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 int GetBit(int prob)
public override bool ReadBit()
{ {
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 uint ReadValue(int nBits)
public override uint ReadValue(int nBits)
{ {
Guard.MustBeGreaterThan(nBits, 0, nameof(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) public int ReadSignedValue(int nBits)
{ {
Guard.MustBeGreaterThan(nBits, 0, nameof(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(); 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; this.pos = length;
} }
/// <inheritdoc/> /// <summary>
public override uint ReadValue(int nBits) /// 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)); Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
@ -113,23 +117,37 @@ namespace SixLabors.ImageSharp.Formats.WebP
return 0; return 0;
} }
/// <inheritdoc/> /// <summary>
public override bool ReadBit() /// 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); uint bit = this.ReadValue(1);
return bit != 0; 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() public ulong PrefetchBits()
{ {
return this.value >> (this.bitPos & (Vp8LLbits - 1)); 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() public void FillBitWindow()
{ {
if (this.bitPos >= Vp8LWbits) 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() public bool IsEndOfStream()
{ {
return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits)); 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; } public sbyte YScale { get; set; }
/// <summary> /// <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> /// </summary>
public sbyte ColorSpace { get; set; } public sbyte ColorSpace { get; set; }
/// <summary> /// <summary>
/// Gets or sets the clamp type. /// 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> /// </summary>
public sbyte ClampType { get; set; } public sbyte ClampType { get; set; }
} }

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

@ -8,6 +8,14 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary> /// </summary>
internal class Vp8SegmentHeader 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; } public bool UseSegment { get; set; }
/// <summary> /// <summary>
@ -16,18 +24,19 @@ namespace SixLabors.ImageSharp.Formats.WebP
public bool UpdateMap { get; set; } public bool UpdateMap { get; set; }
/// <summary> /// <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> /// </summary>
public int AbsoluteOrDelta { get; set; } public bool Delta { get; set; }
/// <summary> /// <summary>
/// Gets or sets quantization changes. /// Gets quantization changes.
/// </summary> /// </summary>
public byte[] Quantizer { get; set; } public byte[] Quantizer { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the filter strength for segments. /// Gets the filter strength for segments.
/// </summary> /// </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"); 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(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (imageInfo.IsLossLess) if (imageInfo.IsLossLess)
{ {
@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.ReadImageHeader(); this.ReadImageHeader();
WebPImageInfo imageInfo = this.ReadVp8Info(); 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> /// <summary>
@ -200,12 +200,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
// 3 bytes for the width. // 3 bytes for the width.
this.currentStream.Read(this.buffer, 0, 3); this.currentStream.Read(this.buffer, 0, 3);
this.buffer[3] = 0; this.buffer[3] = 0;
int width = BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; uint width = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1;
// 3 bytes for the height. // 3 bytes for the height.
this.currentStream.Read(this.buffer, 0, 3); this.currentStream.Read(this.buffer, 0, 3);
this.buffer[3] = 0; 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. // Optional chunks ICCP, ALPH and ANIM can follow here.
WebPChunkType chunkType; WebPChunkType chunkType;
@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.currentStream.Read(this.buffer, 0, 4); this.currentStream.Read(this.buffer, 0, 4);
uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); 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: // Frame tag that contains four fields:
// - A 1-bit frame type (0 for key frames, 1 for interframes). // - A 1-bit frame type (0 for key frames, 1 for interframes).
// - A 3-bit version number. // - A 3-bit version number.
@ -328,15 +328,103 @@ namespace SixLabors.ImageSharp.Formats.WebP
} }
this.currentStream.Read(this.buffer, 0, 4); this.currentStream.Read(this.buffer, 0, 4);
int width = BinaryPrimitives.ReadInt16LittleEndian(this.buffer) & 0x3fff; uint tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer);
int height = BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2)) & 0x3fff; 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) if (width is 0 || height is 0)
{ {
WebPThrowHelper.ThrowImageFormatException("width or height can not be zero"); 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); 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() return new WebPImageInfo()
{ {
Width = width, Width = width,
@ -345,6 +433,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
IsLossLess = false, IsLossLess = false,
Features = features, Features = features,
Vp8Profile = (sbyte)version, Vp8Profile = (sbyte)version,
Vp8FrameHeader = vp8FrameHeader,
Vp8SegmentHeader = vp8SegmentHeader,
Vp8FilterHeader = vp8FilterHeader,
Vp8PictureHeader = vp8PictureHeader,
Vp8BitReader = bitReader Vp8BitReader = bitReader
}; };
} }
@ -392,8 +484,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
return new WebPImageInfo() return new WebPImageInfo()
{ {
Width = (int)width, Width = width,
Height = (int)height, Height = height,
BitsPerPixel = WebPBitsPerPixel.Pixel32, BitsPerPixel = WebPBitsPerPixel.Pixel32,
IsLossLess = true, IsLossLess = true,
Features = features, Features = features,

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

@ -8,12 +8,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary> /// <summary>
/// Gets or sets the bitmap width in pixels (signed integer). /// Gets or sets the bitmap width in pixels (signed integer).
/// </summary> /// </summary>
public int Width { get; set; } public uint Width { get; set; }
/// <summary> /// <summary>
/// Gets or sets the bitmap height in pixels (signed integer). /// Gets or sets the bitmap height in pixels (signed integer).
/// </summary> /// </summary>
public int Height { get; set; } public uint Height { get; set; }
/// <summary> /// <summary>
/// Gets or sets the bits per pixel. /// Gets or sets the bits per pixel.
@ -36,7 +36,27 @@ namespace SixLabors.ImageSharp.Formats.WebP
public int Vp8Profile { get; set; } = -1; public int Vp8Profile { get; set; } = -1;
/// <summary> /// <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> /// </summary>
public Vp8LBitReader Vp8LBitReader { get; set; } = null; public Vp8LBitReader Vp8LBitReader { get; set; } = null;

Loading…
Cancel
Save