Skip to content

Commit 916acb7

Browse files
committed
Add support for decoding jpeg compressed tiff images with cmyk colorspace
1 parent 900e9d0 commit 916acb7

File tree

13 files changed

+99
-10
lines changed

13 files changed

+99
-10
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using SixLabors.ImageSharp.ColorSpaces;
5+
using SixLabors.ImageSharp.ColorSpaces.Conversion;
6+
7+
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
8+
9+
internal abstract partial class JpegColorConverterBase
10+
{
11+
/// <summary>
12+
/// Color converter for tiff images, which use the jpeg compression and CMYK colorspace.
13+
/// </summary>
14+
internal sealed class TiffCmykScalar : JpegColorConverterScalar
15+
{
16+
public TiffCmykScalar(int precision)
17+
: base(JpegColorSpace.TiffCmyk, precision)
18+
{
19+
}
20+
21+
/// <inheritdoc/>
22+
public override void ConvertToRgbInplace(in ComponentValues values)
23+
=> ConvertToRgbInplace(values, this.MaximumValue);
24+
25+
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) => throw new NotImplementedException();
26+
27+
internal static void ConvertToRgbInplace(ComponentValues values, float maxValue)
28+
{
29+
float invMax = 1 / maxValue;
30+
for (int i = 0; i < values.Component0.Length; i++)
31+
{
32+
Cmyk cmyk = new(values.Component0[i] * invMax, values.Component1[i] * invMax, values.Component2[i] * invMax, values.Component3[i] * invMax);
33+
Rgb rgb = ColorSpaceConverter.ToRgb(in cmyk);
34+
values.Component0[i] = rgb.R;
35+
values.Component1[i] = rgb.G;
36+
values.Component2[i] = rgb.B;
37+
}
38+
}
39+
}
40+
}

src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int
105105
/// </summary>
106106
private static JpegColorConverterBase[] CreateConverters()
107107
{
108-
// 5 color types with 2 supported precisions: 8 bit & 12 bit
109-
const int colorConvertersCount = 5 * 2;
108+
// 6 color types with 2 supported precisions: 8 bit & 12 bit
109+
const int colorConvertersCount = 6 * 2;
110110

111111
JpegColorConverterBase[] converters = new JpegColorConverterBase[colorConvertersCount];
112112

@@ -116,13 +116,15 @@ private static JpegColorConverterBase[] CreateConverters()
116116
converters[2] = GetCmykConverter(8);
117117
converters[3] = GetGrayScaleConverter(8);
118118
converters[4] = GetRgbConverter(8);
119+
converters[5] = GetTiffCmykConverter(8);
119120

120121
// 12-bit converters
121-
converters[5] = GetYCbCrConverter(12);
122-
converters[6] = GetYccKConverter(12);
123-
converters[7] = GetCmykConverter(12);
124-
converters[8] = GetGrayScaleConverter(12);
125-
converters[9] = GetRgbConverter(12);
122+
converters[6] = GetYCbCrConverter(12);
123+
converters[7] = GetYccKConverter(12);
124+
converters[8] = GetCmykConverter(12);
125+
converters[9] = GetGrayScaleConverter(12);
126+
converters[10] = GetRgbConverter(12);
127+
converters[11] = GetTiffCmykConverter(12);
126128

127129
return converters;
128130
}
@@ -247,6 +249,8 @@ private static JpegColorConverterBase GetRgbConverter(int precision)
247249
return new RgbScalar(precision);
248250
}
249251

252+
private static JpegColorConverterBase GetTiffCmykConverter(int precision) => new TiffCmykScalar(precision);
253+
250254
/// <summary>
251255
/// A stack-only struct to reference the input buffers using <see cref="ReadOnlySpan{T}"/>-s.
252256
/// </summary>

src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ internal enum JpegColorSpace
2323
/// </summary>
2424
Cmyk,
2525

26+
/// <summary>
27+
/// Cmyk color space with 4 components, used with tiff images, which use jpeg compression.
28+
/// </summary>
29+
TiffCmyk,
30+
2631
/// <summary>
2732
/// Color space with 3 components.
2833
/// </summary>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ private void DecodeJpegData(BufferedReadStream stream, Span<byte> buffer, Cancel
8080

8181
case TiffPhotometricInterpretation.YCbCr:
8282
case TiffPhotometricInterpretation.Rgb:
83+
case TiffPhotometricInterpretation.Separated:
8384
{
8485
using SpectralConverter<Rgb24> spectralConverter = new TiffJpegSpectralConverter<Rgb24>(configuration, this.photometricInterpretation);
8586
HuffmanScanDecoder scanDecoder = new(stream, spectralConverter, cancellationToken);

src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRa
3636
}
3737

