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; + } +}