diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 28ef6a96f..b5a51c5a4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private readonly SpectralConverter spectralConverter; - private CancellationToken cancellationToken; + private readonly CancellationToken cancellationToken; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index e84d13ff1..46821d45d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; + namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// @@ -30,5 +32,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// Actual stride height depends on the subsampling factor of the given component. /// public abstract void ConvertStrideBaseline(); + + /// + /// Gets the color converter. + /// + /// The jpeg frame with the color space to convert to. + /// The raw JPEG data. + /// The color converter. + public abstract JpegColorConverter GetConverter(JpegFrame frame, IRawJpegData jpegData); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 50cfa0188..50fc46c1e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -11,12 +11,12 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { - internal sealed class SpectralConverter : SpectralConverter, IDisposable + internal class SpectralConverter : SpectralConverter, IDisposable where TPixel : unmanaged, IPixel { private readonly Configuration configuration; - private CancellationToken cancellationToken; + private readonly CancellationToken cancellationToken; private JpegComponentPostProcessor[] componentProcessors; @@ -59,6 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } + /// public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) { MemoryAllocator allocator = this.configuration.MemoryAllocator; @@ -85,9 +86,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.rgbaBuffer = allocator.Allocate(frame.PixelWidth); // color converter from Rgba32 to TPixel - this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); + this.colorConverter = this.GetConverter(frame, jpegData); } + /// public override void ConvertStrideBaseline() { // Convert next pixel stride using single spectral `stride' @@ -103,6 +105,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } + /// + public override JpegColorConverter GetConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); + public void Dispose() { if (this.componentProcessors != null) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index 2722c7db4..8abaf2008 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -23,6 +23,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors private readonly byte[] jpegTables; + private readonly TiffPhotometricInterpretation photometricInterpretation; + /// /// Initializes a new instance of the class. /// @@ -31,12 +33,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// The image width. /// The bits per pixel. /// The JPEG tables containing the quantization and/or Huffman tables. - /// The predictor. - public JpegTiffCompression(Configuration configuration, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, byte[] jpegTables, TiffPredictor predictor = TiffPredictor.None) - : base(memoryAllocator, width, bitsPerPixel, predictor) + /// The photometric interpretation. + public JpegTiffCompression( + Configuration configuration, + MemoryAllocator memoryAllocator, + int width, + int bitsPerPixel, + byte[] jpegTables, + TiffPhotometricInterpretation photometricInterpretation) + : base(memoryAllocator, width, bitsPerPixel) { this.configuration = configuration; this.jpegTables = jpegTables; + this.photometricInterpretation = photometricInterpretation; } /// @@ -47,8 +56,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder()); - // Should we pass through the CancellationToken from the tiff decoder? - using var spectralConverter = new SpectralConverter(this.configuration, CancellationToken.None); + // TODO: Should we pass through the CancellationToken from the tiff decoder? + // If the PhotometricInterpretation is YCbCr we explicitly assume the JPEG data is in RGB color space. + // There seems no other way to determine that the JPEG data is RGB colorspace (no APP14 marker, componentId's are not RGB). + using SpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ? + new RgbJpegSpectralConverter(this.configuration, CancellationToken.None) : new SpectralConverter(this.configuration, CancellationToken.None); var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); jpegDecoder.LoadTables(this.jpegTables, scanDecoder); scanDecoder.ResetInterval = 0; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs new file mode 100644 index 000000000..46ec53716 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Threading; +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 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 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. + /// The cancellation token. + public RgbJpegSpectralConverter(Configuration configuration, CancellationToken cancellationToken) + : base(configuration, cancellationToken) + { + } + + /// + public override JpegColorConverter GetConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(JpegColorSpace.RGB, frame.Precision); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index 04f38c6be..2ac00b412 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression case TiffDecoderCompressionType.Jpeg: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new JpegTiffCompression(configuration, allocator, width, bitsPerPixel, jpegTables); + return new JpegTiffCompression(configuration, allocator, width, bitsPerPixel, jpegTables, photometricInterpretation); default: throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 1785f3dec..57d02f643 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -7,6 +7,7 @@ using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -200,6 +201,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.spectralData = new LibJpegTools.SpectralData(spectralComponents); } + + public override JpegColorConverter GetConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 1a201dd09..d7333c0b5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -290,8 +290,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithjpegCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg); + public void TiffEncoder_EncodeRgb_WithJpegCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, useExactComparer: false, compareTolerance: 0.012f); [Theory] [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]