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.
187 lines
5.6 KiB
187 lines
5.6 KiB
// Copyright (c) Six Labors.
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
|
using System;
|
|
using System.Buffers.Binary;
|
|
using System.IO;
|
|
using SixLabors.ImageSharp.Formats.WebP.Lossless;
|
|
|
|
namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
|
|
{
|
|
/// <summary>
|
|
/// A bit writer for writing lossless webp streams.
|
|
/// </summary>
|
|
internal class Vp8LBitWriter
|
|
{
|
|
/// <summary>
|
|
/// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed.
|
|
/// </summary>
|
|
private const int MinExtraSize = 32768;
|
|
|
|
private const int WriterBytes = 4;
|
|
|
|
private const int WriterBits = 32;
|
|
|
|
/// <summary>
|
|
/// Bit accumulator.
|
|
/// </summary>
|
|
private ulong bits;
|
|
|
|
/// <summary>
|
|
/// Number of bits used in accumulator.
|
|
/// </summary>
|
|
private int used;
|
|
|
|
/// <summary>
|
|
/// Buffer to write to.
|
|
/// </summary>
|
|
private byte[] buffer;
|
|
|
|
/// <summary>
|
|
/// Current write position.
|
|
/// </summary>
|
|
private int cur;
|
|
|
|
private int end;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Vp8LBitWriter"/> class.
|
|
/// </summary>
|
|
/// <param name="expectedSize">The expected size in bytes.</param>
|
|
public Vp8LBitWriter(int expectedSize)
|
|
{
|
|
// TODO: maybe use memory allocator here.
|
|
this.buffer = new byte[expectedSize];
|
|
this.end = this.buffer.Length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Vp8LBitWriter"/> class.
|
|
/// Used internally for cloning.
|
|
/// </summary>
|
|
private Vp8LBitWriter(byte[] buffer, ulong bits, int used, int cur)
|
|
{
|
|
this.buffer = buffer;
|
|
this.bits = bits;
|
|
this.used = used;
|
|
this.cur = cur;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This function writes bits into bytes in increasing addresses (little endian),
|
|
/// and within a byte least-significant-bit first. This function can write up to 32 bits in one go.
|
|
/// </summary>
|
|
public void PutBits(uint bits, int nBits)
|
|
{
|
|
if (nBits > 0)
|
|
{
|
|
if (this.used >= 32)
|
|
{
|
|
this.PutBitsFlushBits();
|
|
}
|
|
|
|
this.bits |= (ulong)bits << this.used;
|
|
this.used += nBits;
|
|
}
|
|
}
|
|
|
|
public void WriteHuffmanCode(HuffmanTreeCode code, int codeIndex)
|
|
{
|
|
int depth = code.CodeLengths[codeIndex];
|
|
int symbol = code.Codes[codeIndex];
|
|
this.PutBits((uint)symbol, depth);
|
|
}
|
|
|
|
public void WriteHuffmanCodeWithExtraBits(HuffmanTreeCode code, int codeIndex, int bits, int nBits)
|
|
{
|
|
int depth = code.CodeLengths[codeIndex];
|
|
int symbol = code.Codes[codeIndex];
|
|
this.PutBits((uint)((bits << depth) | symbol), depth + nBits);
|
|
}
|
|
|
|
public int NumBytes()
|
|
{
|
|
return this.cur + ((this.used + 7) >> 3);
|
|
}
|
|
|
|
public Vp8LBitWriter Clone()
|
|
{
|
|
var clonedBuffer = new byte[this.buffer.Length];
|
|
Buffer.BlockCopy(this.buffer, 0, clonedBuffer, 0, this.cur);
|
|
return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the encoded bytes of the image to the stream. Call BitWriterFinish() before this.
|
|
/// </summary>
|
|
/// <param name="stream">The stream to write to.</param>
|
|
public void WriteToStream(Stream stream)
|
|
{
|
|
stream.Write(this.buffer.AsSpan(0, this.NumBytes()));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flush leftover bits.
|
|
/// </summary>
|
|
public void BitWriterFinish()
|
|
{
|
|
this.BitWriterResize((this.used + 7) >> 3);
|
|
while (this.used > 0)
|
|
{
|
|
this.buffer[this.cur++] = (byte)this.bits;
|
|
this.bits >>= 8;
|
|
this.used -= 8;
|
|
}
|
|
|
|
this.used = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal function for PutBits flushing 32 bits from the written state.
|
|
/// </summary>
|
|
private void PutBitsFlushBits()
|
|
{
|
|
// If needed, make some room by flushing some bits out.
|
|
if (this.cur + WriterBytes > this.end)
|
|
{
|
|
var extraSize = (this.end - this.cur) + MinExtraSize;
|
|
this.BitWriterResize(extraSize);
|
|
}
|
|
|
|
BinaryPrimitives.WriteUInt64LittleEndian(this.buffer.AsSpan(this.cur), this.bits);
|
|
this.cur += WriterBytes;
|
|
this.bits >>= WriterBits;
|
|
this.used -= WriterBits;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resizes the buffer to write to.
|
|
/// </summary>
|
|
/// <param name="extraSize">The extra size in bytes needed.</param>
|
|
private void BitWriterResize(int extraSize)
|
|
{
|
|
int maxBytes = this.end + this.buffer.Length;
|
|
int sizeRequired = this.cur + extraSize;
|
|
|
|
if (maxBytes > 0 && sizeRequired < maxBytes)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int newSize = (3 * maxBytes) >> 1;
|
|
if (newSize < sizeRequired)
|
|
{
|
|
newSize = sizeRequired;
|
|
}
|
|
|
|
// make new size multiple of 1k
|
|
newSize = ((newSize >> 10) + 1) << 10;
|
|
if (this.cur > 0)
|
|
{
|
|
Array.Resize(ref this.buffer, newSize);
|
|
}
|
|
|
|
this.end = this.buffer.Length;
|
|
}
|
|
}
|
|
}
|
|
|