Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public sealed partial class BrotliCompressionOptions
{
public BrotliCompressionOptions() { }
public int Quality { get { throw null; } set { } }
public int WindowSize { get { throw null; } set { } }
}
public partial struct BrotliDecoder : System.IDisposable
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace System.IO.Compression
public sealed class BrotliCompressionOptions
{
private int _quality = BrotliUtils.Quality_Default;
private int _windowSize = BrotliUtils.WindowBits_Default;

/// <summary>
/// Gets or sets the compression quality for a Brotli compression stream.
Expand All @@ -28,5 +29,24 @@ public int Quality
_quality = value;
}
}

/// <summary>
/// Gets or sets the window size for a Brotli compression stream.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException" accessor="set">The value is less than 10 or greater than 24.</exception>
/// <remarks>
/// The window size is the sliding window size in bits used by the LZ77 algorithm. Larger window sizes can improve compression ratio but use more memory. Range is from 10 to 24. The default value is 22.
/// </remarks>
public int WindowSize
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bartonjs I just realized, the Zstandard proposal named similar property Window, and the existing ctor parameter on BrotliEncoder is also called window. I think we should make sure the names are consistent.

Unfortunately, we are already somewhat inconsistent because DeflateStream parameters are called windowBits, but we'd better not add a third name for the same thing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like Window is the more naturally aligned name, so it's probably fine to go ahead and use it. Send a blurb to FXDC to see if anyone wants to object.

{
get => _windowSize;
set
{
ArgumentOutOfRangeException.ThrowIfLessThan(value, BrotliUtils.WindowBits_Min, nameof(value));
ArgumentOutOfRangeException.ThrowIfGreaterThan(value, BrotliUtils.WindowBits_Max, nameof(value));

_windowSize = value;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public BrotliStream(Stream stream, BrotliCompressionOptions compressionOptions,
ArgumentNullException.ThrowIfNull(compressionOptions);

_encoder.SetQuality(compressionOptions.Quality);
_encoder.SetWindow(compressionOptions.WindowSize);
}

/// <summary>Writes compressed bytes to the underlying stream from the specified byte array.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,52 @@ public void InvalidBrotliCompressionQuality()
Assert.Throws<ArgumentOutOfRangeException>("value", () => options.Quality = 12);
}

[Fact]
public void InvalidBrotliCompressionWindowSize()
{
BrotliCompressionOptions options = new();

Assert.Equal(22, options.WindowSize); // default value
Assert.Throws<ArgumentOutOfRangeException>("value", () => options.WindowSize = -1);
Assert.Throws<ArgumentOutOfRangeException>("value", () => options.WindowSize = 9);
Assert.Throws<ArgumentOutOfRangeException>("value", () => options.WindowSize = 25);
}

[Theory]
[InlineData(10)]
[InlineData(15)]
[InlineData(22)]
[InlineData(24)]
public void BrotliCompressionWindowSize_RoundTrip(int windowSize)
{
byte[] testData = new byte[10000];
Random.Shared.NextBytes(testData);

// Compress with specific window size
byte[] compressed;
using (var ms = new MemoryStream())
{
using (var compressor = new BrotliStream(ms, new BrotliCompressionOptions() { WindowSize = windowSize }, leaveOpen: true))
{
compressor.Write(testData);
}
compressed = ms.ToArray();
}

// Decompress and verify
byte[] decompressed;
using (var ms = new MemoryStream(compressed))
using (var decompressor = new BrotliStream(ms, CompressionMode.Decompress))
using (var resultStream = new MemoryStream())
{
decompressor.CopyTo(resultStream);
decompressed = resultStream.ToArray();
}

Assert.Equal(testData.Length, decompressed.Length);
Assert.Equal<byte>(testData, decompressed);
}

[Theory]
[MemberData(nameof(UncompressedTestFilesBrotli))]
public async Task BrotliCompressionQuality_SizeInOrder(string testFile)
Expand Down