diff --git a/src/ImageSharp/Formats/Jxl/Fields/JxlBitsCoder.cs b/src/ImageSharp/Formats/Jxl/Fields/JxlBitsCoder.cs index 3e4a2b6676..787d16d088 100644 --- a/src/ImageSharp/Formats/Jxl/Fields/JxlBitsCoder.cs +++ b/src/ImageSharp/Formats/Jxl/Fields/JxlBitsCoder.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; +using SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder; namespace SixLabors.ImageSharp.Formats.Jxl.Fields; diff --git a/src/ImageSharp/Formats/Jxl/Fields/JxlBundle.cs b/src/ImageSharp/Formats/Jxl/Fields/JxlBundle.cs index 94e5aa627c..866e00f317 100644 --- a/src/ImageSharp/Formats/Jxl/Fields/JxlBundle.cs +++ b/src/ImageSharp/Formats/Jxl/Fields/JxlBundle.cs @@ -5,6 +5,7 @@ using SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader; using SixLabors.ImageSharp.Formats.Jxl.IO.Metadata; using SixLabors.ImageSharp.Formats.Jxl.Processing; using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; +using SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder; using SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.AuxiliaryOutput; using SixLabors.ImageSharp.Formats.Jxl.Processing.Quantization; @@ -118,7 +119,7 @@ internal static class JxlBundle return false; } - return writer.WithMaxBits(totalBits, layer, auxOutput, () => + return writer.WithMaxBits(totalBits, () => { JxlWriteVisitor visitor = new(extensionBits, writer); diff --git a/src/ImageSharp/Formats/Jxl/Fields/JxlCanEncodeVisitor.cs b/src/ImageSharp/Formats/Jxl/Fields/JxlCanEncodeVisitor.cs index aae11aa120..87192bfefc 100644 --- a/src/ImageSharp/Formats/Jxl/Fields/JxlCanEncodeVisitor.cs +++ b/src/ImageSharp/Formats/Jxl/Fields/JxlCanEncodeVisitor.cs @@ -102,7 +102,7 @@ internal sealed class JxlCanEncodeVisitor : JxlVisitorBase extensionBits = (int)this.encodedBits - (int)this.posAfterExt; int encodedBits = 0; - this.OK &= JxlU64Coder.CanEncode(extensionBits, ref encodedBits); + this.OK &= JxlU64Coder.CanEncode((ulong)extensionBits, ref encodedBits); totalBits += encodedBits; for (int i = 1; i < BitOperations.PopCount(this.extensions); i++) diff --git a/src/ImageSharp/Formats/Jxl/Fields/JxlF16Coder.cs b/src/ImageSharp/Formats/Jxl/Fields/JxlF16Coder.cs index 3da369a18e..d5b0d30fd2 100644 --- a/src/ImageSharp/Formats/Jxl/Fields/JxlF16Coder.cs +++ b/src/ImageSharp/Formats/Jxl/Fields/JxlF16Coder.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; +using SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder; namespace SixLabors.ImageSharp.Formats.Jxl.Fields; diff --git a/src/ImageSharp/Formats/Jxl/Fields/JxlU32Coder.cs b/src/ImageSharp/Formats/Jxl/Fields/JxlU32Coder.cs index 660b403887..3fa3a3ab79 100644 --- a/src/ImageSharp/Formats/Jxl/Fields/JxlU32Coder.cs +++ b/src/ImageSharp/Formats/Jxl/Fields/JxlU32Coder.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Jxl.Processing; using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; +using SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder; namespace SixLabors.ImageSharp.Formats.Jxl.Fields; diff --git a/src/ImageSharp/Formats/Jxl/Fields/JxlU64Coder.cs b/src/ImageSharp/Formats/Jxl/Fields/JxlU64Coder.cs index 8726c9a27b..3336ecf0f2 100644 --- a/src/ImageSharp/Formats/Jxl/Fields/JxlU64Coder.cs +++ b/src/ImageSharp/Formats/Jxl/Fields/JxlU64Coder.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder; +using SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder; namespace SixLabors.ImageSharp.Formats.Jxl.Fields; diff --git a/src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlBitWriter.cs b/src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlBitWriter.cs new file mode 100644 index 0000000000..e497da0a34 --- /dev/null +++ b/src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlBitWriter.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder; + +internal sealed class JxlBitWriter(Stream stream) +{ + private const int BitsPerByte = 8; + + private bool isLimited; + private ulong bitsLimit; + + private byte currentByte; + private int bitsInCurrentByte; + + public long BitsWritten { get; private set; } + + public void Write(int nBits, int bits) => this.Write(nBits, (ulong)bits); + + public void Write(int nBits, ulong bits) + { + DebugGuard.MustBeGreaterThanOrEqualTo(nBits, 0, nameof(nBits)); + DebugGuard.MustBeLessThanOrEqualTo(nBits, 64, nameof(nBits)); + + if (this.isLimited) + { + if ((ulong)nBits > this.bitsLimit) + { + throw new InvalidOperationException("Too many bits were written"); + } + + this.bitsLimit -= (ulong)nBits; + } + + while (nBits > 0) + { + int bitsAvailable = BitsPerByte - this.bitsInCurrentByte; + int count = Math.Min(nBits, bitsAvailable); + + ulong mask = count == 64 + ? ulong.MaxValue + : (1UL << count) - 1; + + this.currentByte |= (byte)((bits & mask) << this.bitsInCurrentByte); + + bits >>= count; + nBits -= count; + this.bitsInCurrentByte += count; + this.BitsWritten += count; + + if (this.bitsInCurrentByte == BitsPerByte) + { + stream.WriteByte(this.currentByte); + this.currentByte = 0; + this.bitsInCurrentByte = 0; + } + } + } + + public bool WithMaxBits(ulong maxBits, Func func) + { + bool previousIsLimited = this.isLimited; + ulong previousLimit = this.bitsLimit; + + this.isLimited = true; + this.bitsLimit = maxBits; + + try + { + return func(); + } + finally + { + this.isLimited = previousIsLimited; + this.bitsLimit = previousLimit; + } + } + + public void Flush() + { + if (this.bitsInCurrentByte != 0) + { + stream.WriteByte(this.currentByte); + this.currentByte = 0; + this.bitsInCurrentByte = 0; + } + } +}