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;