From 28c1356a765c9601cae4be7322a7a2746d1a3b62 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Aug 2021 21:21:59 +0200 Subject: [PATCH] Add support for decoding 24bit per channel color tiff with planar pixel data --- .../Rgb16PlanarTiffColor{TPixel}.cs | 2 +- .../Rgb24PlanarTiffColor{TPixel}.cs | 87 +++++++++++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 5 ++ .../Formats/Tiff/TiffDecoderTests.cs | 2 + tests/ImageSharp.Tests/TestImages.cs | 2 + .../Input/Tiff/flower-rgb-planar-24.tiff | 3 + .../Input/Tiff/flower-rgb-planar-24_lsb.tiff | 3 + 7 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-rgb-planar-24.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-planar-24_lsb.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs index 20053eb8a1..9a6d4631ac 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -10,7 +10,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { /// - /// Implements the 'RGB' photometric interpretation with 'Planar' layout for all 16 bit. + /// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 16 bit. /// internal class Rgb16PlanarTiffColor : TiffBasePlanarColorDecoder where TPixel : unmanaged, IPixel diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs new file mode 100644 index 0000000000..c322b35b38 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs @@ -0,0 +1,87 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 24 bit. + /// + internal class Rgb24PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel + { + private readonly bool isBigEndian; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgb24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, + // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 + var color = default(TPixel); + color.FromVector4(TiffUtils.Vector4Default); + float scale = 1.0f / 0xFFFFFF; + byte[] buffer = new byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; + + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + + int offset = 0; + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + redData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); + greenData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); + blueData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); + + offset += 3; + + var colorVector = new Vector4(r * scale, g * scale, b * scale, 1.0f); + color.FromVector4(colorVector); + + pixelRow[x] = color; + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + redData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); + greenData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); + blueData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); + + offset += 3; + + var colorVector = new Vector4(r * scale, g * scale, b * scale, 1.0f); + color.FromVector4(colorVector); + + pixelRow[x] = color; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index ed9a14b2a5..1167d4784d 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -166,6 +166,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation return new Rgb16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); } + if (bitsPerSample.Channel0 == 24 && bitsPerSample.Channel1 == 24 && bitsPerSample.Channel2 == 24) + { + return new Rgb24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + } + return new RgbPlanarTiffColor(bitsPerSample); default: diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 9cda1bdac8..91fc699509 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -176,6 +176,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] + [WithFile(FlowerRgb242424Planar, PixelTypes.Rgba32)] + [WithFile(FlowerRgb242424PlanarLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_72Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 1f98c30a3f..2f03e067a0 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -561,6 +561,8 @@ namespace SixLabors.ImageSharp.Tests public const string RgbPalette = "Tiff/rgb_palette.tiff"; public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; + public const string FlowerRgb242424Planar = "Tiff/flower-rgb-planar-24.tiff"; + public const string FlowerRgb242424PlanarLittleEndian = "Tiff/flower-rgb-planar-24_lsb.tiff"; public const string FlowerRgb242424Contiguous = "Tiff/flower-rgb-contig-24.tiff"; public const string FlowerRgb242424ContiguousLittleEndian = "Tiff/flower-rgb-contig-24_lsb.tiff"; public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff"; diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-24.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-24.tiff new file mode 100644 index 0000000000..b0b41901cc --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-planar-24.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:752452ac51ad1e836fb81267ab708ff81cf81a4c7e00daeed703f67782b563ec +size 28586 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-24_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-24_lsb.tiff new file mode 100644 index 0000000000..c615089fd5 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-planar-24_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72f27af4fe177ebe47bef2af64723497d5a5f44808424bedfc2012fe4e3fc34e +size 28586