Browse Source

Add support for decoding jpeg compressed tiff images with cmyk colorspace

pull/2937/head
Brian Popow 3 years ago
parent
commit
916acb7a16
  1. 40
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs
  2. 18
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
  3. 5
      src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs
  4. 1
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  5. 5
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs
  6. 23
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs
  7. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  8. 1
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  9. 2
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  10. 1
      tests/ImageSharp.Tests/TestImages.cs
  11. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png
  12. 3
      tests/Images/Input/Tiff/Cmyk-jpeg.tiff
  13. 3
      tests/Images/Input/Tiff/Cmyk-planar-jpg.tiff

40
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs

@ -0,0 +1,40 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
{
/// <summary>
/// Color converter for tiff images, which use the jpeg compression and CMYK colorspace.
/// </summary>
internal sealed class TiffCmykScalar : JpegColorConverterScalar
{
public TiffCmykScalar(int precision)
: base(JpegColorSpace.TiffCmyk, precision)
{
}
/// <inheritdoc/>
public override void ConvertToRgbInplace(in ComponentValues values)
=> ConvertToRgbInplace(values, this.MaximumValue);
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane) => throw new NotImplementedException();
internal static void ConvertToRgbInplace(ComponentValues values, float maxValue)
{
float invMax = 1 / maxValue;
for (int i = 0; i < values.Component0.Length; i++)
{
Cmyk cmyk = new(values.Component0[i] * invMax, values.Component1[i] * invMax, values.Component2[i] * invMax, values.Component3[i] * invMax);
Rgb rgb = ColorSpaceConverter.ToRgb(in cmyk);
values.Component0[i] = rgb.R;
values.Component1[i] = rgb.G;
values.Component2[i] = rgb.B;
}
}
}
}

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

@ -105,8 +105,8 @@ internal abstract partial class JpegColorConverterBase
/// </summary>
private static JpegColorConverterBase[] CreateConverters()
{
// 5 color types with 2 supported precisions: 8 bit & 12 bit
const int colorConvertersCount = 5 * 2;
// 6 color types with 2 supported precisions: 8 bit & 12 bit
const int colorConvertersCount = 6 * 2;
JpegColorConverterBase[] converters = new JpegColorConverterBase[colorConvertersCount];
@ -116,13 +116,15 @@ internal abstract partial class JpegColorConverterBase
converters[2] = GetCmykConverter(8);
converters[3] = GetGrayScaleConverter(8);
converters[4] = GetRgbConverter(8);
converters[5] = GetTiffCmykConverter(8);
// 12-bit converters
converters[5] = GetYCbCrConverter(12);
converters[6] = GetYccKConverter(12);
converters[7] = GetCmykConverter(12);
converters[8] = GetGrayScaleConverter(12);
converters[9] = GetRgbConverter(12);
converters[6] = GetYCbCrConverter(12);
converters[7] = GetYccKConverter(12);
converters[8] = GetCmykConverter(12);
converters[9] = GetGrayScaleConverter(12);
converters[10] = GetRgbConverter(12);
converters[11] = GetTiffCmykConverter(12);
return converters;
}
@ -247,6 +249,8 @@ internal abstract partial class JpegColorConverterBase
return new RgbScalar(precision);
}
private static JpegColorConverterBase GetTiffCmykConverter(int precision) => new TiffCmykScalar(precision);
/// <summary>
/// A stack-only struct to reference the input buffers using <see cref="ReadOnlySpan{T}"/>-s.
/// </summary>

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

@ -23,6 +23,11 @@ internal enum JpegColorSpace
/// </summary>
Cmyk,
/// <summary>
/// Cmyk color space with 4 components, used with tiff images, which use jpeg compression.
/// </summary>
TiffCmyk,
/// <summary>
/// Color space with 3 components.
/// </summary>

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

