From 1c9777e628f448509aa16a631609f845e24e9c8e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 16 Jul 2022 17:59:47 +0300 Subject: [PATCH] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void DeduceJpegColorSpaceAdobeMarker_ShouldThrowOnUnsupportedComponentCount(byte componentCount) { AdobeMarker adobeMarker = default; + Assert.Throws(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker)); }