From f3354d1078605775132049270617b1c03be7fae9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 13 Oct 2022 14:55:46 +0200 Subject: [PATCH 1/3] Add support for decoding tiff images with cmyk data --- .../CieLabTiffColor{TPixel}.cs | 6 +-- .../CmykTiffColor{TPixel}.cs | 37 +++++++++++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 10 +++++ .../TiffColorType.cs | 5 +++ src/ImageSharp/Formats/Tiff/README.md | 2 +- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 17 +++++++++ .../Formats/Tiff/TiffDecoderTests.cs | 12 ++++++ tests/ImageSharp.Tests/TestImages.cs | 2 + ...TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk.png | 3 ++ tests/Images/Input/Tiff/cmyk.tiff | 3 ++ 10 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs create mode 100644 tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk.png create mode 100644 tests/Images/Input/Tiff/cmyk.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs index 01ccfd355..b39a64644 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs @@ -22,7 +22,7 @@ internal class CieLabTiffColor : TiffBaseColorDecoder /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { - var color = default(TPixel); + TPixel color = default; int offset = 0; for (int y = top; y < top + height; y++) { @@ -31,8 +31,8 @@ internal class CieLabTiffColor : TiffBaseColorDecoder for (int x = 0; x < pixelRow.Length; x++) { float l = (data[offset] & 0xFF) * 100f * Inv255; - var lab = new CieLab(l, (sbyte)data[offset + 1], (sbyte)data[offset + 2]); - var rgb = ColorSpaceConverter.ToRgb(lab); + CieLab lab = new(l, (sbyte)data[offset + 1], (sbyte)data[offset + 2]); + Rgb rgb = ColorSpaceConverter.ToRgb(lab); color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); pixelRow[x] = color; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs new file mode 100644 index 000000000..770ce98fd --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +internal class CmykTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel +{ + private const float Inv255 = 1 / 255.0f; + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + TPixel color = default; + int offset = 0; + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) + { + Cmyk cmyk = new(data[offset] * Inv255, data[offset + 1] * Inv255, data[offset + 2] * Inv255, data[offset + 3] * Inv255); + Rgb rgb = ColorSpaceConverter.ToRgb(in cmyk); + + color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); + pixelRow[x] = color; + + offset += 4; + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 2ad1d8677..c59b08a55 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -402,6 +402,16 @@ internal static class TiffColorDecoderFactory "bitsPerSample"); return new CieLabTiffColor(); + case TiffColorType.Cmyk: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 8 + && bitsPerSample.Channel2 == 8 + && bitsPerSample.Channel1 == 8 + && bitsPerSample.Channel0 == 8, + "bitsPerSample"); + return new CmykTiffColor(); + default: throw TiffThrowHelper.InvalidColorType(colorType.ToString()); } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index 5857fce81..c54857d08 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -287,4 +287,9 @@ internal enum TiffColorType /// The pixels are stored in CieLab format as planar. /// CieLabPlanar, + + /// + /// The pixels are stored as CMYK. + /// + Cmyk, } diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 0ce467e3f..99594b4c5 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -54,7 +54,7 @@ |Rgb (Planar) | | Y | General implementation only. | |PaletteColor | Y | Y | General implementation only. | |TransparencyMask | | | | -|Separated (TIFF Extension) | | | | +|Separated (TIFF Extension) | | Y | | |YCbCr (TIFF Extension) | | Y | | |CieLab (TIFF Extension) | | Y | | |IccLab (TechNote 1) | | | | diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 648b4a093..5a4baafa4 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -413,6 +413,23 @@ internal static class TiffDecoderOptionsParser break; } + case TiffPhotometricInterpretation.Separated: + { + if (options.BitsPerSample.Channels != 4) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for CMYK images."); + } + + ushort bitsPerChannel = options.BitsPerSample.Channel0; + if (bitsPerChannel != 8) + { + TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for CMYK images."); + } + + options.ColorType = TiffColorType.Cmyk; + break; + } + default: { TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}"); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 73c5c00bc..2dd56f079 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -330,6 +330,18 @@ public class TiffDecoderTests : TiffDecoderBaseTester image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + [Theory] + [WithFile(Cmyk, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Cmyk(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong + // converting the pixel data from Magick.NET to our format with CMYK? + using Image image = provider.GetImage(); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + [Theory] [WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index de5044a3c..6f056088a 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -936,6 +936,8 @@ public static class TestImages public const string CieLabPlanar = "Tiff/CieLabPlanar.tiff"; public const string CieLabLzwPredictor = "Tiff/CieLab_lzwcompressed_predictor.tiff"; + public const string Cmyk = "Tiff/Cmyk.tiff"; + public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string Issues1891 = "Tiff/Issues/Issue1891.tiff"; public const string Issues2123 = "Tiff/Issues/Issue2123.tiff"; diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk.png new file mode 100644 index 000000000..00fe4de82 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db076491d7afc78cb5de99cb1e7a9c53f891157bf064994c60d453aec75b9c90 +size 512 diff --git a/tests/Images/Input/Tiff/cmyk.tiff b/tests/Images/Input/Tiff/cmyk.tiff new file mode 100644 index 000000000..892083d9b --- /dev/null +++ b/tests/Images/Input/Tiff/cmyk.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16a73f25e9c2bc6e2e51bd7399d2193bc5d5731f45cfd75147c19746deecf039 +size 40278 From a363e9e1674fe6806f0b647289ec1683969fca2b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 13 Oct 2022 17:06:11 +0200 Subject: [PATCH 2/3] Fix file header --- .../Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs index 770ce98fd..c87051d52 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; From 6b65d23e6ef06f662b9419773525a502aade96f5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 13 Oct 2022 17:28:08 +0200 Subject: [PATCH 3/3] Rename cmyk test file --- tests/Images/Input/Tiff/{cmyk.tiff => Cmyk.tiff} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/Images/Input/Tiff/{cmyk.tiff => Cmyk.tiff} (100%) diff --git a/tests/Images/Input/Tiff/cmyk.tiff b/tests/Images/Input/Tiff/Cmyk.tiff similarity index 100% rename from tests/Images/Input/Tiff/cmyk.tiff rename to tests/Images/Input/Tiff/Cmyk.tiff