diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index bd7d260e4..cd6249632 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -3,6 +3,7 @@ using System; using System.Buffers.Binary; +using System.IO; using SixLabors.ImageSharp.Formats.WebP.Lossless; namespace SixLabors.ImageSharp.Formats.WebP.BitWriter @@ -21,8 +22,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter private const int WriterBits = 32; - private const int WriterMaxBits = 64; - /// /// Bit accumulator. /// @@ -45,15 +44,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter private int end; + /// + /// Initializes a new instance of the class. + /// + /// The expected size in bytes. public Vp8LBitWriter(int expectedSize) { + // TODO: maybe use memory allocator here. this.buffer = new byte[expectedSize]; this.end = this.buffer.Length; } /// /// Initializes a new instance of the class. - /// Used internally for cloning + /// Used internally for cloning. /// private Vp8LBitWriter(byte[] buffer, ulong bits, int used, int cur) { @@ -107,6 +111,31 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); } + /// + /// Writes the encoded bytes of the image to the stream. Call BitWriterFinish() before this. + /// + /// The stream to write to. + public void WriteToStream(Stream stream) + { + stream.Write(this.buffer.AsSpan(0, this.NumBytes())); + } + + /// + /// Flush leftover bits. + /// + 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; + } + /// /// Internal function for PutBits flushing 32 bits from the written state. /// @@ -125,6 +154,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter this.used -= WriterBits; } + /// + /// Resizes the buffer to write to. + /// + /// The extra size in bytes needed. private void BitWriterResize(int extraSize) { int maxBytes = this.end + this.buffer.Length; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 658a2f58c..22b111fbb 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -3,8 +3,10 @@ using System; using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; +using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Memory; @@ -147,6 +149,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// public Vp8LHashChain HashChain { get; } + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { @@ -162,11 +170,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Encode the main image stream. this.EncodeStream(image); - // TODO: write bytes from the bitwriter to the stream. + // Write bytes from the bitwriter buffer to the stream. + this.bitWriter.BitWriterFinish(); + var numBytes = this.bitWriter.NumBytes(); + var vp8LSize = 1 + numBytes; // One byte extra for the VP8L signature. + var pad = vp8LSize & 1; + var riffSize = WebPConstants.TagSize + WebPConstants.ChunkHeaderSize + vp8LSize + pad; + this.WriteRiffHeader(riffSize, vp8LSize, stream); + this.bitWriter.WriteToStream(stream); } /// - /// Writes the image size to the stream. + /// Writes the image size to the bitwriter buffer. /// /// The input image width. /// The input image height. @@ -182,12 +197,36 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.bitWriter.PutBits(height, WebPConstants.Vp8LImageSizeBits); } + /// + /// Writes a flag indicating if alpha channel is used and the VP8L version to the bitwriter buffer. + /// + /// Indicates if a alpha channel is present. private void WriteAlphaAndVersion(bool hasAlpha) { this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); this.bitWriter.PutBits(WebPConstants.Vp8LVersion, WebPConstants.Vp8LVersionBits); } + /// + /// Writes the RIFF header to the stream. + /// + /// The block length. + /// The size in bytes of the compressed image. + /// The stream to write to. + private void WriteRiffHeader(int riffSize, int vp8LSize, Stream stream) + { + Span buffer = stackalloc byte[4]; + + stream.Write(WebPConstants.RiffFourCc); + BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)riffSize); + stream.Write(buffer); + stream.Write(WebPConstants.WebPHeader); + stream.Write(WebPConstants.Vp8LTag); + BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)vp8LSize); + stream.Write(buffer); + stream.WriteByte(WebPConstants.Vp8LMagicByte); + } + /// /// Encodes the image stream using lossless webp format. /// @@ -323,6 +362,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { bitWriterBest = this.bitWriter.Clone(); } + else + { + bitWriterBest = this.bitWriter; + } for (int lz77sIdx = 0; lz77sIdx < lz77sTypesToTrySize; lz77sIdx++) { diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index bbc736b59..d90a0c51f 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -30,6 +30,22 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x2A }; + /// + /// Signature byte which identifies a VP8L header. + /// + public const byte Vp8LMagicByte = 0x2F; + + /// + /// Header bytes identifying a lossless image. + /// + public static readonly byte[] Vp8LTag = + { + 0x56, // V + 0x50, // P + 0x38, // 8 + 0x4C // L + }; + /// /// The header bytes identifying RIFF file. /// @@ -52,11 +68,6 @@ namespace SixLabors.ImageSharp.Formats.WebP 0x50 // P }; - /// - /// Signature byte which identifies a VP8L header. - /// - public const byte Vp8LMagicByte = 0x2F; - /// /// 3 bits reserved for version. /// @@ -67,6 +78,16 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public const int Vp8LImageSizeBits = 14; + /// + /// Size of a chunk header. + /// + public const int ChunkHeaderSize = 8; + + /// + /// Size of a chunk tag (e.g. "VP8L"). + /// + public const int TagSize = 4; + /// /// The Vp8L version 0. ///