From 2d12bc6b4aec1f3edb55634daa39e0299e27bb72 Mon Sep 17 00:00:00 2001
From: winscripter <142818255+winscripter@users.noreply.github.com>
Date: Tue, 18 Aug 2026 20:31:34 +0400
Subject: [PATCH] Zero-copy input access (prototype 1)
---
.../Formats/Jxl/IO/Container/JxlBoxHeader.cs | 13 ++-
.../Jxl/Processing/Decoder/JxlDecoderCore.cs | 102 +++++-------------
2 files changed, 34 insertions(+), 81 deletions(-)
diff --git a/src/ImageSharp/Formats/Jxl/IO/Container/JxlBoxHeader.cs b/src/ImageSharp/Formats/Jxl/IO/Container/JxlBoxHeader.cs
index d39f3c7e7..dc5eb1351 100644
--- a/src/ImageSharp/Formats/Jxl/IO/Container/JxlBoxHeader.cs
+++ b/src/ImageSharp/Formats/Jxl/IO/Container/JxlBoxHeader.cs
@@ -28,17 +28,24 @@ internal struct JxlBoxHeader
///
public bool SizeExtendsTillEnd;
+ ///
+ /// True if the size is 64-bit.
+ ///
+ public bool ContainsLargeSize;
+
///
/// Initializes a new instance of the struct.
///
/// The size of the box.
/// The type of the box.
/// Does the box size extend till the end of the file?
- public JxlBoxHeader(ulong size, uint type, bool sizeExtendsTillEnd)
+ /// Is there a 64-bit size field?
+ public JxlBoxHeader(ulong size, uint type, bool sizeExtendsTillEnd, bool containsLargeSize)
{
this.Size = size;
this.Type = type;
this.SizeExtendsTillEnd = sizeExtendsTillEnd;
+ this.ContainsLargeSize = containsLargeSize;
}
///
@@ -103,11 +110,11 @@ internal struct JxlBoxHeader
throw new InvalidOperationException("Large size cannot have another large size or extend till the end of the file");
}
- return new JxlBoxHeader(size, type, sizeExtendsTillEnd: false);
+ return new JxlBoxHeader(size, type, sizeExtendsTillEnd: false, containsLargeSize: true);
}
else
{
- return new JxlBoxHeader(size, type, sizeExtendsTillEnd: size == 0);
+ return new JxlBoxHeader(size, type, sizeExtendsTillEnd: size == 0, containsLargeSize: false);
}
}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs
index e984b36b9..be5f1d335 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs
@@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jxl.Fields;
using SixLabors.ImageSharp.Formats.Jxl.IO;
+using SixLabors.ImageSharp.Formats.Jxl.IO.Container;
using SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader;
using SixLabors.ImageSharp.Formats.Jxl.IO.Metadata;
using SixLabors.ImageSharp.IO;
@@ -1539,36 +1540,29 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
/// Status of the parsing.
/// Thrown if the data is incorrect.
/// Thrown if the data is malformed.
- public bool ReadBasicInfo()
+ public bool ReadBasicInfo(Stream stream)
{
if (!this.gotCodestreamSignature)
{
- Span span = this.GetCodeStreamSpan();
-
- if (span.Length < 2)
- {
- return this.TryRequestMoreInput();
- }
+ Span fileSignature = stackalloc byte[2];
+ stream.ReadExactly(fileSignature);
- if (span[0] != 0xFF || span[1] != CodestreamMarker)
+ if (fileSignature[0] != 0xFF || fileSignature[1] != CodestreamMarker)
{
throw new InvalidOperationException("The file signature is invalid");
}
this.gotCodestreamSignature = true;
- this.AdvanceCodeStream(2);
}
- Span sp = this.GetCodeStreamSpan();
-
- JxlBitReader bitReader = new(sp);
+ JxlBitReader bitReader = new(stream);
- if (!this.ReadBundle(sp, bitReader, this.metadata!.Size!))
+ if (!this.ReadBundle(stream, bitReader, this.metadata!.Size!))
{
throw new InvalidDataException("Could not parse the size header");
}
- if (!this.ReadBundle(sp, bitReader, this.metadata!.ImageMetadata!))
+ if (!this.ReadBundle(stream, bitReader, this.metadata!.ImageMetadata!))
{
throw new InvalidDataException("Could not parse the image metadata");
}
@@ -1924,8 +1918,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
if (this.skippingFrame)
{
- bool referenceable = this.frameHeader.CanBeReferenced
- || this.frameHeader.FrameType == JxlFrameType.DcFrame;
+ bool referenceable = this.frameHeader.CanBeReferenced || this.frameHeader.FrameType == JxlFrameType.DcFrame;
if (internalFrameIndex < this.frameRequired.Count && this.frameRequired[internalFrameIndex] == 0)
{
@@ -2182,63 +2175,18 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
/// Parses the start of a box.
///
/// Input bytes to parse from.
- /// Size of remaining input bytes.
- /// Offset of input bytes.
- /// File offset.
- /// Type of the parsed box.
+ /// Type of the box
/// Output box size.
/// Output header size.
- ///
- /// True if the parsing went fine. False if the parsing requests
- /// more input bytes.
- ///
///
/// Thrown when data is invalid.
///
- private static bool ParseBoxHeader(Span input, long size, long pos, long filePos, JxlBoxType type, out long boxSize, out long headerSize)
+ private static void ParseBoxHeader(Stream input, out JxlBoxType type, out long boxSize, out long headerSize)
{
- boxSize = 0;
- headerSize = 0;
-
- if (IsOutOfBounds((int)pos, 8, (int)size))
- {
- headerSize = 8;
- return false;
- }
-
- long boxStart = pos;
- boxSize = BinaryPrimitives.ReadInt32BigEndian(input[(int)pos..]);
- pos += 4;
- type = (JxlBoxType)BitConverter.ToInt32(input.Slice((int)pos, 4));
- pos += 4;
-
- if (boxSize == 1)
- {
- headerSize = 16;
-
- if (IsOutOfBounds((int)pos, 8, (int)size))
- {
- return false;
- }
-
- long boxSize64 = BinaryPrimitives.ReadInt64BigEndian(input[(int)pos..]);
- pos += 8;
- boxSize = boxSize64;
- }
-
- headerSize = pos - boxStart;
-
- if (boxSize > 0 && boxSize < headerSize)
- {
- throw new InvalidOperationException("Invalid box size");
- }
-
- if (filePos + boxSize < filePos)
- {
- throw new InvalidOperationException("Box size overflow");
- }
-
- return true;
+ JxlBoxHeader header = JxlBoxHeader.ReadHeader(input);
+ boxSize = (long)header.Size;
+ headerSize = (header.ContainsLargeSize ? 12 : 4) + 4;
+ type = (JxlBoxType)header.Type;
}
///
@@ -2246,14 +2194,14 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
///
/// Status of processing.
/// Thrown when data is invalid.
- public int ProcessBoxes()
+ public int ProcessBoxes(Stream stream)
{
// We have a box handling loop here.
while (true)
{
if (this.boxStage != JxlBoxStage.Header)
{
- this.AdvanceInput(this.headerSize);
+ // this.AdvanceInput(this.headerSize);
this.headerSize = 0;
if ((this.eventsWanted & Box) != 0 && this.boxEvent && !this.boxOutBufferSetCurrentBox)
@@ -2582,20 +2530,18 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
return NeedMoreInput;
}
- Span nextSpan = this.nextInput!.Memory.Span;
- if (!(nextSpan[0] == 'j' && nextSpan[1] == 'x' && nextSpan[2] == 'l' && nextSpan[3] == ' '))
+ if (BinaryUtils.ReadInt32BigEndian(stream) != 0x6A786C20) // Bytes "jxl " in Big Endian
{
throw new InvalidOperationException("File type box major brand must be \"jxl \"");
}
- uint version = BinaryPrimitives.ReadUInt32BigEndian(nextSpan[4..]);
+ uint version = BinaryUtils.ReadUInt32BigEndian(stream);
if (version > 1)
{
throw new InvalidOperationException("Unknown JXL file format version " + version + ", known versions are 0 and 1");
}
this.jxlFileFormatVersion = (int)version;
- this.AdvanceInput(8);
this.boxStage = JxlBoxStage.Skip;
}
else if (this.boxStage == JxlBoxStage.PartialCodeStream)
@@ -2615,7 +2561,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
throw new InvalidOperationException("jxlp box is too small to contain an index");
}
- uint jxlpIndex = BinaryPrimitives.ReadUInt32BigEndian(this.nextInput!.Memory.Span);
+ uint jxlpIndex = BinaryUtils.ReadUInt32BigEndian(stream);
uint counter = jxlpIndex & 0x7FFFFFFFu;
bool isLast = (jxlpIndex & 0x80000000u) != 0;
@@ -2624,8 +2570,6 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
throw new InvalidOperationException("jxlp box index " + counter + " is a duplicate (already processed)");
}
- this.AdvanceInput(4);
-
if (counter == this.nextJxlpIndex)
{
this.nextJxlpIndex++;
@@ -2720,9 +2664,11 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
return Error;
}
- entry!.CodestreamBytes.Write(this.nextInput!.Memory.Span[..(int)remaining]);
+ // Now we want to write the 'remaining' number of bytes
+ // from input into the codestream.
+ using IMemoryOwner buffer = this.Options.Configuration.MemoryAllocator.Allocate((int)remaining);
+ entry!.CodestreamBytes.Write(buffer.Memory.Span);
this.jxlpOooBufferTotal += remaining;
- this.AdvanceInput(remaining);
bool boxDone = !this.boxContentsUnbounded && this.filePosition >= this.boxContentsEnd;