Skip to content

Commit 1b01984

Browse files
committed
For YCbCr Tiff's with Jpeg compression explicitly assume RGB color space
1 parent e092d86 commit 1b01984

File tree

8 files changed

+75
-12
lines changed

8 files changed

+75
-12
lines changed

src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ internal class HuffmanScanDecoder
6767

6868
private readonly SpectralConverter spectralConverter;
6969

70-
private CancellationToken cancellationToken;
70+
private readonly CancellationToken cancellationToken;
7171

7272
/// <summary>
7373
/// Initializes a new instance of the <see cref="HuffmanScanDecoder"/> class.

src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
5+
46
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
57
{
68
/// <summary>
@@ -30,5 +32,13 @@ internal abstract class SpectralConverter
3032
/// Actual stride height depends on the subsampling factor of the given component.
3133
/// </remarks>
3234
public abstract void ConvertStrideBaseline();
35+
36+
/// <summary>
37+
/// Gets the color converter.
38+
/// </summary>
39+
/// <param name="frame">The jpeg frame with the color space to convert to.</param>
40+
/// <param name="jpegData">The raw JPEG data.</param>
41+
/// <returns>The color converter.</returns>
42+
public abstract JpegColorConverter GetConverter(JpegFrame frame, IRawJpegData jpegData);
3343
}
3444
}

src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111

1212
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
1313
{
14-
internal sealed class SpectralConverter<TPixel> : SpectralConverter, IDisposable
14+
internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
1515
where TPixel : unmanaged, IPixel<TPixel>
1616
{
1717
private readonly Configuration configuration;
1818

19-
private CancellationToken cancellationToken;
19+
private readonly CancellationToken cancellationToken;
2020

2121
private JpegComponentPostProcessor[] componentProcessors;
2222

@@ -59,6 +59,7 @@ public Buffer2D<TPixel> PixelBuffer
5959
}
6060
}
6161

62+
/// <inheritdoc/>
6263
public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
6364
{
6465
MemoryAllocator allocator = this.configuration.MemoryAllocator;
@@ -85,9 +86,10 @@ public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
8586
this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth);
8687

8788
// color converter from Rgba32 to TPixel
88-
this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision);
89+
this.colorConverter = this.GetConverter(frame, jpegData);
8990
}
9091

92+
/// <inheritdoc/>
9193
public override void ConvertStrideBaseline()
9294
{
9395
// Convert next pixel stride using single spectral `stride'
@@ -103,6 +105,9 @@ public override void ConvertStrideBaseline()
103105
}
104106
}
105107

