Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ internal class Vp8BitWriter : BitWriterBase

private uint pos;

private readonly int maxPos;

/// <summary>
/// Initializes a new instance of the <see cref="Vp8BitWriter"/> class.
/// </summary>
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
}

/// <inheritdoc/>
Expand Down
71 changes: 71 additions & 0 deletions tests/ImageSharp.Tests/Formats/WebP/Vp8BitWriterTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
Loading