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