diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
index e9f50fb493..aff768543c 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
@@ -48,8 +48,6 @@ internal class Vp8BitWriter : BitWriterBase
private uint pos;
- private readonly int maxPos;
-
///
/// Initializes a new instance of the class.
///
@@ -63,7 +61,6 @@ public Vp8BitWriter(int expectedSize, Vp8Encoder enc)
this.run = 0;
this.nbBits = -8;
this.pos = 0;
- this.maxPos = 0;
this.enc = enc;
}
@@ -185,12 +182,15 @@ public int PutCoeffs(int ctx, Vp8Residual residual)
public override void BitWriterResize(int extraSize)
{
long neededSize = this.pos + extraSize;
- if (neededSize <= this.maxPos)
+
+ // Compare against the current capacity and grow from it, so the buffer only reallocates
+ // when it is actually full and grows geometrically (libwebp tracks this as max_pos).
+ if (neededSize <= this.Buffer.Length)
{
return;
}
- this.ResizeBuffer(this.maxPos, (int)neededSize);
+ this.ResizeBuffer(this.Buffer.Length, (int)neededSize);
}
///
diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8BitWriterTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8BitWriterTests.cs
new file mode 100644
index 0000000000..4bd5eeae0a
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8BitWriterTests.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Webp;
+using SixLabors.ImageSharp.Formats.Webp.BitWriter;
+using SixLabors.ImageSharp.Formats.Webp.Lossy;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Webp;
+
+[Trait("Format", "Webp")]
+public class Vp8BitWriterTests
+{
+ [Fact]
+ public void BitWriterResize_WithinCapacity_DoesNotReallocate()
+ {
+ using Vp8Encoder encoder = CreateEncoder();
+ Vp8BitWriter writer = new(4096, encoder);
+ byte[] buffer = writer.Buffer;
+
+ writer.BitWriterResize(1);
+ writer.BitWriterResize(4096);
+
+ Assert.Same(buffer, writer.Buffer);
+ Assert.Equal(4096, writer.Buffer.Length);
+ }
+
+ [Fact]
+ public void BitWriterResize_GrowsBufferGeometrically()
+ {
+ const int targetBytes = 256 * 1024;
+ using Vp8Encoder encoder = CreateEncoder();
+ Vp8BitWriter writer = new(1024, encoder);
+ Random random = new(42);
+ byte[] buffer = writer.Buffer;
+ int reallocations = 0;
+
+ // Emit pseudo-random modes until the partition holds at least targetBytes, counting how often
+ // the backing buffer is replaced.
+ while (writer.NumBytes < targetBytes)
+ {
+ writer.PutUvMode(random.Next(4));
+ if (!ReferenceEquals(buffer, writer.Buffer))
+ {
+ reallocations++;
+ buffer = writer.Buffer;
+ }
+ }
+
+ writer.Finish();
+
+ Assert.True(writer.Buffer.Length >= writer.NumBytes);
+
+ // Growing by 1.5x (rounded up to the next KiB) from 1 KiB needs 12 reallocations to hold 256 KiB.
+ // Growing in 1 KiB steps (one reallocation per KiB written) would need 256.
+ Assert.InRange(reallocations, 1, 16);
+ }
+
+ private static Vp8Encoder CreateEncoder()
+ => new(
+ Configuration.Default.MemoryAllocator,
+ Configuration.Default,
+ 16,
+ 16,
+ 75,
+ false,
+ WebpEncodingMethod.Default,
+ 1,
+ 60,
+ 50,
+ false);
+}