diff --git a/src/ImageSharp/Formats/WebP/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReaderBase.cs
index a07c68ed94..0a62299eac 100644
--- a/src/ImageSharp/Formats/WebP/BitReaderBase.cs
+++ b/src/ImageSharp/Formats/WebP/BitReaderBase.cs
@@ -19,19 +19,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
protected byte[] Data { get; private set; }
- ///
- /// Reads a single bit from the stream.
- ///
- /// True if the bit read was 1, false otherwise.
- public abstract bool ReadBit();
-
- ///
- /// 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).
- /// A ushort value.
- public abstract uint ReadValue(int nBits);
-
///
/// Copies the raw encoded image data from the stream into a byte array.
///
diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs
index cac683b1f4..a3996b08da 100644
--- a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs
+++ b/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
///
internal class Vp8BitReader : BitReaderBase
{
+ private const int BitsCount = 56;
+
///
/// Current value.
///
- private long value;
+ private ulong value;
///
/// Current range minus 1. In [127, 254] interval.
///
- private int range;
+ private uint range;
///
/// Number of valid bits left.
@@ -36,18 +39,23 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
/// End of read buffer.
///
- private byte bufEnd;
+ // private byte bufEnd;
///
/// Max packed-read position on buffer.
///
- private byte bufMax;
+ // private byte bufMax;
///
/// True if input is exhausted.
///
private bool eof;
+ ///
+ /// Byte position in buffer.
+ ///
+ private long pos;
+
///
/// Initializes a new instance of the class.
///
@@ -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();
}
- ///
- 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;
}
- ///
- 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);
}
}
}
diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs
new file mode 100644
index 0000000000..7093b1bf0a
--- /dev/null
+++ b/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];
+ }
+
+ ///
+ /// Gets or sets the loop filter.
+ ///
+ 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; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
index 27dab46878..8938124728 100644
--- a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
@@ -95,8 +95,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
this.pos = length;
}
- ///
- 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.
+ ///
+ /// The number of bits to read (should not exceed 16).
+ /// A ushort value.
+ public uint ReadValue(int nBits)
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
@@ -113,23 +117,37 @@ namespace SixLabors.ImageSharp.Formats.WebP
return 0;
}
- ///
- public override bool ReadBit()
+ ///
+ /// Reads a single bit from the stream.
+ ///
+ /// True if the bit read was 1, false otherwise.
+ public bool ReadBit()
{
uint bit = this.ReadValue(1);
return bit != 0;
}
- public void AdvanceBitPosition(int bitPosition)
+ ///
+ /// For jumping over a number of bits in the bit stream when accessed with PrefetchBits and FillBitWindow.
+ ///
+ /// The number of bits to advance the position.
+ public void AdvanceBitPosition(int numberOfBits)
{
- this.bitPos += bitPosition;
+ this.bitPos += numberOfBits;
}
+ ///
+ /// Return the pre-fetched bits, so they can be looked up.
+ ///
+ /// Prefetched bits.
public ulong PrefetchBits()
{
return this.value >> (this.bitPos & (Vp8LLbits - 1));
}
+ ///
+ /// Advances the read buffer by 4 bytes to make room for reading next 32 bits.
+ ///
public void FillBitWindow()
{
if (this.bitPos >= Vp8LWbits)
@@ -138,6 +156,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
+ ///
+ /// Returns true if there was an attempt at reading bit past the end of the buffer.
+ ///
+ /// True, if end of buffer was reached.
public bool IsEndOfStream()
{
return this.eos || ((this.pos == this.len) && (this.bitPos > Vp8LLbits));
diff --git a/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs b/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs
index f9f0fd37d2..3f80b13b29 100644
--- a/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs
@@ -26,12 +26,16 @@ namespace SixLabors.ImageSharp.Formats.WebP
public sbyte YScale { get; set; }
///
- /// 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.
///
public sbyte ColorSpace { get; set; }
///
/// 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.
///
public sbyte ClampType { get; set; }
}
diff --git a/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs
index 993b744845..27bce1f043 100644
--- a/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs
+++ b/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs
@@ -8,6 +8,14 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
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; }
///
@@ -16,18 +24,19 @@ namespace SixLabors.ImageSharp.Formats.WebP
public bool UpdateMap { get; set; }
///
- /// 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.
///
- public int AbsoluteOrDelta { get; set; }
+ public bool Delta { get; set; }
///
- /// Gets or sets quantization changes.
+ /// Gets quantization changes.
///
- public byte[] Quantizer { get; set; }
+ public byte[] Quantizer { get; private set; }
///
- /// Gets or sets the filter strength for segments.
+ /// Gets the filter strength for segments.
///
- public byte[] FilterStrength { get; set; }
+ public byte[] FilterStrength { get; private set; }
}
}
diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
index 1816e56ae0..f0b0233cf5 100644
--- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
+++ b/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(this.configuration, imageInfo.Width, imageInfo.Height, this.Metadata);
+ var image = new Image(this.configuration, (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata);
Buffer2D 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);
}
///
@@ -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,
diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs
index 08bfe2314c..1eeb4d2e0a 100644
--- a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs
+++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs
@@ -8,12 +8,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
/// Gets or sets the bitmap width in pixels (signed integer).
///
- public int Width { get; set; }
+ public uint Width { get; set; }
///
/// Gets or sets the bitmap height in pixels (signed integer).
///
- public int Height { get; set; }
+ public uint Height { get; set; }
///
/// Gets or sets the bits per pixel.
@@ -36,7 +36,27 @@ namespace SixLabors.ImageSharp.Formats.WebP
public int Vp8Profile { get; set; } = -1;
///
- /// Gets or sets the Vp8L bitreader. Will be null, if its not lossless image.
+ /// Gets or sets the VP8 frame header.
+ ///
+ public Vp8FrameHeader Vp8FrameHeader { get; set; }
+
+ ///
+ /// Gets or sets the VP8 picture header.
+ ///
+ public Vp8PictureHeader Vp8PictureHeader { get; set; }
+
+ ///
+ /// Gets or sets the VP8 segment header.
+ ///
+ public Vp8SegmentHeader Vp8SegmentHeader { get; set; }
+
+ ///
+ /// Gets or sets the VP8 filter header.
+ ///
+ public Vp8FilterHeader Vp8FilterHeader { get; set; }
+
+ ///
+ /// Gets or sets the VP8L bitreader. Will be null, if its not lossless image.
///
public Vp8LBitReader Vp8LBitReader { get; set; } = null;