@ -80,6 +80,7 @@ internal sealed class JpegTiffCompression : TiffBaseDecompressor
case TiffPhotometricInterpretation.YCbCr:
case TiffPhotometricInterpretation.Rgb:
case TiffPhotometricInterpretation.Separated:
{
using SpectralConverter<Rgb24> spectralConverter = new TiffJpegSpectralConverter<Rgb24>(configuration, this.photometricInterpretation);
HuffmanScanDecoder scanDecoder = new(stream, spectralConverter, cancellationToken);

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

@ -36,7 +36,9 @@ internal sealed class TiffJpegSpectralConverter<TPixel> : SpectralConverter<TPix
}
/// <summary>
/// This converter must be used only for RGB and YCbCr color spaces for performance reasons.
/// 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).
/// The color conversion will be done after the decompression. For Separated/CMYK, the jpeg color converter will handle the color conversion,
/// since the jpeg color converter needs to return RGB data and cannot return 4 component data.
/// For grayscale images <see cref="GrayJpegSpectralConverter{TPixel}"/> must be used.
/// </summary>
private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation)
@ -44,6 +46,7 @@ internal sealed class TiffJpegSpectralConverter<TPixel> : SpectralConverter<TPix
{
TiffPhotometricInterpretation.Rgb => JpegColorSpace.RGB,
TiffPhotometricInterpretation.YCbCr => JpegColorSpace.RGB,
TiffPhotometricInterpretation.Separated => JpegColorSpace.TiffCmyk,
_ => throw new InvalidImageContentException($"Invalid tiff photometric interpretation for jpeg encoding: {interpretation}"),
};
}

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

@ -4,6 +4,7 @@
using System.Numerics;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -14,11 +15,33 @@ internal class CmykTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
private const float Inv255 = 1 / 255.0f;
private readonly TiffDecoderCompressionType compression;
public CmykTiffColor(TiffDecoderCompressionType compression) => this.compression = compression;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default;
int offset = 0;
if (this.compression == TiffDecoderCompressionType.Jpeg)
{
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
color.FromVector4(new Vector4(data[offset] * Inv255, data[offset + 1] * Inv255, data[offset + 2] * Inv255, 1.0f));
pixelRow[x] = color;
offset += 3;
}
}
return;
}
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -19,6 +20,7 @@ internal static class TiffColorDecoderFactory<TPixel>
Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients,
ushort[] ycbcrSubSampling,
TiffDecoderCompressionType compression,
ByteOrder byteOrder)
{
switch (colorType)
@ -410,7 +412,7 @@ internal static class TiffColorDecoderFactory<TPixel>
&& bitsPerSample.Channel1 == 8
&& bitsPerSample.Channel0 == 8,
"bitsPerSample");
return new CmykTiffColor<TPixel>();
return new CmykTiffColor<TPixel>(compression);
default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString());

1
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -703,6 +703,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
this.ReferenceBlackAndWhite,
this.YcbcrCoefficients,
this.YcbcrSubSampling,
this.CompressionType,
this.byteOrder);
private TiffBasePlanarColorDecoder<TPixel> CreatePlanarColorDecoder<TPixel>()

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

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
// ReSharper disable InconsistentNaming
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff;
@ -309,6 +308,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[Theory]
[WithFile(Cmyk, PixelTypes.Rgba32)]
[WithFile(CmykLzwPredictor, PixelTypes.Rgba32)]
[WithFile(CmykJpeg, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Cmyk<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{

1
tests/ImageSharp.Tests/TestImages.cs

@ -968,6 +968,7 @@ public static class TestImages
public const string Cmyk = "Tiff/Cmyk.tiff";
public const string Cmyk64BitDeflate = "Tiff/cmyk_deflate_64bit.tiff";
public const string CmykLzwPredictor = "Tiff/Cmyk-lzw-predictor.tiff";
public const string CmykJpeg = "Tiff/Cmyk-jpeg.tiff";
public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff";
public const string Issues1891 = "Tiff/Issues/Issue1891.tiff";

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7f68db78d765a7f36570cd7b57a1f06cfca24c3b4916d0692a4aa051209ec327
size 616

3
tests/Images/Input/Tiff/Cmyk-jpeg.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:abb923e457acc31a7f18c46a7d58fc5a42f5c3d197236403921e3ee623fa4fac
size 2046

3
tests/Images/Input/Tiff/Cmyk-planar-jpg.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:abb923e457acc31a7f18c46a7d58fc5a42f5c3d197236403921e3ee623fa4fac
size 2046
Loading…
Cancel
Save