108+
/// <inheritdoc/>
109+
public override JpegColorConverter GetConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision);
110+
106111
public void Dispose()
107112
{
108113
if (this.componentProcessors != null)

src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ internal class JpegTiffCompression : TiffBaseDecompressor
2323

2424
private readonly byte[] jpegTables;
2525

26+
private readonly TiffPhotometricInterpretation photometricInterpretation;
27+
2628
/// <summary>
2729
/// Initializes a new instance of the <see cref="JpegTiffCompression"/> class.
2830
/// </summary>
@@ -31,12 +33,19 @@ internal class JpegTiffCompression : TiffBaseDecompressor
3133
/// <param name="width">The image width.</param>
3234
/// <param name="bitsPerPixel">The bits per pixel.</param>
3335
/// <param name="jpegTables">The JPEG tables containing the quantization and/or Huffman tables.</param>
34-
/// <param name="predictor">The predictor.</param>
35-
public JpegTiffCompression(Configuration configuration, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, byte[] jpegTables, TiffPredictor predictor = TiffPredictor.None)
36-
: base(memoryAllocator, width, bitsPerPixel, predictor)
36+
/// <param name="photometricInterpretation">The photometric interpretation.</param>
37+
public JpegTiffCompression(
38+
Configuration configuration,
39+
MemoryAllocator memoryAllocator,
40+
int width,
41+
int bitsPerPixel,
42+
byte[] jpegTables,
43+
TiffPhotometricInterpretation photometricInterpretation)
44+
: base(memoryAllocator, width, bitsPerPixel)
3745
{
3846
this.configuration = configuration;
3947
this.jpegTables = jpegTables;
48+
this.photometricInterpretation = photometricInterpretation;
4049
}
4150

4251
/// <inheritdoc/>
@@ -47,8 +56,11 @@ protected override void Decompress(BufferedReadStream stream, int byteCount, Spa
4756
{
4857
var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder());
4958

50-
// Should we pass through the CancellationToken from the tiff decoder?
51-
using var spectralConverter = new SpectralConverter<Rgb24>(this.configuration, CancellationToken.None);
59+
// TODO: Should we pass through the CancellationToken from the tiff decoder?
60+
// If the PhotometricInterpretation is YCbCr we explicitly assume the JPEG data is in RGB color space.
61+
// There seems no other way to determine that the JPEG data is RGB colorspace (no APP14 marker, componentId's are not RGB).
62+
using SpectralConverter<Rgb24> spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ?
63+
new RgbJpegSpectralConverter<Rgb24>(this.configuration, CancellationToken.None) : new SpectralConverter<Rgb24>(this.configuration, CancellationToken.None);
5264
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None);
5365
jpegDecoder.LoadTables(this.jpegTables, scanDecoder);
5466
scanDecoder.ResetInterval = 0;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.Threading;
5+
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
6+
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
7+
using SixLabors.ImageSharp.PixelFormats;
8+
9+
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
10+
{
11+
/// <summary>
12+
/// Spectral converter for YCbCr TIFF's which use the JPEG compression.
13+
/// The jpeg data should be always treated as RGB color space.
14+
/// </summary>
15+
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
16+
internal sealed class RgbJpegSpectralConverter<TPixel> : SpectralConverter<TPixel>
17+
where TPixel : unmanaged, IPixel<TPixel>
18+
{
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="RgbJpegSpectralConverter{TPixel}"/> class.
21+
/// This Spectral converter will always convert the pixel data to RGB color.
22+
/// </summary>
23+
/// <param name="configuration">The configuration.</param>
24+
/// <param name="cancellationToken">The cancellation token.</param>
25+
public RgbJpegSpectralConverter(Configuration configuration, CancellationToken cancellationToken)
26+
: base(configuration, cancellationToken)
27+
{
28+
}
29+
30+
/// <inheritdoc/>
31+
public override JpegColorConverter GetConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(JpegColorSpace.RGB, frame.Precision);
32+
}
33+
}

src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static TiffBaseDecompressor Create(
5454

5555
case TiffDecoderCompressionType.Jpeg:
5656
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
57-
return new JpegTiffCompression(configuration, allocator, width, bitsPerPixel, jpegTables);
57+
return new JpegTiffCompression(configuration, allocator, width, bitsPerPixel, jpegTables, photometricInterpretation);
5858

5959
default:
6060
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));

tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using SixLabors.ImageSharp.Formats.Jpeg;
88
using SixLabors.ImageSharp.Formats.Jpeg.Components;
99
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
10+
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
1011
using SixLabors.ImageSharp.IO;
1112
using SixLabors.ImageSharp.Memory;
1213
using SixLabors.ImageSharp.PixelFormats;
@@ -200,6 +201,8 @@ public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
200201

201202
this.spectralData = new LibJpegTools.SpectralData(spectralComponents);
202203
}
204+
205+
public override JpegColorConverter GetConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision);
203206
}
204207
}
205208
}

tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,8 @@ public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works<TPixel>(TestImag
290290

291291
[Theory]
292292
[WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
293-
public void TiffEncoder_EncodeRgb_WithjpegCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
294-
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg);
293+
public void TiffEncoder_EncodeRgb_WithJpegCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
294+
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, useExactComparer: false, compareTolerance: 0.012f);
295295

296296
[Theory]
297297
[WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]

0 commit comments

Comments
 (0)