mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
7.3 KiB
243 lines
7.3 KiB
// Copyright (c) Six Labors and contributors.
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
|
using System;
|
|
using System.Buffers.Binary;
|
|
using System.IO;
|
|
using System.Runtime.CompilerServices;
|
|
using SixLabors.ImageSharp.Memory;
|
|
|
|
namespace SixLabors.ImageSharp.Formats.WebP
|
|
{
|
|
/// <summary>
|
|
/// A bit reader for VP8 streams.
|
|
/// </summary>
|
|
internal class Vp8BitReader : BitReaderBase
|
|
{
|
|
private const int BitsCount = 56;
|
|
|
|
/// <summary>
|
|
/// Current value.
|
|
/// </summary>
|
|
private ulong value;
|
|
|
|
/// <summary>
|
|
/// Current range minus 1. In [127, 254] interval.
|
|
/// </summary>
|
|
private uint range;
|
|
|
|
/// <summary>
|
|
/// Number of valid bits left.
|
|
/// </summary>
|
|
private int bits;
|
|
|
|
/// <summary>
|
|
/// Max packed-read position of the buffer.
|
|
/// </summary>
|
|
private uint bufferMax;
|
|
|
|
private uint bufferEnd;
|
|
|
|
/// <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>
|
|
/// <param name="inputStream">The input stream to read from.</param>
|
|
/// <param name="imageDataSize">The raw image data size in bytes.</param>
|
|
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
|
|
/// <param name="partitionLength">The partition length.</param>
|
|
/// <param name="startPos">Start index in the data array. Defaults to 0.</param>
|
|
public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0)
|
|
{
|
|
this.ImageDataSize = imageDataSize;
|
|
this.PartitionLength = partitionLength;
|
|
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator);
|
|
this.InitBitreader(partitionLength, startPos);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Vp8BitReader"/> class.
|
|
/// </summary>
|
|
/// <param name="imageData">The raw encoded image data.</param>
|
|
/// <param name="partitionLength">The partition length.</param>
|
|
/// <param name="startPos">Start index in the data array. Defaults to 0.</param>
|
|
public Vp8BitReader(byte[] imageData, uint partitionLength, int startPos = 0)
|
|
{
|
|
this.Data = imageData;
|
|
this.ImageDataSize = (uint)imageData.Length;
|
|
this.PartitionLength = partitionLength;
|
|
this.InitBitreader(partitionLength, startPos);
|
|
}
|
|
|
|
public int Pos
|
|
{
|
|
get { return (int)this.pos; }
|
|
}
|
|
|
|
public uint ImageDataSize { get; }
|
|
|
|
public uint PartitionLength { get; }
|
|
|
|
public uint Remaining { get; set; }
|
|
|
|
public int GetBit(int prob)
|
|
{
|
|
uint range = this.range;
|
|
if (this.bits < 0)
|
|
{
|
|
this.LoadNewBytes();
|
|
}
|
|
|
|
int pos = this.bits;
|
|
uint split = (uint)((range * prob) >> 8);
|
|
ulong value = this.value >> pos;
|
|
bool bit = 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;
|
|
}
|
|
|
|
// Simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here)
|
|
public int GetSigned(int v)
|
|
{
|
|
if (this.bits < 0)
|
|
{
|
|
this.LoadNewBytes();
|
|
}
|
|
|
|
int pos = this.bits;
|
|
uint split = this.range >> 1;
|
|
ulong value = this.value >> pos;
|
|
ulong mask = (split - value) >> 31; // -1 or 0
|
|
this.bits -= 1;
|
|
this.range += (uint)mask;
|
|
this.range |= 1;
|
|
this.value -= ((split + 1) & mask) << pos;
|
|
|
|
return (v ^ (int)mask) - (int)mask;
|
|
}
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)]
|
|
public bool ReadBool()
|
|
{
|
|
return this.ReadValue(1) is 1;
|
|
}
|
|
|
|
public uint ReadValue(int nBits)
|
|
{
|
|
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
|
|
|
|
uint v = 0;
|
|
while (nBits-- > 0)
|
|
{
|
|
v |= (uint)this.GetBit(0x80) << nBits;
|
|
}
|
|
|
|
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 InitBitreader(uint size, int pos = 0)
|
|
{
|
|
this.range = 255 - 1;
|
|
this.value = 0;
|
|
this.bits = -8; // to load the very first 8 bits.
|
|
this.eof = false;
|
|
this.pos = pos;
|
|
this.bufferEnd = (uint)(pos + size);
|
|
this.bufferMax = (uint)(size > 8 ? pos + size - 8 + 1 : pos);
|
|
|
|
this.LoadNewBytes();
|
|
}
|
|
|
|
private void LoadNewBytes()
|
|
{
|
|
if (this.pos < this.bufferMax)
|
|
{
|
|
ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan((int)this.pos, 8));
|
|
this.pos += BitsCount >> 3;
|
|
ulong 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.bufferEnd)
|
|
{
|
|
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.
|
|
}
|
|
}
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)]
|
|
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;
|
|
}
|
|
|
|
// Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n).
|
|
[MethodImpl(InliningOptions.ShortMethod)]
|
|
private int BitsLog2Floor(uint n)
|
|
{
|
|
int logValue = 0;
|
|
while (n >= 256)
|
|
{
|
|
logValue += 8;
|
|
n >>= 8;
|
|
}
|
|
|
|
return logValue + WebPLookupTables.LogTable8bit[n];
|
|
}
|
|
}
|
|
}
|
|
|