From 52ad9c67f617378432f1d457d352f518715ed82a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 16 Jul 2022 03:18:14 +0300 Subject: [PATCH 1/6] Jpeg colorspace deduction fix --- .../Decoder/SpectralConverter{TPixel}.cs | 2 +- .../Formats/Jpeg/JpegDecoderCore.cs | 52 +------------------ 2 files changed, 3 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 532892e060..2c3dccdee6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -95,7 +95,7 @@ public Buffer2D GetPixelBuffer(CancellationToken cancellationToken) } } - var buffer = this.pixelBuffer; + Buffer2D buffer = this.pixelBuffer; this.pixelBuffer = null; return buffer; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 58c85bd34e..94439f35a9 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -90,11 +90,6 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals /// private JFifMarker jFif; - /// - /// Whether the image has a JFIF marker. This is needed to determine, if the colorspace is YCbCr. - /// - private bool hasJFif; - /// /// Contains information about the Adobe marker. /// @@ -516,8 +511,6 @@ private JpegColorSpace DeduceJpegColorSpace(byte componentCount) if (componentCount == 3) { - // We prioritize adobe marker over jfif marker, if somebody really encoded this image with redundant adobe marker, - // then it's most likely an adobe jfif image. if (!this.adobe.Equals(default)) { if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYCbCr) @@ -529,51 +522,14 @@ private JpegColorSpace DeduceJpegColorSpace(byte componentCount) { return JpegColorSpace.RGB; } - - // Fallback to the id color deduction: If these values are 1-3 for a 3-channel image, then the image is assumed to be YCbCr. - if (this.Components[2].Id == 3 && this.Components[1].Id == 2 && this.Components[0].Id == 1) - { - return JpegColorSpace.YCbCr; - } - - JpegThrowHelper.ThrowNotSupportedColorSpace(); - } - - if (this.hasJFif) - { - // JFIF implies YCbCr. - return JpegColorSpace.YCbCr; } - // Fallback to the id color deduction. - // If the component Id's are R, G, B in ASCII the colorspace is RGB and not YCbCr. - // See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#color - if (this.Components[2].Id == 66 && this.Components[1].Id == 71 && this.Components[0].Id == 82) - { - return JpegColorSpace.RGB; - } - - // If these values are 1-3 for a 3-channel image, then the image is assumed to be YCbCr. - if (this.Components[2].Id == 3 && this.Components[1].Id == 2 && this.Components[0].Id == 1) - { - return JpegColorSpace.YCbCr; - } - - // 3-channel non-subsampled images are assumed to be RGB. - if (this.Components[2].VerticalSamplingFactor == 1 && this.Components[1].VerticalSamplingFactor == 1 && this.Components[0].VerticalSamplingFactor == 1 && - this.Components[2].HorizontalSamplingFactor == 1 && this.Components[1].HorizontalSamplingFactor == 1 && this.Components[0].HorizontalSamplingFactor == 1) - { - return JpegColorSpace.RGB; - } - - // Some images are poorly encoded and contain incorrect colorspace transform metadata. - // We ignore that and always fall back to the default colorspace. + // Fallback to YCbCr return JpegColorSpace.YCbCr; } if (componentCount == 4) { - // jfif images doesn't not support 4 component images, so we only check adobe. if (!this.adobe.Equals(default)) { if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck) @@ -585,11 +541,9 @@ private JpegColorSpace DeduceJpegColorSpace(byte componentCount) { return JpegColorSpace.Cmyk; } - - JpegThrowHelper.ThrowNotSupportedColorSpace(); } - // Fallback to cmyk as neither of cmyk nor ycck have 'special' component ids. + // Fallback to CMYK return JpegColorSpace.Cmyk; } @@ -757,8 +711,6 @@ private void ExtendProfile(ref byte[] profile, byte[] extension) /// The remaining bytes in the segment block. private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining) { - this.hasJFif = true; - // We can only decode JFif identifiers. // Some images contain multiple JFIF markers (Issue 1932) so we check to see // if it's already been read. From 0cc3c683ccc2a5250ea71827e752db89f16d9da7 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 16 Jul 2022 04:15:26 +0300 Subject: [PATCH 2/6] Tests --- .../Jpeg/Components/Decoder/AdobeMarker.cs | 2 +- .../Formats/Jpeg/JpegDecoderCore.cs | 69 +++++++++++-------- .../Formats/Jpg/JpegDecoderTests.Internal.cs | 69 +++++++++++++++++++ 3 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs index b41d52aa40..63a295d430 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 94439f35a9..5104f606d4 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -85,6 +85,11 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals /// 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 @@ public void Dispose() /// 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 @@ private void ProcessApp14Marker(BufferedReadStream stream, int remaining) 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 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, 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 0000000000..71951cfef4 --- /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)); + } +} From 1959bf6531963444897b2f5c6c3b73b8945e3f8b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 16 Jul 2022 14:19:23 +0200 Subject: [PATCH 3/6] Always use RgbJpegSpectralConverter for decompressed jpeg data --- .../Tiff/Compression/Decompressors/JpegTiffCompression.cs | 5 +++-- .../Compression/Decompressors/RgbJpegSpectralConverter.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index 3b2c765b38..3c0ebbb6fd 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -73,8 +73,9 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, int case TiffPhotometricInterpretation.YCbCr: case TiffPhotometricInterpretation.Rgb: { - using SpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ? - new RgbJpegSpectralConverter(this.configuration) : new SpectralConverter(this.configuration); + // The jpeg data should treated as RGB color space. If the PhotometricInterpretation is YCbCr, + // the conversion to RGB will be handled in the next step by the YCbCr color decoder. + using SpectralConverter spectralConverter = new RgbJpegSpectralConverter(this.configuration); var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); jpegDecoder.LoadTables(this.jpegTables, scanDecoder); jpegDecoder.ParseStream(stream, spectralConverter, CancellationToken.None); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs index a83518064d..fa7d0ffc9f 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -8,8 +8,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { /// - /// Spectral converter for YCbCr TIFF's which use the JPEG compression. - /// The jpeg data should be always treated as RGB color space. + /// Spectral converter for TIFF's which use the JPEG compression. + /// The compressed jpeg data should be always treated as RGB color space. + /// If PhotometricInterpretation indicates the data is YCbCr, the color decoder will handle the conversion. /// /// The type of the pixel. internal sealed class RgbJpegSpectralConverter : SpectralConverter From 1c9777e628f448509aa16a631609f845e24e9c8e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 16 Jul 2022 17:59:47 +0300 Subject: [PATCH 4/6] Tiif decoding fix --- .../Formats/Jpeg/JpegDecoderCore.cs | 15 ++++-- .../Decompressors/JpegTiffCompression.cs | 8 +-- .../Decompressors/RgbJpegSpectralConverter.cs | 32 ------------ .../TiffJpegSpectralConverter{TPixel}.cs | 50 +++++++++++++++++++ .../Formats/Jpg/JpegDecoderTests.Internal.cs | 3 +- 5 files changed, 68 insertions(+), 40 deletions(-) delete mode 100644 src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs create mode 100644 src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 5104f606d4..f75525ab98 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -503,9 +503,9 @@ public void Dispose() } /// - /// Returns the correct colorspace based on the image component count and the jpeg frame component id's. + /// Returns encoded colorspace based on the adobe APP14 marker. /// - /// The number of components. + /// Number of components. /// Parsed adobe APP14 marker. /// The internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount, ref AdobeMarker adobeMarker) @@ -534,6 +534,15 @@ internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount, ref Ado return default; } + /// + /// Returns encoded colorspace based on the component count and component ids. + /// + /// + /// Must take into account atleast RGB component identifiers i.e. [82, 71, 66] + /// as TIFF images with jpeg encoding don't have APP14 marker. + /// + /// Number of components. + /// The internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount) { if (componentCount == 1) @@ -1216,7 +1225,7 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, int maxH = 0; int maxV = 0; int index = 0; - for (int i = 0; i < componentCount; i++) + for (int i = 0; i < this.Frame.Components.Length; i++) { // 1 byte: component identifier byte componentId = this.temp[index]; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index 3c0ebbb6fd..e3df4b565b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -59,7 +59,8 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, int case TiffPhotometricInterpretation.BlackIsZero: case TiffPhotometricInterpretation.WhiteIsZero: { - using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(this.configuration); + using SpectralConverter spectralConverterGray = + new GrayJpegSpectralConverter(this.configuration); var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, CancellationToken.None); jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray); jpegDecoder.ParseStream(stream, spectralConverterGray, CancellationToken.None); @@ -73,9 +74,8 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, int case TiffPhotometricInterpretation.YCbCr: case TiffPhotometricInterpretation.Rgb: { - // The jpeg data should treated as RGB color space. If the PhotometricInterpretation is YCbCr, - // the conversion to RGB will be handled in the next step by the YCbCr color decoder. - using SpectralConverter spectralConverter = new RgbJpegSpectralConverter(this.configuration); + using SpectralConverter spectralConverter = + new TiffJpegSpectralConverter(this.configuration, this.photometricInterpretation); var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); jpegDecoder.LoadTables(this.jpegTables, scanDecoder); jpegDecoder.ParseStream(stream, spectralConverter, CancellationToken.None); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs deleted file mode 100644 index fa7d0ffc9f..0000000000 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors -{ - /// - /// Spectral converter for TIFF's which use the JPEG compression. - /// The compressed jpeg data should be always treated as RGB color space. - /// If PhotometricInterpretation indicates the data is YCbCr, the color decoder will handle the conversion. - /// - /// The type of the pixel. - internal sealed class RgbJpegSpectralConverter : SpectralConverter - where TPixel : unmanaged, IPixel - { - /// - /// Initializes a new instance of the class. - /// This Spectral converter will always convert the pixel data to RGB color. - /// - /// The configuration. - public RgbJpegSpectralConverter(Configuration configuration) - : base(configuration) - { - } - - /// - protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, frame.Precision); - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs new file mode 100644 index 0000000000..ea550b1261 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +{ + /// + /// Spectral converter for YCbCr TIFF's which use the JPEG compression. + /// The jpeg data should be always treated as RGB color space. + /// + /// The type of the pixel. + internal sealed class TiffJpegSpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel + { + private readonly TiffPhotometricInterpretation photometricInterpretation; + + /// + /// Initializes a new instance of the class. + /// This Spectral converter will always convert the pixel data to RGB color. + /// + /// The configuration. + /// Tiff photometric interpretation. + public TiffJpegSpectralConverter(Configuration configuration, TiffPhotometricInterpretation photometricInterpretation) + : base(configuration) + => this.photometricInterpretation = photometricInterpretation; + + /// + protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) + { + JpegColorSpace colorSpace = GetJpegColorSpaceFromPhotometricInterpretation(this.photometricInterpretation); + return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision); + } + + /// + /// This converter must be used only for RGB and YCbCr color spaces for performance reasons. + /// For grayscale images must be used. + /// + private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation) + => interpretation switch + { + TiffPhotometricInterpretation.Rgb => JpegColorSpace.RGB, + TiffPhotometricInterpretation.YCbCr => JpegColorSpace.RGB, + _ => throw new InvalidImageContentException($"Invalid tiff photometric interpretation for jpeg encoding: {interpretation}"), + }; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs index 71951cfef4..e4e59cdd9f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs @@ -33,8 +33,8 @@ internal void DeduceJpegColorSpaceAdobeMarker_ShouldReturnValidColorSpace(byte c { 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); @@ -46,6 +46,7 @@ internal void DeduceJpegColorSpaceAdobeMarker_ShouldReturnValidColorSpace(byte c public void DeduceJpegColorSpaceAdobeMarker_ShouldThrowOnUnsupportedComponentCount(byte componentCount) { AdobeMarker adobeMarker = default; + Assert.Throws(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker)); } From 95273d35b65838eaec6e2dcff7f21d6dbd58294d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 16 Jul 2022 18:37:06 +0300 Subject: [PATCH 5/6] Added grayscale app14 support, build fix --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 5 +++++ .../Decompressors/TiffJpegSpectralConverter{TPixel}.cs | 2 +- .../Formats/Jpg/JpegDecoderTests.Internal.cs | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 69770dfe4c..48d397a54d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -510,6 +510,11 @@ public void Dispose() /// The internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount, ref AdobeMarker adobeMarker) { + if (componentCount == 1) + { + return JpegColorSpace.Grayscale; + } + if (componentCount == 3) { if (adobeMarker.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs index ea550b1261..ced6b9027c 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs index e4e59cdd9f..6bf7ae88fa 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs @@ -25,6 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public partial class JpegDecoderTests { [Theory] + [InlineData(1, 0, JpegColorSpace.Grayscale)] [InlineData(3, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.RGB)] [InlineData(3, JpegConstants.Adobe.ColorTransformYCbCr, JpegColorSpace.YCbCr)] [InlineData(4, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.Cmyk)] From 8ca517b58670d261df2931f2067850134613a148 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 16 Jul 2022 21:43:36 +0300 Subject: [PATCH 6/6] Docs fix --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 48d397a54d..91606e9efb 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -540,12 +540,8 @@ internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount, ref Ado } /// - /// Returns encoded colorspace based on the component count and component ids. + /// Returns encoded colorspace based on the component count. /// - /// - /// Must take into account atleast RGB component identifiers i.e. [82, 71, 66] - /// as TIFF images with jpeg encoding don't have APP14 marker. - /// /// Number of components. /// The internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount)