diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlBoxContentDecoder.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlBoxContentDecoder.cs
new file mode 100644
index 000000000..7621102ef
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlBoxContentDecoder.cs
@@ -0,0 +1,108 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using System.IO.Compression;
+using SixLabors.ImageSharp.Formats.Jxl.IO;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder;
+
+///
+/// Allows decoding and decompressing box data.
+///
+internal sealed class JxlBoxContentDecoder
+{
+ ///
+ /// Specifies how many bytes to read to fetch box data. This is ignored
+ /// if the box extends till EOF.
+ ///
+ private ulong boxSize;
+
+ ///
+ /// When true the box size is ignored and is unbounded - that is, keeps going
+ /// till the end of the file or stream.
+ ///
+ private bool boxExtendsTillEnd;
+
+ ///
+ /// This contains flags that determine whether the box is Brotli-compressed or
+ /// not.
+ ///
+ private JxlBoxCodingMode codingMode;
+
+ ///
+ /// Prepares parsing the box.
+ ///
+ /// Specifies box compression.
+ /// Specifies whether or not the box size keeps going till the end of stream.
+ /// Specifies the fixed size of the box when it is not unbounded.
+ public void Initialize(JxlBoxCodingMode codingMode, bool isUnbounded, ulong size)
+ {
+ this.boxSize = size;
+ this.codingMode = codingMode;
+ this.boxExtendsTillEnd = isUnbounded;
+ }
+
+ ///
+ /// Prepares parsing the box.
+ ///
+ /// True if the box is compressed with Brotli. If uncompressed - false.
+ /// Specifies whether or not the box size keeps going till the end of stream.
+ /// Specifies the fixed size of the box when it is not unbounded.
+ public void Initialize(bool isBrotliCompressed, bool isUnbounded, ulong size)
+ => this.Initialize(
+ isBrotliCompressed ? JxlBoxCodingMode.Brotli : JxlBoxCodingMode.Uncompressed,
+ isUnbounded,
+ size);
+
+ public void Process(Stream stream, JxlMemoryWriter writer)
+ {
+ byte[] cache = ArrayPool.Shared.Rent(16384);
+
+ try
+ {
+ if (this.codingMode == JxlBoxCodingMode.Brotli)
+ {
+ using BrotliStream brotli = new(stream, CompressionMode.Decompress, leaveOpen: true);
+
+ int bytesRead;
+ while ((bytesRead = brotli.Read(cache, 0, cache.Length)) > 0)
+ {
+ writer.Write(cache.AsSpan(0, bytesRead));
+ }
+ }
+ else
+ {
+ if (this.boxExtendsTillEnd)
+ {
+ int bytesRead;
+ while ((bytesRead = stream.Read(cache, 0, cache.Length)) > 0)
+ {
+ writer.Write(cache.AsSpan(0, bytesRead));
+ }
+ }
+ else
+ {
+ ulong bytesLeft = this.boxSize;
+ while (bytesLeft > 0)
+ {
+ int toRead = (int)Math.Min((ulong)cache.Length, bytesLeft);
+ int bytesRead = stream.Read(cache, 0, toRead);
+
+ if (bytesRead == 0)
+ {
+ throw new EndOfStreamException("Unexpected EOF while reading box content");
+ }
+
+ writer.Write(cache.AsSpan(0, bytesRead));
+ bytesLeft -= (ulong)bytesRead;
+ }
+ }
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(cache);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs
index 016269e4d..7ca3ace0e 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs
@@ -1508,7 +1508,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
Span fileSignature = stackalloc byte[2];
stream.ReadExactly(fileSignature);
- if (fileSignature[0] != 0xFF || fileSignature[1] != CodestreamMarker)
+ if (fileSignature[0] != 0xFF || fileSignature[1] != JxlShared.CodestreamMarker)
{
throw new InvalidOperationException("The file signature is invalid");
}
@@ -1553,7 +1553,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
{
Span span = this.GetCodeStreamSpan();
- JxlBitReader reader = new(span);
+ JxlBitReader reader = new(this.stream);
reader.SkipBits64((ulong)this.codestreamBitsAhead);
this.metadata!.CustomTransformData!.NonserializedXybEncoded = this.metadata.ImageMetadata!.XybEncoded;
@@ -2286,7 +2286,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
{
if (this.decoderStage != JxlDecoderStage.CodeStreamFinished || this.JbrdNeedsMoreBoxes())
{
- return NeedMoreInput;
+ ThrowNotEnoughData();
}
if (this.inputClosed || (this.eventsWanted & Box) != 0)
@@ -2294,7 +2294,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
return Success;
}
- return NeedMoreInput;
+ ThrowNotEnoughData();
}
bool boxedCodestreamDone = ((this.eventsWanted & Box) != 0)
@@ -2556,7 +2556,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
if (!boxDone)
{
- return NeedMoreInput;
+ ThrowNotEnoughData();
}
this.boxStage = JxlBoxStage.Header;