diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index a31ff7a9fd..2a52e8bff5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - ushort intensity = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); + ushort intensity = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); offset += 2; pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - ushort intensity = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); + ushort intensity = TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2)); offset += 2; pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index 4b34d5a0db..86ac94f55a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -41,11 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - ulong r = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); + ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); offset += 2; - ulong g = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); offset += 2; - ulong b = TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); offset += 2; pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); @@ -55,11 +55,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - ulong r = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); + ulong r = TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2)); offset += 2; - ulong g = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2)); offset += 2; - ulong b = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2)); offset += 2; pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs index c24fada54c..20053eb8a1 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -44,9 +44,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - ulong r = TiffUtils.ConvertToShortBigEndian(redData.Slice(offset, 2)); - ulong g = TiffUtils.ConvertToShortBigEndian(greenData.Slice(offset, 2)); - ulong b = TiffUtils.ConvertToShortBigEndian(blueData.Slice(offset, 2)); + ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2)); offset += 2; @@ -57,9 +57,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - ulong r = TiffUtils.ConvertToShortLittleEndian(redData.Slice(offset, 2)); - ulong g = TiffUtils.ConvertToShortLittleEndian(greenData.Slice(offset, 2)); - ulong b = TiffUtils.ConvertToShortLittleEndian(blueData.Slice(offset, 2)); + ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); offset += 2; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs new file mode 100644 index 0000000000..7d13fdcdf4 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 16 bits for each channel. + /// + internal class Rgb242424TiffColor : TiffBaseColorDecoder + 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 Rgb242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(ReadOnlySpan 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); + int offset = 0; + float scale = 1.0f / 0xFFFFFF; + byte[] buffer = new byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 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++) + { + data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + data.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++) + { + data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.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 0c93998c48..ed9a14b2a5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -136,6 +136,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb161616TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb242424: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 24 + && bitsPerSample.Channel1 == 24 + && bitsPerSample.Channel0 == 24, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb242424TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.PaletteColor: DebugGuard.NotNull(colorMap, "colorMap"); return new PaletteTiffColor(bitsPerSample, colorMap); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index 331065d273..b49bcc219e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -103,6 +103,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// Rgb161616, + /// + /// RGB color image with 24 bits for each channel. + /// + Rgb242424, + /// /// RGB Full Color. Planar configuration of data. /// diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs index a6b5151d70..18b5300b27 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortBigEndian(data.Slice(offset, 2))); + ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2))); offset += 2; pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2))); + ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2))); offset += 2; pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 14c527a34c..4563e23170 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -206,6 +206,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff ushort bitsPerChannel = options.BitsPerSample.Channel0; switch (bitsPerChannel) { + case 24: + options.ColorType = TiffColorType.Rgb242424; + break; + case 16: options.ColorType = TiffColorType.Rgb161616; break; diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index a0ba40f4e0..9514e43015 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -21,12 +21,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils public static L16 L16Default { get; } = new L16(0); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ConvertToShortBigEndian(ReadOnlySpan buffer) => - BinaryPrimitives.ReadUInt16BigEndian(buffer); + public static ushort ConvertToUShortBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ConvertToShortLittleEndian(ReadOnlySpan buffer) => - BinaryPrimitives.ReadUInt16LittleEndian(buffer); + public static ushort ConvertToUShortLittleEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16LittleEndian(buffer); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ConvertToUIntBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt32BigEndian(buffer); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ConvertToUIntLittleEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt32LittleEndian(buffer); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorFromL8(L8 l8, byte intensity, TPixel color) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 3bf1c25f3f..9cda1bdac8 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -173,6 +173,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_72Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 9c24184366..1f98c30a3f 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 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"; public const string FlowerRgb161616ContiguousLittleEndian = "Tiff/flower-rgb-contig-16_lsb.tiff"; public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff"; diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-24.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-24.tiff new file mode 100644 index 0000000000..9145c21db1 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-24.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6368a704b0a629239024f6fbfb30723fa317593ef36ddba05d76302530bd974 +size 28568 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff new file mode 100644 index 0000000000..40cf1c9b85 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbb2b4ca6d7eeee4737c6963c99ef68fb6971cf6ccee463427a8246574bc6440 +size 28632