diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs index b41d52aa4..63a295d43 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 94439f35a..5104f606d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -85,6 +85,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private byte[] xmpData; + /// + /// Whether the image has a APP14 adobe marker. This is needed to determine image encoded colorspace. + /// + private bool hasAdobeMarker; + /// /// Contains information about the JFIF marker. /// @@ -501,49 +506,48 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Returns the correct colorspace based on the image component count and the jpeg frame component id's. /// /// The number of components. + /// Parsed adobe APP14 marker. /// The - private JpegColorSpace DeduceJpegColorSpace(byte componentCount) + internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount, ref AdobeMarker adobeMarker) { - if (componentCount == 1) - { - return JpegColorSpace.Grayscale; - } - if (componentCount == 3) { - if (!this.adobe.Equals(default)) + if (adobeMarker.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) { - if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYCbCr) - { - return JpegColorSpace.YCbCr; - } - - if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) - { - return JpegColorSpace.RGB; - } + return JpegColorSpace.RGB; } - // Fallback to YCbCr return JpegColorSpace.YCbCr; } if (componentCount == 4) { - if (!this.adobe.Equals(default)) + if (adobeMarker.ColorTransform == JpegConstants.Adobe.ColorTransformYcck) { - if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck) - { - return JpegColorSpace.Ycck; - } - - if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) - { - return JpegColorSpace.Cmyk; - } + return JpegColorSpace.Ycck; } - // Fallback to CMYK + return JpegColorSpace.Cmyk; + } + + JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); + return default; + } + + internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount) + { + if (componentCount == 1) + { + return JpegColorSpace.Grayscale; + } + + if (componentCount == 3) + { + return JpegColorSpace.YCbCr; + } + + if (componentCount == 4) + { return JpegColorSpace.Cmyk; } @@ -1013,7 +1017,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg stream.Read(this.temp, 0, MarkerLength); remaining -= MarkerLength; - AdobeMarker.TryParse(this.temp, out this.adobe); + if (AdobeMarker.TryParse(this.temp, out this.adobe)) + { + this.hasAdobeMarker = true; + } if (remaining > 0) { @@ -1260,7 +1267,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg index += componentBytes; } - this.ColorSpace = this.DeduceJpegColorSpace(componentCount); + this.ColorSpace = this.hasAdobeMarker + ? DeduceJpegColorSpace(componentCount, ref this.adobe) + : DeduceJpegColorSpace(componentCount); this.Metadata.GetJpegMetadata().ColorType = this.DeduceJpegColorType(); if (!metadataOnly) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs new file mode 100644 index 000000000..71951cfef --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; +using Xunit; +using Xunit.Abstractions; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + [Trait("Format", "Jpg")] + public partial class JpegDecoderTests + { + [Theory] + [InlineData(3, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.RGB)] + [InlineData(3, JpegConstants.Adobe.ColorTransformYCbCr, JpegColorSpace.YCbCr)] + [InlineData(4, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.Cmyk)] + [InlineData(4, JpegConstants.Adobe.ColorTransformYcck, JpegColorSpace.Ycck)] + internal void DeduceJpegColorSpaceAdobeMarker_ShouldReturnValidColorSpace(byte componentCount, byte adobeFlag, JpegColorSpace expectedColorSpace) + { + byte[] adobeMarkerPayload = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, adobeFlag }; + ProfileResolver.AdobeMarker.CopyTo(adobeMarkerPayload); + + _ = AdobeMarker.TryParse(adobeMarkerPayload, out AdobeMarker adobeMarker); + JpegColorSpace actualColorSpace = JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker); + + Assert.Equal(expectedColorSpace, actualColorSpace); + } + + [Theory] + [InlineData(2)] + [InlineData(5)] + public void DeduceJpegColorSpaceAdobeMarker_ShouldThrowOnUnsupportedComponentCount(byte componentCount) + { + AdobeMarker adobeMarker = default; + Assert.Throws(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker)); + } + + [Theory] + [InlineData(1, JpegColorSpace.Grayscale)] + [InlineData(3, JpegColorSpace.YCbCr)] + [InlineData(4, JpegColorSpace.Cmyk)] + internal void DeduceJpegColorSpace_ShouldReturnValidColorSpace(byte componentCount, JpegColorSpace expectedColorSpace) + { + JpegColorSpace actualColorSpace = JpegDecoderCore.DeduceJpegColorSpace(componentCount); + + Assert.Equal(expectedColorSpace, actualColorSpace); + } + + [Theory] + [InlineData(2)] + [InlineData(5)] + public void DeduceJpegColorSpace_ShouldThrowOnUnsupportedComponentCount(byte componentCount) + => Assert.Throws(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount)); + } +}