From 9d6b7a6204a146c32137df9db7076e2d43eef127 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 6 Aug 2021 20:34:01 +0200 Subject: [PATCH] Add support for decoding tiff's with 32bit float gray pixel data with min is black --- .../BlackIsZero32FloatTiffColor{TPixel}.cs | 69 +++++++++++++++++++ .../BlackIsZero32TiffColor{TPixel}.cs | 1 - .../RgbFloat323232TiffColor{TPixel}.cs | 1 - .../TiffColorDecoderFactory{TPixel}.cs | 5 ++ .../TiffColorType.cs | 5 ++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 18 +++-- .../Formats/Tiff/TiffDecoderTests.cs | 11 +++ tests/ImageSharp.Tests/TestImages.cs | 2 + .../Tiff/flower-minisblack-float32_lsb.tiff | 3 + .../Tiff/flower-minisblack-float32_msb.tiff | 3 + 10 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-minisblack-float32_lsb.tiff create mode 100644 tests/Images/Input/Tiff/flower-minisblack-float32_msb.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs new file mode 100644 index 0000000000..ff34a29eb2 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs @@ -0,0 +1,69 @@ +// 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 float grayscale images. + /// + internal class BlackIsZero32FloatTiffColor : 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 BlackIsZero32FloatTiffColor(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 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, 4).CopyTo(buffer); + Array.Reverse(buffer); + float intensity = BitConverter.ToSingle(buffer, 0); + offset += 4; + + var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); + color.FromVector4(colorVector); + pixelRow[x] = color; + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 4).CopyTo(buffer); + float intensity = BitConverter.ToSingle(buffer, 0); + offset += 4; + + var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); + color.FromVector4(colorVector); + pixelRow[x] = color; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs index 862756bc42..f54a794840 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{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/RgbFloat323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs index 739d1059c9..f3f27d5c4b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs @@ -57,7 +57,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation offset += 4; var colorVector = new Vector4(r, g, b, 1.0f); - Array.Reverse(buffer); 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 e1c813027d..f3cc4c01c8 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -82,6 +82,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new BlackIsZero32TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.BlackIsZero32Float: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero32FloatTiffColor(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 07959056c5..0d742c2b95 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -43,6 +43,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// BlackIsZero32, + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Pixel data is 32-bit float. + /// + BlackIsZero32Float, + /// /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 8449eb7b2e..79c4d372d5 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -177,6 +177,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff { case 32: { + if (options.SampleFormat == TiffSampleFormat.Float) + { + options.ColorType = TiffColorType.BlackIsZero32Float; + return; + } + options.ColorType = TiffColorType.BlackIsZero32; break; } @@ -234,18 +240,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported."); } - if (options.SampleFormat == TiffSampleFormat.Float) - { - options.ColorType = TiffColorType.RgbFloat323232; - return; - } - if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) { ushort bitsPerChannel = options.BitsPerSample.Channel0; switch (bitsPerChannel) { case 32: + if (options.SampleFormat == TiffSampleFormat.Float) + { + options.ColorType = TiffColorType.RgbFloat323232; + return; + } + options.ColorType = TiffColorType.Rgb323232; break; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 9839350382..e45b994a62 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -248,6 +248,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff image.DebugSave(provider); } + [Theory] + [WithFile(Flower32BitFloatGray, PixelTypes.Rgba32)] + [WithFile(Flower32BitFloatGrayLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Float_96Bit_Gray(TestImageProvider 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)] [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 74fde6a56a..eb83062d95 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -605,6 +605,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 Flower32BitFloatGray = "Tiff/flower-minisblack-float32_msb.tiff"; + public const string Flower32BitFloatGrayLittleEndian = "Tiff/flower-minisblack-float32_lsb.tiff"; public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32.tiff"; public const string Flower32BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-32_lsb.tiff"; diff --git a/tests/Images/Input/Tiff/flower-minisblack-float32_lsb.tiff b/tests/Images/Input/Tiff/flower-minisblack-float32_lsb.tiff new file mode 100644 index 0000000000..8e94faea0b --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-float32_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af82e07e6298082c91d60a97acb29b67ecabf386bc14371fcb698b248cfd3b40 +size 12814 diff --git a/tests/Images/Input/Tiff/flower-minisblack-float32_msb.tiff b/tests/Images/Input/Tiff/flower-minisblack-float32_msb.tiff new file mode 100644 index 0000000000..464074c153 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-float32_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c918b8aa9968c03c12d85b3bcacd35ae57663a19f5490fc1c351521ed835f30 +size 12814