3838
/// <summary>
39-
/// This converter must be used only for RGB and YCbCr color spaces for performance reasons.
39+
/// Photometric interpretation Rgb and YCbCr will be mapped to RGB colorspace, which means the jpeg decompression will leave the data as is (no color conversion).
40+
/// The color conversion will be done after the decompression. For Separated/CMYK, the jpeg color converter will handle the color conversion,
41+
/// since the jpeg color converter needs to return RGB data and cannot return 4 component data.
4042
/// For grayscale images <see cref="GrayJpegSpectralConverter{TPixel}"/> must be used.
4143
/// </summary>
4244
private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation)
4345
=> interpretation switch
4446
{
4547
TiffPhotometricInterpretation.Rgb => JpegColorSpace.RGB,
4648
TiffPhotometricInterpretation.YCbCr => JpegColorSpace.RGB,
49+
TiffPhotometricInterpretation.Separated => JpegColorSpace.TiffCmyk,
4750
_ => throw new InvalidImageContentException($"Invalid tiff photometric interpretation for jpeg encoding: {interpretation}"),
4851
};
4952
}

src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Numerics;
55
using SixLabors.ImageSharp.ColorSpaces;
66
using SixLabors.ImageSharp.ColorSpaces.Conversion;
7+
using SixLabors.ImageSharp.Formats.Tiff.Compression;
78
using SixLabors.ImageSharp.Memory;
89
using SixLabors.ImageSharp.PixelFormats;
910

@@ -14,11 +15,33 @@ internal class CmykTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
1415
{
1516
private const float Inv255 = 1 / 255.0f;
1617

18+
private readonly TiffDecoderCompressionType compression;
19+
20+
public CmykTiffColor(TiffDecoderCompressionType compression) => this.compression = compression;
21+
1722
/// <inheritdoc/>
1823
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
1924
{
2025
TPixel color = default;
2126
int offset = 0;
27+
28+
if (this.compression == TiffDecoderCompressionType.Jpeg)
29+
{
30+
for (int y = top; y < top + height; y++)
31+
{
32+
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
33+
for (int x = 0; x < pixelRow.Length; x++)
34+
{
35+
color.FromVector4(new Vector4(data[offset] * Inv255, data[offset + 1] * Inv255, data[offset + 2] * Inv255, 1.0f));
36+
pixelRow[x] = color;
37+
38+
offset += 3;
39+
}
40+
}
41+
42+
return;
43+
}
44+
2245
for (int y = top; y < top + height; y++)
2346
{
2447
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);

src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using SixLabors.ImageSharp.Formats.Tiff.Compression;
45
using SixLabors.ImageSharp.Memory;
56
using SixLabors.ImageSharp.PixelFormats;
67

@@ -19,6 +20,7 @@ public static TiffBaseColorDecoder<TPixel> Create(
1920
Rational[] referenceBlackAndWhite,
2021
Rational[] ycbcrCoefficients,
2122
ushort[] ycbcrSubSampling,
23+
TiffDecoderCompressionType compression,
2224
ByteOrder byteOrder)
2325
{
2426
switch (colorType)
@@ -410,7 +412,7 @@ public static TiffBaseColorDecoder<TPixel> Create(
410412
&& bitsPerSample.Channel1 == 8
411413
&& bitsPerSample.Channel0 == 8,
412414
"bitsPerSample");
413-
return new CmykTiffColor<TPixel>();
415+
return new CmykTiffColor<TPixel>(compression);
414416

415417
default:
416418
throw TiffThrowHelper.InvalidColorType(colorType.ToString());

src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ private TiffBaseColorDecoder<TPixel> CreateChunkyColorDecoder<TPixel>()
703703
this.ReferenceBlackAndWhite,
704704
this.YcbcrCoefficients,
705705
this.YcbcrSubSampling,
706+
this.CompressionType,
706707
this.byteOrder);
707708

708709
private TiffBasePlanarColorDecoder<TPixel> CreatePlanarColorDecoder<TPixel>()

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Six Labors Split License.
33

44
// ReSharper disable InconsistentNaming
5-
using System.Runtime.InteropServices;
65
using System.Runtime.Intrinsics.X86;
76
using SixLabors.ImageSharp.Formats;
87
using SixLabors.ImageSharp.Formats.Tiff;
@@ -309,6 +308,7 @@ public void TiffDecoder_CanDecode_CieLab<TPixel>(TestImageProvider<TPixel> provi
309308
[Theory]
310309
[WithFile(Cmyk, PixelTypes.Rgba32)]
311310
[WithFile(CmykLzwPredictor, PixelTypes.Rgba32)]
311+
[WithFile(CmykJpeg, PixelTypes.Rgba32)]
312312
public void TiffDecoder_CanDecode_Cmyk<TPixel>(TestImageProvider<TPixel> provider)
313313
where TPixel : unmanaged, IPixel<TPixel>
314314
{

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ public static class Tiff
968968
public const string Cmyk = "Tiff/Cmyk.tiff";
969969
public const string Cmyk64BitDeflate = "Tiff/cmyk_deflate_64bit.tiff";
970970
public const string CmykLzwPredictor = "Tiff/Cmyk-lzw-predictor.tiff";
971+
public const string CmykJpeg = "Tiff/Cmyk-jpeg.tiff";
971972

972973
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
973974
public const string Issues1891 = "Tiff/Issues/Issue1891.tiff";

0 commit comments

Comments
 (0)