From 5de2701e1c1061be684d95bf4044899539b135cb Mon Sep 17 00:00:00 2001
From: winscripter <142818255+winscripter@users.noreply.github.com>
Date: Wed, 2 Sep 2026 22:23:34 +0400
Subject: [PATCH] Add JxlToJpegDecoder static methods
---
.../Jxl/Processing/Decoder/JxlDecoderCore.cs | 13 +-
.../Processing/Decoder/JxlToJpegDecoder.cs | 150 ++++++++++++++++++
2 files changed, 154 insertions(+), 9 deletions(-)
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlToJpegDecoder.cs
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs
index 4606b6d338..9fefcde018 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlDecoderCore.cs
@@ -363,11 +363,6 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
///
private readonly JxlBoxContentDecoder? boxContentDecoder;
- ///
- /// Decodes JPEG XL to JPEG.
- ///
- private JxlToJpegDecoder? jpegDecoder;
-
private readonly JxlBoxContentDecoder? metadataDecoder;
///
@@ -2246,12 +2241,12 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
if (this.reconstructionExifSize > 0)
{
- JxlToJpegDecoder.SetExif(this.exifMetadata!.Memory, jpegData);
+ JxlToJpegDecoder.TrySetExif(this.exifMetadata!.Memory.Span, jpegData);
}
if (this.reconstructionXmpSize > 0)
{
- JxlToJpegDecoder.SetXmp(this.xmpMetadata!.Memory, jpegData);
+ JxlToJpegDecoder.TrySetXmp(this.xmpMetadata!.Memory.Span, jpegData);
}
this.reconstructionOutputJpeg = JpegReconstructionStage.Output;
@@ -2592,7 +2587,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
throw new InvalidOperationException("Only one EXIF marker for JPEG reconstruction can be present");
}
- if (JxlToJpegDecoder.ExifBoxContentSize(jpegData, ref this.reconstructionExifSize) != Success)
+ if (!JxlToJpegDecoder.ExifBoxContentSize(jpegData, ref this.reconstructionExifSize))
{
throw new InvalidOperationException("Invalid jbrd EXIF size");
}
@@ -2605,7 +2600,7 @@ internal sealed class JxlDecoderCore : ImageDecoderCore, IDisposable
throw new InvalidOperationException("Only one XMP marker for JPEG reconstruction can be present");
}
- if (JxlToJpegDecoder.XmlBoxContentSize(jpegData, ref this.reconstructionXmpSize) != Success)
+ if (!JxlToJpegDecoder.XmpBoxContentSize(jpegData, ref this.reconstructionXmpSize))
{
throw new InvalidOperationException("Invalid jbrd XMP size");
}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlToJpegDecoder.cs b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlToJpegDecoder.cs
new file mode 100644
index 0000000000..995b74852a
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/Decoder/JxlToJpegDecoder.cs
@@ -0,0 +1,150 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Formats.Jxl.IO.Jpeg.Data;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder;
+
+internal class JxlToJpegDecoder
+{
+ ///
+ /// Returns the number of EXIF markers in the JPEG file.
+ ///
+ /// Input parsed JPEG data
+ /// Number of EXIFs in the JPEG
+ public static int NumExifMarkers(JpegData jpegData) => jpegData.AppMarkerTypes.Count(x => x == JpegAppMarkerType.Exif);
+
+ ///
+ /// Returns the number of XMP markers in the JPEG file.
+ ///
+ /// Input parsed JPEG data
+ /// Number of XMPs in the JPEG
+ public static int NumXmpMarkers(JpegData jpegData) => jpegData.AppMarkerTypes.Count(x => x == JpegAppMarkerType.Xmp);
+
+ ///
+ /// Attempts to set EXIF data in the JPEG file.
+ ///
+ /// EXIF data
+ /// JPEG file for EXIF data
+ /// If EXIF data was set, true; returns false if no EXIF marker is present, or is present but not enough data
+ public static bool TrySetExif(Span data, JpegData jpegData)
+ {
+ int size = data.Length;
+ ReadOnlySpan exifTag = JpegDataConstants.ExifTag;
+ int exifTagSize = exifTag.Length;
+
+ for (int i = 0; i < jpegData.AppData.Count; ++i)
+ {
+ if (jpegData.AppMarkerTypes[i] == JpegAppMarkerType.Exif)
+ {
+ Span dataSpan = CollectionsMarshal.AsSpan(jpegData.AppData[i]);
+
+ if (dataSpan.Length != size + 3 + exifTagSize - 4)
+ {
+ return false;
+ }
+
+ // The first 9 bytes are used for JPEG marker header.
+ dataSpan[0] = 0xE1;
+
+ // The second and third byte are already filled in correctly
+ exifTag.CopyTo(dataSpan[3..]);
+
+ // The first 4 bytes are the TIFF header from the box contents, and are
+ // not included in the JPEG
+ data[4..].CopyTo(dataSpan[(3 + exifTagSize)..]);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Attempts to set XMP data in the JPEG file.
+ ///
+ /// XMP data
+ /// JPEG file for XMP data
+ /// If XMP data was set, true; returns false if no XMP marker is present, or is present but not enough data
+ public static bool TrySetXmp(Span data, JpegData jpegData)
+ {
+ int size = data.Length;
+ ReadOnlySpan xmpTag = JpegDataConstants.XmpTag;
+ int xmpTagSize = xmpTag.Length;
+
+ for (int i = 0; i < jpegData.AppData.Count; ++i)
+ {
+ if (jpegData.AppMarkerTypes[i] == JpegAppMarkerType.Xmp)
+ {
+ Span dataSpan = CollectionsMarshal.AsSpan(jpegData.AppData[i]);
+
+ if (dataSpan.Length != size + 3 + xmpTagSize)
+ {
+ return false;
+ }
+
+ // The first 9 bytes are used for JPEG marker header.
+ dataSpan[0] = 0xE1;
+
+ // The second and third byte are already filled in correctly
+ xmpTag.CopyTo(dataSpan[3..]);
+
+ data.CopyTo(dataSpan[(3 + xmpTagSize)..]);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool ExifBoxContentSize(JpegData jpegData, ref long size)
+ {
+ size = 0;
+ int exifTagLength = JpegDataConstants.ExifTag.Length;
+
+ for (int i = 0; i < jpegData.AppData.Count; ++i)
+ {
+ if (jpegData.AppMarkerTypes[i] == JpegAppMarkerType.Exif)
+ {
+ if (jpegData.AppData[i].Count < 3 + exifTagLength)
+ {
+ // too small for app marker header
+ return false;
+ }
+
+ // The first 4 bytes are the TIFF header from the box contents, and are
+ // not included in the JPEG
+ size = jpegData.AppData[i].Count + 4 - 3 - exifTagLength;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool XmpBoxContentSize(JpegData jpegData, ref long size)
+ {
+ size = 0;
+ int xmpTagLength = JpegDataConstants.XmpTag.Length;
+
+ for (int i = 0; i < jpegData.AppData.Count; ++i)
+ {
+ if (jpegData.AppMarkerTypes[i] == JpegAppMarkerType.Xmp)
+ {
+ if (jpegData.AppData[i].Count < 3 + xmpTagLength)
+ {
+ // too small for app marker header
+ return false;
+ }
+
+ size = jpegData.AppData[i].Count - 3 - xmpTagLength;
+ return true;
+ }
+ }
+
+ return false;
+ }
+}