From 6e9cff93f4f37f2f257b6d065d9ed0edb6fb28c9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Aug 2021 20:31:48 +0200 Subject: [PATCH 01/20] Add support for decoding 24bit per channel color tiff with contiguous pixel data --- .../BlackIsZero16TiffColor{TPixel}.cs | 4 +- .../Rgb161616TiffColor{TPixel}.cs | 12 +-- .../Rgb16PlanarTiffColor{TPixel}.cs | 12 +-- .../Rgb242424TiffColor{TPixel}.cs | 89 +++++++++++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 10 +++ .../TiffColorType.cs | 5 ++ .../WhiteIsZero16TiffColor{TPixel}.cs | 4 +- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 4 + .../Formats/Tiff/Utils/TiffUtils.cs | 12 ++- .../Formats/Tiff/TiffDecoderTests.cs | 6 ++ tests/ImageSharp.Tests/TestImages.cs | 2 + .../Input/Tiff/flower-rgb-contig-24.tiff | 3 + .../Input/Tiff/flower-rgb-contig-24_lsb.tiff | 3 + 13 files changed, 146 insertions(+), 20 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-24.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-24_lsb.tiff 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 From 28c1356a765c9601cae4be7322a7a2746d1a3b62 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Aug 2021 21:21:59 +0200 Subject: [PATCH 02/20] 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 From e700b972fd3c9a554fe3dac2bce8564c44d3e268 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 3 Aug 2021 22:04:21 +0200 Subject: [PATCH 03/20] Add support for decoding gray 24 bit tiff's --- .../BlackIsZero24TiffColor{TPixel}.cs | 65 +++++++++++++++++++ .../Rgb242424TiffColor{TPixel}.cs | 12 +--- .../Rgb24PlanarTiffColor{TPixel}.cs | 12 +--- .../TiffColorDecoderFactory{TPixel}.cs | 10 +++ .../TiffColorType.cs | 10 +++ .../WhiteIsZero24TiffColor{TPixel}.cs | 64 ++++++++++++++++++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 16 ++++- .../Formats/Tiff/Utils/TiffUtils.cs | 26 +++++++- .../Formats/Tiff/TiffDecoderTests.cs | 5 ++ tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Tiff/flower-minisblack-24.tiff | 3 + 11 files changed, 199 insertions(+), 25 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-minisblack-24.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs new file mode 100644 index 0000000000..9dc989c386 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs @@ -0,0 +1,65 @@ +// 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 'BlackIsZero' photometric interpretation for 24-bit grayscale images. + /// + internal class BlackIsZero24TiffColor : 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 BlackIsZero24TiffColor(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); + byte[] buffer = new byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; + + 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++) + { + data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs index 7d13fdcdf4..ce8d2db64a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs @@ -2,7 +2,6 @@ // 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; @@ -31,7 +30,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation 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; @@ -55,10 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation 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; + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); } } else @@ -77,10 +72,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation 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; + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs index c322b35b38..cd94f8e817 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -31,7 +30,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation // 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; @@ -56,10 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 3; - var colorVector = new Vector4(r * scale, g * scale, b * scale, 1.0f); - color.FromVector4(colorVector); - - pixelRow[x] = color; + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); } } else @@ -75,10 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 3; - var colorVector = new Vector4(r * scale, g * scale, b * scale, 1.0f); - color.FromVector4(colorVector); - - pixelRow[x] = color; + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 1167d4784d..20b3b13407 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -37,6 +37,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new WhiteIsZero16TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.WhiteIsZero24: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 24, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero24TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.BlackIsZero: DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); @@ -62,6 +67,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new BlackIsZero16TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.BlackIsZero24: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 24, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero24TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb: DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index b49bcc219e..81418df297 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -33,6 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// BlackIsZero16, + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 24-bit images. + /// + BlackIsZero24, + /// /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. /// @@ -58,6 +63,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// WhiteIsZero16, + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 24-bit images. + /// + WhiteIsZero24, + /// /// Palette-color. /// diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs new file mode 100644 index 0000000000..143a684c27 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation for 24-bit grayscale images. + /// + internal class WhiteIsZero24TiffColor : 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 WhiteIsZero24TiffColor(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); + byte[] buffer = new byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; + + 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++) + { + data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 4563e23170..2186f2f9af 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -104,13 +104,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff } ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel > 16) + if (bitsPerChannel > 24) { TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); } switch (bitsPerChannel) { + case 24: + { + options.ColorType = TiffColorType.WhiteIsZero24; + break; + } + case 16: { options.ColorType = TiffColorType.WhiteIsZero16; @@ -153,13 +159,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff } ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel > 16) + if (bitsPerChannel > 24) { TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); } switch (bitsPerChannel) { + case 24: + { + options.ColorType = TiffColorType.BlackIsZero24; + break; + } + case 16: { options.ColorType = TiffColorType.BlackIsZero16; diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index 9514e43015..c8fd980216 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -14,6 +14,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils /// internal static class TiffUtils { + private const float Scale = 1.0f / 0xFFFFFF; + public static Vector4 Vector4Default { get; } = new Vector4(0.0f, 0.0f, 0.0f, 0.0f); public static Rgba64 Rgba64Default { get; } = new Rgba64(0, 0, 0, 0); @@ -41,6 +43,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromRgba64(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color) + where TPixel : unmanaged, IPixel + { + rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); + color.FromRgba64(rgba); + return color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(r * Scale, g * Scale, b * Scale, 1.0f); + color.FromVector4(colorVector); + return color; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorFromL16(L16 l16, ushort intensity, TPixel color) where TPixel : unmanaged, IPixel @@ -51,11 +71,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorFromRgba64(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color) + public static TPixel ColorScaleTo24Bit(ulong intensity, TPixel color) where TPixel : unmanaged, IPixel { - rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); - color.FromRgba64(rgba); + var colorVector = new Vector4(intensity * Scale, intensity * Scale, intensity * Scale, 1.0f); + color.FromVector4(colorVector); return color; } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 91fc699509..90d17c959c 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -147,6 +147,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_16Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Flower24BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(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 2f03e067a0..641ff4671c 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -588,6 +588,7 @@ namespace SixLabors.ImageSharp.Tests public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff"; public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff"; public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff"; + public const string Flower24BitGray = "Tiff/flower-minisblack-24.tiff"; public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff"; public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff"; public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16.tiff"; diff --git a/tests/Images/Input/Tiff/flower-minisblack-24.tiff b/tests/Images/Input/Tiff/flower-minisblack-24.tiff new file mode 100644 index 0000000000..7f9dd009d6 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-24.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe2d4e0d99bdfade966e27bd9583bce39bebb90efa8e7f768ce3cec69aa306e2 +size 9770 From c34a1ed0247c131af590feabb2603e40b3a3164d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 4 Aug 2021 08:57:10 +0200 Subject: [PATCH 04/20] Add support for decoding gray 32 bit tiff's --- .../BlackIsZero24TiffColor{TPixel}.cs | 1 - .../BlackIsZero32TiffColor{TPixel}.cs | 61 +++++++++++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 10 +++ .../TiffColorType.cs | 10 +++ .../WhiteIsZero32TiffColor{TPixel}.cs | 60 ++++++++++++++++++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 16 ++++- .../Formats/Tiff/Utils/TiffUtils.cs | 17 +++++- .../Formats/Tiff/TiffDecoderTests.cs | 29 ++++++--- tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Tiff/flower-minisblack-32.tiff | 3 + 10 files changed, 194 insertions(+), 14 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-minisblack-32.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs index 9dc989c386..813d7beb57 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs @@ -2,7 +2,6 @@ // 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; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs new file mode 100644 index 0000000000..862756bc42 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs @@ -0,0 +1,61 @@ +// 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 'BlackIsZero' photometric interpretation for 32-bit grayscale images. + /// + internal class BlackIsZero32TiffColor : 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 BlackIsZero32TiffColor(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; + 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++) + { + ulong intensity = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong intensity = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 20b3b13407..34866b58fb 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -42,6 +42,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new WhiteIsZero24TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.WhiteIsZero32: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero32TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.BlackIsZero: DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); @@ -72,6 +77,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new BlackIsZero24TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.BlackIsZero32: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero32TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb: DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index 81418df297..23f031173d 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -38,6 +38,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// BlackIsZero24, + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 32-bit images. + /// + BlackIsZero32, + /// /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. /// @@ -68,6 +73,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// WhiteIsZero24, + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 32-bit images. + /// + WhiteIsZero32, + /// /// Palette-color. /// diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs new file mode 100644 index 0000000000..0071740036 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +{ + /// + /// Implements the 'WhiteIsZero' photometric interpretation for 32-bit grayscale images. + /// + internal class WhiteIsZero32TiffColor : 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 WhiteIsZero32TiffColor(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; + 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++) + { + ulong intensity = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong intensity = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 2186f2f9af..1162addee7 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -104,13 +104,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff } ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel > 24) + if (bitsPerChannel > 32) { TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); } switch (bitsPerChannel) { + case 32: + { + options.ColorType = TiffColorType.WhiteIsZero32; + break; + } + case 24: { options.ColorType = TiffColorType.WhiteIsZero24; @@ -159,13 +165,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff } ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel > 24) + if (bitsPerChannel > 32) { TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); } switch (bitsPerChannel) { + case 32: + { + options.ColorType = TiffColorType.BlackIsZero32; + break; + } + case 24: { options.ColorType = TiffColorType.BlackIsZero24; diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index c8fd980216..95c4a542f2 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -14,7 +14,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils /// internal static class TiffUtils { - private const float Scale = 1.0f / 0xFFFFFF; + private const float Scale24Bit = 1.0f / 0xFFFFFF; + + private const float Scale32Bit = 1.0f / 0xFFFFFFFF; public static Vector4 Vector4Default { get; } = new Vector4(0.0f, 0.0f, 0.0f, 0.0f); @@ -56,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, TPixel color) where TPixel : unmanaged, IPixel { - var colorVector = new Vector4(r * Scale, g * Scale, b * Scale, 1.0f); + var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f); color.FromVector4(colorVector); return color; } @@ -74,7 +76,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils public static TPixel ColorScaleTo24Bit(ulong intensity, TPixel color) where TPixel : unmanaged, IPixel { - var colorVector = new Vector4(intensity * Scale, intensity * Scale, intensity * Scale, 1.0f); + var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f); + color.FromVector4(colorVector); + return color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo32Bit(ulong intensity, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(intensity * Scale32Bit, intensity * Scale32Bit, intensity * Scale32Bit, 1.0f); color.FromVector4(colorVector); return color; } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 90d17c959c..ae02cc9889 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -107,36 +107,44 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(Flower2BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_2Bit(TestImageProvider provider) + public void TiffDecoder_CanDecode_2Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] [WithFile(FlowerRgb222Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb222Planar, PixelTypes.Rgba32)] - [WithFile(Flower6BitGray, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_6Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Flower6BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_6Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(Flower8BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_8Bit(TestImageProvider provider) + public void TiffDecoder_CanDecode_8Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] [WithFile(Flower10BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_10Bit(TestImageProvider provider) + public void TiffDecoder_CanDecode_10Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] [WithFile(FlowerRgb444Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] - [WithFile(Flower12BitGray, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_12Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Flower12BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_12Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(Flower14BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_14Bit(TestImageProvider provider) + public void TiffDecoder_CanDecode_14Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] @@ -144,12 +152,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(Flower16BitGray, PixelTypes.Rgba32)] [WithFile(Flower16BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower16BitGrayMinIsWhiteBigEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_16Bit(TestImageProvider provider) + public void TiffDecoder_CanDecode_16Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] [WithFile(Flower24BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_24Bit(TestImageProvider provider) + public void TiffDecoder_CanDecode_24Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] @@ -158,6 +166,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_30Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Flower32BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_32Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_36Bit(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 641ff4671c..9005c98fa6 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -589,6 +589,7 @@ namespace SixLabors.ImageSharp.Tests public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff"; public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff"; public const string Flower24BitGray = "Tiff/flower-minisblack-24.tiff"; + public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff"; public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff"; public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff"; public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16.tiff"; diff --git a/tests/Images/Input/Tiff/flower-minisblack-32.tiff b/tests/Images/Input/Tiff/flower-minisblack-32.tiff new file mode 100644 index 0000000000..b64616ed22 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-32.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6677c372a449fe0324b148385cf0ebaaf33ab4563484ae89831dfeacd80d7c93 +size 12885 From 9c585c48993d29cf36d4dd9ac892536b35b01ceb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 4 Aug 2021 09:09:06 +0200 Subject: [PATCH 05/20] Add support for decoding 32bit per channel color tiff with contiguous pixel data --- .../Rgb242424TiffColor{TPixel}.cs | 2 +- .../Rgb323232TiffColor{TPixel}.cs | 73 +++++++++++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 10 +++ .../TiffColorType.cs | 5 ++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 4 + .../Formats/Tiff/Utils/TiffUtils.cs | 9 +++ .../Formats/Tiff/TiffDecoderTests.cs | 5 ++ tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Tiff/flower-rgb-contig-32.tiff | 3 + 9 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-32.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs index ce8d2db64a..abc3a82a94 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs @@ -9,7 +9,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { /// - /// Implements the 'RGB' photometric interpretation with 16 bits for each channel. + /// Implements the 'RGB' photometric interpretation with 24 bits for each channel. /// internal class Rgb242424TiffColor : TiffBaseColorDecoder where TPixel : unmanaged, IPixel diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs new file mode 100644 index 0000000000..e2ba085e1f --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 32 bits for each channel. + /// + internal class Rgb323232TiffColor : 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 Rgb323232TiffColor(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; + + 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++) + { + ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 34866b58fb..a34b1ad3e0 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -166,6 +166,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb242424TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb323232: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 32 + && bitsPerSample.Channel1 == 32 + && bitsPerSample.Channel0 == 32, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb323232TiffColor(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 23f031173d..ffc5a81673 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -128,6 +128,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// Rgb242424, + /// + /// RGB color image with 32 bits for each channel. + /// + Rgb323232, + /// /// RGB Full Color. Planar configuration of data. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 1162addee7..ceb2a3c94b 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -230,6 +230,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff ushort bitsPerChannel = options.BitsPerSample.Channel0; switch (bitsPerChannel) { + case 32: + options.ColorType = TiffColorType.Rgb323232; + break; + case 24: options.ColorType = TiffColorType.Rgb242424; break; diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index 95c4a542f2..f2872858cf 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -63,6 +63,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f); + color.FromVector4(colorVector); + return color; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ColorFromL16(L16 l16, ushort intensity, TPixel color) where TPixel : unmanaged, IPixel diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index ae02cc9889..19237b4b9e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -199,6 +199,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_72Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(FlowerRgb323232Contiguous, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_96Bit(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 9005c98fa6..bb22cbcc82 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -561,6 +561,7 @@ 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 FlowerRgb323232Contiguous = "Tiff/flower-rgb-contig-32.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"; diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-32.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-32.tiff new file mode 100644 index 0000000000..28461d8d8e --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-32.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7b9da8ec44da84fc89aed1ad221a5eb130a1f233a1ff8a4a15b41898a0e364f +size 38027 From 8c7ee589e635d8e7be9ccf0213d46eccb0f72155 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 4 Aug 2021 14:19:17 +0200 Subject: [PATCH 06/20] Add support for decoding 32bit per channel color tiff with planar pixel data --- .../Rgb32PlanarTiffColor{TPixel}.cs | 71 +++++++++++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 22 +++--- .../TiffColorType.cs | 19 ++++- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 17 ++++- .../Formats/Tiff/TiffDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Tiff/flower-rgb-planar-32.tiff | 3 + 7 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-rgb-planar-32.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs new file mode 100644 index 0000000000..a7432549ce --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +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 32 bit. + /// + internal class Rgb32PlanarTiffColor : 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 Rgb32PlanarTiffColor(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); + + 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++) + { + ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4)); + ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4)); + ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4)); + + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4)); + ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4)); + ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4)); + + offset += 4; + + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index a34b1ad3e0..9b3fb058ba 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -189,19 +189,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { switch (colorType) { - case TiffColorType.RgbPlanar: + case TiffColorType.Rgb888Planar: DebugGuard.IsTrue(colorMap == null, "colorMap"); - if (bitsPerSample.Channel0 == 16 && bitsPerSample.Channel1 == 16 && bitsPerSample.Channel2 == 16) - { - return new Rgb16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); - } + return new RgbPlanarTiffColor(bitsPerSample); - if (bitsPerSample.Channel0 == 24 && bitsPerSample.Channel1 == 24 && bitsPerSample.Channel2 == 24) - { - return new Rgb24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); - } + case TiffColorType.Rgb161616Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); - return new RgbPlanarTiffColor(bitsPerSample); + case TiffColorType.Rgb242424Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + + case TiffColorType.Rgb323232Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb32PlanarTiffColor(byteOrder == ByteOrder.BigEndian); 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 ffc5a81673..dc47dc8cd4 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -134,8 +134,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation Rgb323232, /// - /// RGB Full Color. Planar configuration of data. + /// RGB Full Color. Planar configuration of data. 8 Bit per color channel. /// - RgbPlanar, + Rgb888Planar, + + /// + /// RGB Full Color. Planar configuration of data. 16 Bit per color channel. + /// + Rgb161616Planar, + + /// + /// RGB Full Color. Planar configuration of data. 24 Bit per color channel. + /// + Rgb242424Planar, + + /// + /// RGB Full Color. Planar configuration of data. 32 Bit per color channel. + /// + Rgb323232Planar, } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index ceb2a3c94b..dadf8d707a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -270,7 +270,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - options.ColorType = TiffColorType.RgbPlanar; + ushort bitsPerChannel = options.BitsPerSample.Channel0; + switch (bitsPerChannel) + { + case 32: + options.ColorType = TiffColorType.Rgb323232Planar; + break; + case 24: + options.ColorType = TiffColorType.Rgb242424Planar; + break; + case 16: + options.ColorType = TiffColorType.Rgb161616Planar; + break; + default: + options.ColorType = TiffColorType.Rgb888Planar; + break; + } } break; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 19237b4b9e..8001701ff5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -201,6 +201,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(FlowerRgb323232Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb323232Planar, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_96Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index bb22cbcc82..c5dec8efcb 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -562,6 +562,7 @@ namespace SixLabors.ImageSharp.Tests public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; public const string FlowerRgb323232Contiguous = "Tiff/flower-rgb-contig-32.tiff"; + public const string FlowerRgb323232Planar = "Tiff/flower-rgb-planar-32.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"; diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-32.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-32.tiff new file mode 100644 index 0000000000..a84b4ab37f --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-planar-32.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a718ae37d6d7a5bb5702cc75350f6feec3e9cdcd7e22aaa4753c7fe9c2db9aae +size 38035 From 8f1e43a95d35f398a5166b6272df54f4c325f083 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 4 Aug 2021 16:06:12 +0200 Subject: [PATCH 07/20] Additional test images --- tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs | 4 ++++ tests/ImageSharp.Tests/TestImages.cs | 9 +++++++-- tests/Images/Input/Tiff/flower-minisblack-24.tiff | 2 +- tests/Images/Input/Tiff/flower-minisblack-24_lsb.tiff | 3 +++ tests/Images/Input/Tiff/flower-minisblack-32_lsb.tiff | 3 +++ tests/Images/Input/Tiff/flower-rgb-contig-32_lsb.tiff | 3 +++ tests/Images/Input/Tiff/flower-rgb-planar-32_lsb.tiff | 3 +++ 7 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/Images/Input/Tiff/flower-minisblack-24_lsb.tiff create mode 100644 tests/Images/Input/Tiff/flower-minisblack-32_lsb.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-32_lsb.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-planar-32_lsb.tiff diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 8001701ff5..9b6c40285f 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -157,6 +157,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(Flower24BitGray, PixelTypes.Rgba32)] + [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_24Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); @@ -168,6 +169,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(Flower32BitGray, PixelTypes.Rgba32)] + [WithFile(Flower32BitGrayLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_32Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); @@ -201,7 +203,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(FlowerRgb323232Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb323232ContiguousLittleEndian, PixelTypes.Rgba32)] [WithFile(FlowerRgb323232Planar, PixelTypes.Rgba32)] + [WithFile(FlowerRgb323232PlanarLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_96Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index c5dec8efcb..a8c42113fe 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -562,7 +562,9 @@ namespace SixLabors.ImageSharp.Tests public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; public const string FlowerRgb323232Contiguous = "Tiff/flower-rgb-contig-32.tiff"; + public const string FlowerRgb323232ContiguousLittleEndian = "Tiff/flower-rgb-contig-32_lsb.tiff"; public const string FlowerRgb323232Planar = "Tiff/flower-rgb-planar-32.tiff"; + public const string FlowerRgb323232PlanarLittleEndian = "Tiff/flower-rgb-planar-32_lsb.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"; @@ -590,11 +592,14 @@ namespace SixLabors.ImageSharp.Tests public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff"; public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff"; public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff"; - public const string Flower24BitGray = "Tiff/flower-minisblack-24.tiff"; - public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff"; public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff"; public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff"; public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16.tiff"; + public const string Flower24BitGray = "Tiff/flower-minisblack-24.tiff"; + public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff"; + public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff"; + public const string Flower32BitGrayLittleEndian = "Tiff/flower-minisblack-32_lsb.tiff"; + public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; diff --git a/tests/Images/Input/Tiff/flower-minisblack-24.tiff b/tests/Images/Input/Tiff/flower-minisblack-24.tiff index 7f9dd009d6..1fddb22e34 100644 --- a/tests/Images/Input/Tiff/flower-minisblack-24.tiff +++ b/tests/Images/Input/Tiff/flower-minisblack-24.tiff @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe2d4e0d99bdfade966e27bd9583bce39bebb90efa8e7f768ce3cec69aa306e2 +oid sha256:6b5a96942ee27a2b25d3cbb8bdd05239be71f84acc4d63c95380841a8a67befd size 9770 diff --git a/tests/Images/Input/Tiff/flower-minisblack-24_lsb.tiff b/tests/Images/Input/Tiff/flower-minisblack-24_lsb.tiff new file mode 100644 index 0000000000..7f9dd009d6 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-24_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe2d4e0d99bdfade966e27bd9583bce39bebb90efa8e7f768ce3cec69aa306e2 +size 9770 diff --git a/tests/Images/Input/Tiff/flower-minisblack-32_lsb.tiff b/tests/Images/Input/Tiff/flower-minisblack-32_lsb.tiff new file mode 100644 index 0000000000..cc3be01d21 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-32_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e37a4455e6b61e32720af99127b82aacdc907be91b8ed1d8e1a1f06d6a853211 +size 12885 diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb.tiff new file mode 100644 index 0000000000..c602b5c4a9 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-32_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0876580f9c5d8e13656210582137104daba137c99d55eafb5ebbfa418efa6525 +size 38027 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-32_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-32_lsb.tiff new file mode 100644 index 0000000000..5caa0886ec --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-planar-32_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2241683d74e9a52c5077870731e7bd5a7e7558c2a04fd0edf57da3a583044442 +size 38035 From f20a16593c14c59081ae56f156f6be61f06c4497 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 4 Aug 2021 16:27:30 +0200 Subject: [PATCH 08/20] Only DebugSave testimages with bit depth >= 24 --- .../Formats/Tiff/TiffDecoderTests.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 9b6c40285f..b0b2a6b564 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -159,7 +159,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(Flower24BitGray, PixelTypes.Rgba32)] [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_24Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } [Theory] [WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)] @@ -171,7 +176,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(Flower32BitGray, PixelTypes.Rgba32)] [WithFile(Flower32BitGrayLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_32Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } [Theory] [WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)] @@ -199,7 +209,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(FlowerRgb242424Planar, PixelTypes.Rgba32)] [WithFile(FlowerRgb242424PlanarLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_72Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } [Theory] [WithFile(FlowerRgb323232Contiguous, PixelTypes.Rgba32)] @@ -207,7 +222,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(FlowerRgb323232Planar, PixelTypes.Rgba32)] [WithFile(FlowerRgb323232PlanarLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_96Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } [Theory] [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] From 9b1276d2dae3cedd5dcc52395298f74f18a1b141 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 4 Aug 2021 18:04:42 +0200 Subject: [PATCH 09/20] Add test images for miniswhite 32 bit --- tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs | 2 ++ tests/ImageSharp.Tests/TestImages.cs | 2 ++ tests/Images/Input/Tiff/flower-miniswhite-32.tiff | 3 +++ tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff | 3 +++ 4 files changed, 10 insertions(+) create mode 100644 tests/Images/Input/Tiff/flower-miniswhite-32.tiff create mode 100644 tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index b0b2a6b564..504892ca2e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -175,6 +175,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(Flower32BitGray, PixelTypes.Rgba32)] [WithFile(Flower32BitGrayLittleEndian, PixelTypes.Rgba32)] + [WithFile(Flower32BitGrayMinIsWhite, PixelTypes.Rgba32)] + [WithFile(Flower32BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_32Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a8c42113fe..ad2dc1f59d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -599,6 +599,8 @@ namespace SixLabors.ImageSharp.Tests public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff"; public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff"; public const string Flower32BitGrayLittleEndian = "Tiff/flower-minisblack-32_lsb.tiff"; + public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32.tiff"; + public const string Flower32BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-32_lsb.tiff"; public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; diff --git a/tests/Images/Input/Tiff/flower-miniswhite-32.tiff b/tests/Images/Input/Tiff/flower-miniswhite-32.tiff new file mode 100644 index 0000000000..f8cf87553e --- /dev/null +++ b/tests/Images/Input/Tiff/flower-miniswhite-32.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:514417ead3d6c5c6ca33374ef0bb6ecbe5f875a266519d4cbaa4a6b91033d243 +size 12778 diff --git a/tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff new file mode 100644 index 0000000000..8c99dda7fb --- /dev/null +++ b/tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64c948aa03bc4a24cd1d68bb18b5031c119936154a90f1cb1d9aaabd854c5d9b +size 12778 From 4eb4e540125f64a1fd7747b4af3598c8897e8dd7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 4 Aug 2021 20:38:43 +0200 Subject: [PATCH 10/20] Add support for decoding tiff's encoded with LeastSignificantBitFirst compression --- .../ModifiedHuffmanTiffCompression.cs | 7 +++-- .../Compression/Decompressors/T4BitReader.cs | 29 ++++++++++++++++--- .../Decompressors/T4TiffCompression.cs | 13 +++++++-- .../Compression/TiffDecompressorsFactory.cs | 7 +++-- .../Formats/Tiff/TiffDecoderCore.cs | 18 ++++++++++-- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 5 ++-- .../Formats/Tiff/TiffDecoderTests.cs | 6 ++++ tests/ImageSharp.Tests/TestImages.cs | 3 ++ ...i3p02_huffman_rle_lowerOrderBitsFirst.tiff | 3 ++ .../f8179f8f5e566349cf3583a1ff3ea95c.tiff | 3 ++ tests/Images/Input/Tiff/g3test.tiff | 3 ++ 11 files changed, 80 insertions(+), 17 deletions(-) create mode 100644 tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff create mode 100644 tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff create mode 100644 tests/Images/Input/Tiff/g3test.tiff diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs index 017591e53f..9b12dc90ff 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs @@ -22,11 +22,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// Initializes a new instance of the class. /// /// The memory allocator. + /// The logical order of bits within a byte. /// The image width. /// The number of bits per pixel. /// The photometric interpretation. - public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) - : base(allocator, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation) + public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) + : base(allocator, fillOrder, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation) { bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; this.whiteValue = (byte)(isWhiteZero ? 0 : 1); @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) { - using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true); + using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true); buffer.Clear(); uint bitsWritten = 0; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs index 09f8c71f72..384be1cf24 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs @@ -5,7 +5,8 @@ using System; using System.Buffers; using System.Collections.Generic; using System.IO; - +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors @@ -20,6 +21,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// private int bitsRead; + /// + /// The logical order of bits within a byte. + /// + private readonly TiffFillOrder fillOrder; + /// /// Current value. /// @@ -221,12 +227,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// Initializes a new instance of the class. /// /// The compressed input stream. + /// The logical order of bits within a byte. /// The number of bytes to read from the stream. /// The memory allocator. /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. /// Indicates, if its the modified huffman code variation. Defaults to false. - public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false) + public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false) { + this.fillOrder = fillOrder; this.Data = allocator.Allocate(bytesToRead); this.ReadImageDataFromStream(input, bytesToRead); @@ -375,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors break; } - var currBit = this.ReadValue(1); + uint currBit = this.ReadValue(1); this.value = (this.value << 1) | currBit; if (this.IsEndOfScanLine) @@ -816,7 +824,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors Span dataSpan = this.Data.GetSpan(); int shift = 8 - this.bitsRead - 1; - var bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0); + uint bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0); this.bitsRead++; return bit; @@ -837,6 +845,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { Span dataSpan = this.Data.GetSpan(); input.Read(dataSpan, 0, bytesToRead); + + if (this.fillOrder == TiffFillOrder.LeastSignificantBitFirst) + { + for (int i = 0; i < dataSpan.Length; i++) + { + dataSpan[i] = ReverseBits(dataSpan[i]); + } + } } + + // http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte ReverseBits(byte b) => + (byte)((((b * 0x80200802UL) & 0x0884422110UL) * 0x0101010101UL) >> 32); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index 76f0883643..d95fea29bf 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -24,20 +24,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// Initializes a new instance of the class. /// /// The memory allocator. + /// The logical order of bits within a byte. /// The image width. /// The number of bits per pixel. /// Fax compression options. /// The photometric interpretation. - public T4TiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation) + public T4TiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation) : base(allocator, width, bitsPerPixel) { this.faxCompressionOptions = faxOptions; + this.FillOrder = fillOrder; bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; this.whiteValue = (byte)(isWhiteZero ? 0 : 1); this.blackValue = (byte)(isWhiteZero ? 1 : 0); } + /// + /// Gets the logical order of bits within a byte. + /// + protected TiffFillOrder FillOrder { get; } + /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) { @@ -46,8 +53,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported"); } - var eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); - using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding); + bool eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); + using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding); buffer.Clear(); uint bitsWritten = 0; diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index a6d44f4d3c..ff04edab70 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -16,7 +16,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression int width, int bitsPerPixel, TiffPredictor predictor, - FaxCompressionOptions faxOptions) + FaxCompressionOptions faxOptions, + TiffFillOrder fillOrder) { switch (method) { @@ -40,11 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression case TiffDecoderCompressionType.T4: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T4TiffCompression(allocator, width, bitsPerPixel, faxOptions, photometricInterpretation); + return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation); case TiffDecoderCompressionType.HuffmanRle: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new ModifiedHuffmanTiffCompression(allocator, width, bitsPerPixel, photometricInterpretation); + return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); default: throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 011d037796..9fb8c6cebd 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -85,6 +85,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public FaxCompressionOptions FaxCompressionOptions { get; set; } + /// + /// Gets or sets the the logical order of bits within a byte. + /// + public TiffFillOrder FillOrder { get; set; } + /// /// Gets or sets the planar configuration type to use when decoding the image. /// @@ -264,7 +269,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff stripBuffers[stripIndex] = this.memoryAllocator.Allocate(uncompressedStripSize); } - using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); + using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( + this.CompressionType, + this.memoryAllocator, + this.PhotometricInterpretation, + frame.Width, + bitsPerPixel, + this.Predictor, + this.FaxCompressionOptions, + this.FillOrder); TiffBasePlanarColorDecoder colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); @@ -314,7 +327,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff frame.Width, bitsPerPixel, this.Predictor, - this.FaxCompressionOptions); + this.FaxCompressionOptions, + this.FillOrder); TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 14c527a34c..5496b32bfd 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -35,9 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff } TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; - if (fillOrder != TiffFillOrder.MostSignificantBitFirst) + if (fillOrder == TiffFillOrder.LeastSignificantBitFirst && frameMetadata.BitsPerPixel != TiffBitsPerPixel.Bit1) { - TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is not supported."); + TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is only supported in combination with 1bit per pixel bicolor tiff's."); } if (frameMetadata.Predictor == TiffPredictor.FloatingPoint) @@ -69,6 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0); + options.FillOrder = fillOrder; options.ParseColorType(exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 67892b14b1..cae1597a50 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -221,6 +221,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_Fax3Compressed(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(CcittFax3LowerOrderBitsFirst01, PixelTypes.Rgba32)] + [WithFile(CcittFax3LowerOrderBitsFirst02, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(Calliphora_RgbPackbits, PixelTypes.Rgba32)] [WithFile(RgbPackbits, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 929e375246..d24c32b55e 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -539,6 +539,9 @@ namespace SixLabors.ImageSharp.Tests public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff"; public const string HuffmanRleAllTermCodes = "Tiff/huffman_rle_all_terminating_codes.tiff"; public const string HuffmanRleAllMakeupCodes = "Tiff/huffman_rle_all_makeup_codes.tiff"; + public const string CcittFax3LowerOrderBitsFirst01 = "Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff"; + public const string CcittFax3LowerOrderBitsFirst02 = "Tiff/g3test.tiff"; + public const string HuffmanRleLowerOrderBitsFirst = "Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff"; // Test case for an issue, that the last bits in a row got ignored. public const string HuffmanRle_basi3p02 = "Tiff/basi3p02_huffman_rle.tiff"; diff --git a/tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff b/tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff new file mode 100644 index 0000000000..6ab0603246 --- /dev/null +++ b/tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ac3e56a93996464a579ae19cf5f8d9531e2f08db36879aaba176731c24951a5 +size 352 diff --git a/tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff b/tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff new file mode 100644 index 0000000000..9dc10018e4 --- /dev/null +++ b/tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf75c4b679d2449e239f228cdee6a25adc7d7b16dde3fb9061a07b2fb0699db1 +size 735412 diff --git a/tests/Images/Input/Tiff/g3test.tiff b/tests/Images/Input/Tiff/g3test.tiff new file mode 100644 index 0000000000..62207de3ad --- /dev/null +++ b/tests/Images/Input/Tiff/g3test.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5b2e1a17338133aa95cb8a16d82a171f5b50f7b9ae1a51ab06227dc3daa81d5 +size 50401 From 4439c3804229f534e0d416cede087484719c18f7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 4 Aug 2021 21:26:39 +0200 Subject: [PATCH 11/20] Update readme --- src/ImageSharp/Formats/Tiff/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 5b116b819a..7b7c0efd53 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -25,7 +25,7 @@ ## Implementation Status -- The Decoder and Encoder currently only supports a single frame per image. +- The Decoder currently only supports a single frame per image. - Some compression formats are not yet supported. See the list below. ### Deviations from the TIFF spec (to be fixed) @@ -81,7 +81,7 @@ |Thresholding | | | | |CellWidth | | | | |CellLength | | | | -|FillOrder | | - | Ignore. In practice is very uncommon, and is not recommended. | +|FillOrder | | Y | | |ImageDescription | Y | Y | | |Make | Y | Y | | |Model | Y | Y | | From af439841b8b37c1f7df772ad8199c4a646194b27 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 10:33:34 +0200 Subject: [PATCH 12/20] Run ArrayPoolMemoryAllocatorTests serial --- .../Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs index 50ec09ce3f..7620d63deb 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs @@ -11,6 +11,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Memory.Allocators { + [Collection("RunSerial")] public class ArrayPoolMemoryAllocatorTests { private const int MaxPooledBufferSizeInBytes = 2048; @@ -56,19 +57,14 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [Fact] public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown() - { - Assert.ThrowsAny(() => new ArrayPoolMemoryAllocator(100, 200)); - } + => Assert.ThrowsAny(() => new ArrayPoolMemoryAllocator(100, 200)); } [Theory] [InlineData(32)] [InlineData(512)] [InlineData(MaxPooledBufferSizeInBytes - 1)] - public void SmallBuffersArePooled_OfByte(int size) - { - Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(size)); - } + public void SmallBuffersArePooled_OfByte(int size) => Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(size)); [Theory] [InlineData(128 * 1024 * 1024)] From c61ce85fb7eb4959d747dd55650836e80f04c605 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 11:11:06 +0200 Subject: [PATCH 13/20] Throw exception if bits per channel for rgb are not equal --- src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index dadf8d707a..8590203a70 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -220,11 +220,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffPhotometricInterpretation.Rgb: { - if (options.BitsPerSample.Channels != 3) + TiffBitsPerSample bitsPerSample = options.BitsPerSample; + if (bitsPerSample.Channels != 3) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } + if (!(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2)) + { + TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported."); + } + if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) { ushort bitsPerChannel = options.BitsPerSample.Channel0; From 4e5dec9f4f75f779456d43134729b73f2213acee Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 12:47:03 +0200 Subject: [PATCH 14/20] Introduce variable for buffer.AsSpan(bufferStartIdx) --- .../BlackIsZero24TiffColor{TPixel}.cs | 5 +++-- .../Rgb242424TiffColor{TPixel}.cs | 13 +++++++------ .../Rgb24PlanarTiffColor{TPixel}.cs | 13 +++++++------ .../WhiteIsZero24TiffColor{TPixel}.cs | 5 +++-- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs index 813d7beb57..ec07abd5c4 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs @@ -32,6 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation byte[] buffer = new byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; + Span bufferSpan = buffer.AsSpan(bufferStartIdx); int offset = 0; for (int y = top; y < top + height; y++) { @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + data.Slice(offset, 3).CopyTo(bufferSpan); ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer); offset += 3; @@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + data.Slice(offset, 3).CopyTo(bufferSpan); ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer); offset += 3; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs index abc3a82a94..3be0540a03 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs @@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation byte[] buffer = new byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; + Span bufferSpan = buffer.AsSpan(bufferStartIdx); for (int y = top; y < top + height; y++) { Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); @@ -41,15 +42,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + data.Slice(offset, 3).CopyTo(bufferSpan); ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); offset += 3; - data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + data.Slice(offset, 3).CopyTo(bufferSpan); ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); offset += 3; - data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + data.Slice(offset, 3).CopyTo(bufferSpan); ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); offset += 3; @@ -60,15 +61,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + data.Slice(offset, 3).CopyTo(bufferSpan); ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); offset += 3; - data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + data.Slice(offset, 3).CopyTo(bufferSpan); ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); offset += 3; - data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + data.Slice(offset, 3).CopyTo(bufferSpan); ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); offset += 3; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs index cd94f8e817..9c3e57e2a4 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs @@ -36,6 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation Span redData = data[0].GetSpan(); Span greenData = data[1].GetSpan(); Span blueData = data[2].GetSpan(); + Span bufferSpan = buffer.AsSpan(bufferStartIdx); int offset = 0; for (int y = top; y < top + height; y++) @@ -45,11 +46,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - redData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + redData.Slice(offset, 3).CopyTo(bufferSpan); ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); - greenData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + greenData.Slice(offset, 3).CopyTo(bufferSpan); ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); - blueData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + blueData.Slice(offset, 3).CopyTo(bufferSpan); ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); offset += 3; @@ -61,11 +62,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - redData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + redData.Slice(offset, 3).CopyTo(bufferSpan); ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); - greenData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + greenData.Slice(offset, 3).CopyTo(bufferSpan); ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); - blueData.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + blueData.Slice(offset, 3).CopyTo(bufferSpan); ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); offset += 3; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs index 143a684c27..b1088732ba 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs @@ -32,6 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation byte[] buffer = new byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; + Span bufferSpan = buffer.AsSpan(bufferStartIdx); int offset = 0; for (int y = top; y < top + height; y++) { @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + data.Slice(offset, 3).CopyTo(bufferSpan); ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer); offset += 3; @@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { for (int x = 0; x < pixelRow.Length; x++) { - data.Slice(offset, 3).CopyTo(buffer.AsSpan(bufferStartIdx)); + data.Slice(offset, 3).CopyTo(bufferSpan); ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer); offset += 3; From 928db20b1fee312103117a71383aee029b712280 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 18:46:04 +0200 Subject: [PATCH 15/20] Use fax3 test image with multiple strips --- tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff b/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff index 39852d5345..d2761d2919 100644 --- a/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff +++ b/tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b9b105857723bca5f478a9ab23c0aeca93abe863781019bbd2da47f18c46f24 -size 125778 +oid sha256:bba35f1e43c8425f3bcfab682efae4d2c00c62f0d8a4b411e646d32047469526 +size 125802 From eb63882d4e77574cdbc63962d7b2fe9b84ee1ebf Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 20:19:05 +0200 Subject: [PATCH 16/20] Handle edge case when we are at the last byte position, but not all pixels have been written --- .../Decompressors/T4TiffCompression.cs | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index d95fea29bf..a79ef3fe5e 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -20,6 +20,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors private readonly byte blackValue; + private readonly int width; + /// /// Initializes a new instance of the class. /// @@ -34,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { this.faxCompressionOptions = faxOptions; this.FillOrder = fillOrder; - + this.width = width; bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; this.whiteValue = (byte)(isWhiteZero ? 0 : 1); this.blackValue = (byte)(isWhiteZero ? 1 : 0); @@ -58,22 +60,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors buffer.Clear(); uint bitsWritten = 0; + uint pixelWritten = 0; while (bitReader.HasMoreData) { bitReader.ReadNextRun(); if (bitReader.RunLength > 0) { - if (bitReader.IsWhiteRun) - { - BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); - bitsWritten += bitReader.RunLength; - } - else - { - BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); - bitsWritten += bitReader.RunLength; - } + this.WritePixelRun(buffer, bitReader, bitsWritten); + + bitsWritten += bitReader.RunLength; + pixelWritten += bitReader.RunLength; } if (bitReader.IsEndOfScanLine) @@ -85,8 +82,29 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); bitsWritten += pad; } + + pixelWritten = 0; } } + + // Edge case for when we are at the last byte, but there are still some unwritten pixels left. + if (pixelWritten > 0 && pixelWritten < this.width) + { + bitReader.ReadNextRun(); + this.WritePixelRun(buffer, bitReader, bitsWritten); + } + } + + private void WritePixelRun(Span buffer, T4BitReader bitReader, uint bitsWritten) + { + if (bitReader.IsWhiteRun) + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); + } + else + { + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); + } } /// From ebfb1b5148b4a60175107b2535e1d813a4972532 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 20:19:36 +0200 Subject: [PATCH 17/20] Use smaller test images --- tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs | 3 +-- tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs | 6 +++++- tests/ImageSharp.Tests/TestImages.cs | 6 ++---- tests/Images/Input/Tiff/b0350_fillorder2.tiff | 3 --- .../Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff | 3 +++ 5 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 tests/Images/Input/Tiff/b0350_fillorder2.tiff create mode 100644 tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index cae1597a50..6d0f65168e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -222,8 +222,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); [Theory] - [WithFile(CcittFax3LowerOrderBitsFirst01, PixelTypes.Rgba32)] - [WithFile(CcittFax3LowerOrderBitsFirst02, PixelTypes.Rgba32)] + [WithFile(CcittFax3LowerOrderBitsFirst, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 47b6fcf727..712c8502ab 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -117,7 +117,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] - public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffBitsPerPixel expectedBitsPerPixel, TiffCompression expectedCompression) + public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works( + TiffPhotometricInterpretation? photometricInterpretation, + TiffCompression compression, + TiffBitsPerPixel expectedBitsPerPixel, + TiffCompression expectedCompression) { // arrange var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression }; diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index d24c32b55e..4233c69e05 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -539,8 +539,7 @@ namespace SixLabors.ImageSharp.Tests public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff"; public const string HuffmanRleAllTermCodes = "Tiff/huffman_rle_all_terminating_codes.tiff"; public const string HuffmanRleAllMakeupCodes = "Tiff/huffman_rle_all_makeup_codes.tiff"; - public const string CcittFax3LowerOrderBitsFirst01 = "Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff"; - public const string CcittFax3LowerOrderBitsFirst02 = "Tiff/g3test.tiff"; + public const string CcittFax3LowerOrderBitsFirst = "Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff"; public const string HuffmanRleLowerOrderBitsFirst = "Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff"; // Test case for an issue, that the last bits in a row got ignored. @@ -607,7 +606,6 @@ namespace SixLabors.ImageSharp.Tests public const string MultiframeDifferentSize = "Tiff/multipage_differentSize.tiff"; public const string MultiframeDifferentVariants = "Tiff/multipage_differentVariants.tiff"; - public const string FillOrder2 = "Tiff/b0350_fillorder2.tiff"; public const string LittleEndianByteOrder = "Tiff/little_endian.tiff"; public const string Fax4_Motorola = "Tiff/moy.tiff"; @@ -622,7 +620,7 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] Metadata = { SampleMetadata }; - public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbJpeg, RgbUncompressedTiled, MultiframeDifferentSize, MultiframeDifferentVariants, FillOrder2, Calliphora_Fax4Compressed, Fax4_Motorola }; + public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbJpeg, RgbUncompressedTiled, MultiframeDifferentSize, MultiframeDifferentVariants, Calliphora_Fax4Compressed, Fax4_Motorola }; } } } diff --git a/tests/Images/Input/Tiff/b0350_fillorder2.tiff b/tests/Images/Input/Tiff/b0350_fillorder2.tiff deleted file mode 100644 index 3b7ee6ac3a..0000000000 --- a/tests/Images/Input/Tiff/b0350_fillorder2.tiff +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37c6a28f460d8781fdc3bcf0cc9bd23f633b03899563546bfc6234a8478f67f0 -size 68637 diff --git a/tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff b/tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff new file mode 100644 index 0000000000..d9b5a5bfb7 --- /dev/null +++ b/tests/Images/Input/Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb56b3582c5c7d91d712e68181110ab0bf74d21992030629f05803c420b7b483 +size 388 From 0559f464dd60b19515dd764fff1f950da36b41da Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 5 Aug 2021 22:31:13 +0200 Subject: [PATCH 18/20] Use smaller test image --- tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff index 5574bd58ed..6f929f3e1d 100644 --- a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff +++ b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:622d69dba0a8a67aa3b87e384a2b9ea8d29689eaa5cb5d0eee857f98ed660517 -size 15154924 +oid sha256:e4a6c925dd6d293c5d97aac01cdb0ab3f2fb4bbfa4bb1cbe6463545295f5c5fb +size 207979 From 1d8b9101f8ac5403e548335576a3d9e5519b715d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 6 Aug 2021 11:55:47 +0200 Subject: [PATCH 19/20] Restore broken IPTC data for testimage (which got lost during resize) --- tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff index 6f929f3e1d..24a4141f56 100644 --- a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff +++ b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4a6c925dd6d293c5d97aac01cdb0ab3f2fb4bbfa4bb1cbe6463545295f5c5fb -size 207979 +oid sha256:579db6b2bd34566846de992f255c6b341d0f88d957a0eb02b01caad3f20c5b44 +size 78794 From 01202b62c381be6bf1724601d0ebf4c1673c38fa Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 6 Aug 2021 14:39:58 +0200 Subject: [PATCH 20/20] Use bulk pixel conversion for some tiff color decoders --- .../BlackIsZero16TiffColor{TPixel}.cs | 22 ++++++++++----- .../BlackIsZero8TiffColor{TPixel}.cs | 21 ++++++++------ .../Rgb161616TiffColor{TPixel}.cs | 26 +++++++++-------- .../Rgb888TiffColor{TPixel}.cs | 28 ++++++++----------- .../TiffColorDecoderFactory{TPixel}.cs | 10 +++---- .../Formats/Tiff/TiffDecoderCore.cs | 2 +- .../BlackIsZeroTiffColorTests.cs | 2 +- .../RgbTiffColorTests.cs | 2 +- 8 files changed, 62 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index a31ff7a9fd..1cddc9b63d 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -16,11 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { private readonly bool isBigEndian; + private readonly Configuration configuration; + /// /// Initializes a new instance of the class. /// + /// The configuration. /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public BlackIsZero16TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + public BlackIsZero16TiffColor(Configuration configuration, bool isBigEndian) + { + this.configuration = configuration; + this.isBigEndian = isBigEndian; + } /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) @@ -47,13 +54,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation } else { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort intensity = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); - offset += 2; + int byteCount = pixelRow.Length * 2; + PixelOperations.Instance.FromL16Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); - pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); - } + offset += byteCount; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs index ea2608f6fd..f62cf29528 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -14,22 +13,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation internal class BlackIsZero8TiffColor : TiffBaseColorDecoder where TPixel : unmanaged, IPixel { + private readonly Configuration configuration; + + public BlackIsZero8TiffColor(Configuration configuration) => this.configuration = configuration; + /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { - var color = default(TPixel); - int offset = 0; - var l8 = default(L8); for (int y = top; y < top + height; y++) { Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - byte intensity = data[offset++]; - pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color); - } + int byteCount = pixelRow.Length; + PixelOperations.Instance.FromL8Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); + + offset += byteCount; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index 4b34d5a0db..e0d558a149 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -16,11 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation { private readonly bool isBigEndian; + private readonly Configuration configuration; + /// /// Initializes a new instance of the class. /// + /// The configuration. /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb161616TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + public Rgb161616TiffColor(Configuration configuration, bool isBigEndian) + { + this.configuration = configuration; + this.isBigEndian = isBigEndian; + } /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) @@ -53,17 +60,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation } else { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); - offset += 2; - ulong g = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); - offset += 2; - ulong b = TiffUtils.ConvertToShortLittleEndian(data.Slice(offset, 2)); - offset += 2; + int byteCount = pixelRow.Length * 6; + PixelOperations.Instance.FromRgb48Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); - pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); - } + offset += byteCount; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs index 536dece8e3..2a86eb2ee9 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -14,29 +13,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation internal class Rgb888TiffColor : TiffBaseColorDecoder where TPixel : unmanaged, IPixel { + private readonly Configuration configuration; + + public Rgb888TiffColor(Configuration configuration) => this.configuration = configuration; + /// public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { - var color = default(TPixel); - int offset = 0; - var rgba = default(Rgba32); for (int y = top; y < top + height; y++) { Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); - - for (int x = 0; x < pixelRow.Length; x++) - { - byte r = data[offset++]; - byte g = data[offset++]; - byte b = data[offset++]; - - rgba.PackedValue = (uint)(r | (g << 8) | (b << 16) | (0xff << 24)); - color.FromRgba32(rgba); - - pixelRow[x] = color; - } + int byteCount = pixelRow.Length * 3; + PixelOperations.Instance.FromRgb24Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); + + offset += byteCount; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 0c93998c48..97d3d86655 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation internal static class TiffColorDecoderFactory where TPixel : unmanaged, IPixel { - public static TiffBaseColorDecoder Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder) + public static TiffBaseColorDecoder Create(Configuration configuration, TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap, ByteOrder byteOrder) { switch (colorType) { @@ -55,12 +55,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation case TiffColorType.BlackIsZero8: DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero8TiffColor(); + return new BlackIsZero8TiffColor(configuration); case TiffColorType.BlackIsZero16: DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero16TiffColor(byteOrder == ByteOrder.BigEndian); + return new BlackIsZero16TiffColor(configuration, byteOrder == ByteOrder.BigEndian); case TiffColorType.Rgb: DebugGuard.IsTrue(colorMap == null, "colorMap"); @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 8, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb888TiffColor(); + return new Rgb888TiffColor(configuration); case TiffColorType.Rgb101010: DebugGuard.IsTrue( @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation && bitsPerSample.Channel0 == 16, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb161616TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + return new Rgb161616TiffColor(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); case TiffColorType.PaletteColor: DebugGuard.NotNull(colorMap, "colorMap"); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 011d037796..63185619d4 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -316,7 +316,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.Predictor, this.FaxCompressionOptions); - TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); + TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.Configuration, this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs index 769ab850e4..38611c6f37 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs @@ -188,7 +188,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation { AssertDecode(expectedResult, pixels => { - new BlackIsZero8TiffColor().Decode(inputData, pixels, left, top, width, height); + new BlackIsZero8TiffColor(Configuration.Default).Decode(inputData, pixels, left, top, width, height); }); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs index 9adf59e484..f9f6331063 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs @@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation { AssertDecode(expectedResult, pixels => { - new Rgb888TiffColor().Decode(inputData, pixels, left, top, width, height); + new Rgb888TiffColor(Configuration.Default).Decode(inputData, pixels, left, top, width, height); }); } }