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.
///