From 3d02cfdbf05feb6a16f2d45cad127587dca4fa47 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 6 Aug 2021 20:09:29 +0200 Subject: [PATCH 1/4] Add support for decoding tiff's with 32bit float rgb pixel data --- .../RgbFloat323232TiffColor{TPixel}.cs | 89 +++++++++++++++++++ .../TiffColorDecoderFactory{TPixel}.cs | 10 +++ .../TiffColorType.cs | 5 ++ .../Formats/Tiff/TiffDecoderCore.cs | 5 ++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 19 ++-- .../Formats/Tiff/TiffDecoderTests.cs | 11 +++ tests/ImageSharp.Tests/TestImages.cs | 2 + .../Input/Tiff/flower-rgb-float32_lsb.tiff | 3 + .../Input/Tiff/flower-rgb-float32_msb.tiff | 3 + 9 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-rgb-float32_lsb.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-float32_msb.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs new file mode 100644 index 0000000000..739d1059c9 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{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 32 bits for each channel. + /// + internal class RgbFloat323232TiffColor : 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 RgbFloat323232TiffColor(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; + byte[] buffer = new byte[4]; + + 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 r = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float g = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float b = BitConverter.ToSingle(buffer, 0); + offset += 4; + + var colorVector = new Vector4(r, g, b, 1.0f); + Array.Reverse(buffer); + color.FromVector4(colorVector); + pixelRow[x] = color; + } + } + else + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 4).CopyTo(buffer); + float r = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + float g = BitConverter.ToSingle(buffer, 0); + offset += 4; + + data.Slice(offset, 4).CopyTo(buffer); + float b = BitConverter.ToSingle(buffer, 0); + offset += 4; + + var colorVector = new Vector4(r, g, b, 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 4a2fe93fe4..e1c813027d 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -176,6 +176,16 @@ public static TiffBaseColorDecoder Create(Configuration configuration, T DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + case TiffColorType.RgbFloat323232: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 32 + && bitsPerSample.Channel1 == 32 + && bitsPerSample.Channel0 == 32, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbFloat323232TiffColor(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 dc47dc8cd4..07959056c5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -133,6 +133,11 @@ internal enum TiffColorType /// Rgb323232, + /// + /// RGB color image with 32 bits floats for each channel. + /// + RgbFloat323232, + /// /// RGB Full Color. Planar configuration of data. 8 Bit per color channel. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 63185619d4..cb5b82bfd9 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -95,6 +95,11 @@ public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) /// public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } + /// + /// Gets or sets the sample format. + /// + public TiffSampleFormat SampleFormat { get; set; } + /// /// Gets or sets the horizontal predictor. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 8590203a70..8449eb7b2e 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -45,14 +45,16 @@ public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exif TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported."); } - TiffSampleFormat[] sampleFormat = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray(); - if (sampleFormat != null) + TiffSampleFormat[] sampleFormats = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray(); + TiffSampleFormat? sampleFormat = null; + if (sampleFormats != null) { - foreach (TiffSampleFormat format in sampleFormat) + sampleFormat = sampleFormats[0]; + foreach (TiffSampleFormat format in sampleFormats) { - if (format != TiffSampleFormat.UnsignedInteger) + if (format != TiffSampleFormat.UnsignedInteger && format != TiffSampleFormat.Float) { - TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger SampleFormat."); + TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger and Float SampleFormat."); } } } @@ -67,6 +69,7 @@ public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exif options.PlanarConfiguration = (TiffPlanarConfiguration?)exifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration; options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; + options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger; options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0); @@ -231,6 +234,12 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi 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; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 182fafa33e..9839350382 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -237,6 +237,17 @@ public void TiffDecoder_CanDecode_96Bit(TestImageProvider provid image.DebugSave(provider); } + [Theory] + [WithFile(FlowerRgbFloat323232, PixelTypes.Rgba32)] + [WithFile(FlowerRgbFloat323232LittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Float_96Bit(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 8eaaec4935..74fde6a56a 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -563,6 +563,8 @@ public static class Tiff 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 FlowerRgbFloat323232 = "Tiff/flower-rgb-float32_msb.tiff"; + public const string FlowerRgbFloat323232LittleEndian = "Tiff/flower-rgb-float32_lsb.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"; diff --git a/tests/Images/Input/Tiff/flower-rgb-float32_lsb.tiff b/tests/Images/Input/Tiff/flower-rgb-float32_lsb.tiff new file mode 100644 index 0000000000..da0f104387 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-float32_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1f889baa6f3bb99f15609848cdd47d548d3e2ed1b7b558d428550dfa3bd4bf9 +size 38050 diff --git a/tests/Images/Input/Tiff/flower-rgb-float32_msb.tiff b/tests/Images/Input/Tiff/flower-rgb-float32_msb.tiff new file mode 100644 index 0000000000..353771db94 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-float32_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c02be5dff2bcd5d60afbf379ba9095b0c8fd3a7a0063f684ac9ac9119f967a5 +size 38050 From 9d6b7a6204a146c32137df9db7076e2d43eef127 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 6 Aug 2021 20:34:01 +0200 Subject: [PATCH 2/4] 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 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in 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 @@ public static TiffBaseColorDecoder Create(Configuration configuration, T 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 @@ internal enum TiffColorType /// 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 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi { case 32: { + if (options.SampleFormat == TiffSampleFormat.Float) + { + options.ColorType = TiffColorType.BlackIsZero32Float; + return; + } + options.ColorType = TiffColorType.BlackIsZero32; break; } @@ -234,18 +240,18 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi 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 @@ public void TiffDecoder_CanDecode_Float_96Bit(TestImageProvider 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 @@ public static class 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 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 From b29581e57a759d7fa2dcff7a483a28f8d3a78f3d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 6 Aug 2021 20:54:32 +0200 Subject: [PATCH 3/4] Add support for decoding tiff's with 32bit float gray pixel data with min is white --- .../TiffColorDecoderFactory{TPixel}.cs | 5 ++ .../TiffColorType.cs | 5 ++ .../WhiteIsZero32FloatTiffColor{TPixel}.cs | 69 +++++++++++++++++++ .../WhiteIsZero8TiffColor{TPixel}.cs | 2 +- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 6 ++ .../Formats/Tiff/TiffDecoderTests.cs | 2 + tests/ImageSharp.Tests/TestImages.cs | 2 + .../Tiff/flower-miniswhite-float32_lsb.tiff | 3 + .../Tiff/flower-miniswhite-float32_msb.tiff | 3 + 9 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs create mode 100644 tests/Images/Input/Tiff/flower-miniswhite-float32_lsb.tiff create mode 100644 tests/Images/Input/Tiff/flower-miniswhite-float32_msb.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index f3cc4c01c8..e27c0a61cf 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -47,6 +47,11 @@ public static TiffBaseColorDecoder Create(Configuration configuration, T DebugGuard.IsTrue(colorMap == null, "colorMap"); return new WhiteIsZero32TiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.WhiteIsZero32Float: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero32FloatTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.BlackIsZero: DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index 0d742c2b95..81db744a1e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -83,6 +83,11 @@ internal enum TiffColorType /// WhiteIsZero32, + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Pixel data is 32-bit float. + /// + WhiteIsZero32Float, + /// /// Palette-color. /// diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs new file mode 100644 index 0000000000..d532247fe3 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{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 'WhiteIsZero' photometric interpretation for 32-bit float grayscale images. + /// + internal class WhiteIsZero32FloatTiffColor : 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 WhiteIsZero32FloatTiffColor(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 = 1.0f - 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 = 1.0f - 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/WhiteIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs index 6a6c2af225..15ebed58f9 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs @@ -27,7 +27,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { - byte intensity = (byte)(255 - data[offset++]); + byte intensity = (byte)(byte.MaxValue - data[offset++]); pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 79c4d372d5..5fa28f4186 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -116,6 +116,12 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi { case 32: { + if (options.SampleFormat == TiffSampleFormat.Float) + { + options.ColorType = TiffColorType.WhiteIsZero32Float; + return; + } + options.ColorType = TiffColorType.WhiteIsZero32; break; } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index e45b994a62..350b089164 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -251,6 +251,8 @@ public void TiffDecoder_CanDecode_Float_96Bit(TestImageProvider [Theory] [WithFile(Flower32BitFloatGray, PixelTypes.Rgba32)] [WithFile(Flower32BitFloatGrayLittleEndian, PixelTypes.Rgba32)] + [WithFile(Flower32BitFloatGrayMinIsWhite, PixelTypes.Rgba32)] + [WithFile(Flower32BitFloatGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_Float_96Bit_Gray(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index eb83062d95..f1c852da34 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -607,6 +607,8 @@ public static class 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 Flower32BitFloatGrayMinIsWhite = "Tiff/flower-miniswhite-float32_msb.tiff"; + public const string Flower32BitFloatGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-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-miniswhite-float32_lsb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-float32_lsb.tiff new file mode 100644 index 0000000000..3241c8fbec --- /dev/null +++ b/tests/Images/Input/Tiff/flower-miniswhite-float32_lsb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93e06533486f15e33f2435d081713fbecc3ba96c842058b7ba3a5d9116fe5f5c +size 12814 diff --git a/tests/Images/Input/Tiff/flower-miniswhite-float32_msb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-float32_msb.tiff new file mode 100644 index 0000000000..5623a74286 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-miniswhite-float32_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:800a101f4d23fa2a499fcef036ebfca7d9338ac71b06a32ad05e7eb1905ddae3 +size 12814 From b842c07fa866bd435a11dc0381e2ed7960fc4951 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 7 Aug 2021 16:18:45 +0200 Subject: [PATCH 4/4] Fix issue with white is zero with 24 and 32 bit --- .../WhiteIsZero24TiffColor{TPixel}.cs | 5 +++-- .../WhiteIsZero32TiffColor{TPixel}.cs | 5 +++-- tests/ImageSharp.Tests/TestImages.cs | 8 ++++---- ...r-minisblack-24.tiff => flower-minisblack-24_msb.tiff} | 0 ...r-minisblack-32.tiff => flower-minisblack-32_msb.tiff} | 0 ...r-miniswhite-16.tiff => flower-miniswhite-16_msb.tiff} | 0 tests/Images/Input/Tiff/flower-miniswhite-32.tiff | 3 --- tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff | 2 +- tests/Images/Input/Tiff/flower-miniswhite-32_msb.tiff | 3 +++ 9 files changed, 14 insertions(+), 12 deletions(-) rename tests/Images/Input/Tiff/{flower-minisblack-24.tiff => flower-minisblack-24_msb.tiff} (100%) rename tests/Images/Input/Tiff/{flower-minisblack-32.tiff => flower-minisblack-32_msb.tiff} (100%) rename tests/Images/Input/Tiff/{flower-miniswhite-16.tiff => flower-miniswhite-16_msb.tiff} (100%) delete mode 100644 tests/Images/Input/Tiff/flower-miniswhite-32.tiff create mode 100644 tests/Images/Input/Tiff/flower-miniswhite-32_msb.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs index b1088732ba..10182f250f 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs @@ -31,6 +31,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in color.FromVector4(TiffUtils.Vector4Default); byte[] buffer = new byte[4]; int bufferStartIdx = this.isBigEndian ? 1 : 0; + const uint maxValue = 0xFFFFFF; Span bufferSpan = buffer.AsSpan(bufferStartIdx); int offset = 0; @@ -42,7 +43,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in for (int x = 0; x < pixelRow.Length; x++) { data.Slice(offset, 3).CopyTo(bufferSpan); - ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer); + ulong intensity = maxValue - TiffUtils.ConvertToUIntBigEndian(buffer); offset += 3; pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); @@ -53,7 +54,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in for (int x = 0; x < pixelRow.Length; x++) { data.Slice(offset, 3).CopyTo(bufferSpan); - ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer); + ulong intensity = maxValue - TiffUtils.ConvertToUIntLittleEndian(buffer); offset += 3; pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs index 0071740036..ef62b4f441 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs @@ -29,6 +29,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 var color = default(TPixel); color.FromVector4(TiffUtils.Vector4Default); + const uint maxValue = 0xFFFFFFFF; int offset = 0; for (int y = top; y < top + height; y++) @@ -38,7 +39,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in { for (int x = 0; x < pixelRow.Length; x++) { - ulong intensity = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + ulong intensity = maxValue - TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); offset += 4; pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); @@ -48,7 +49,7 @@ public override void Decode(ReadOnlySpan data, Buffer2D pixels, in { for (int x = 0; x < pixelRow.Length; x++) { - ulong intensity = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + ulong intensity = maxValue - TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); offset += 4; pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f1c852da34..a25306cc9c 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -600,16 +600,16 @@ public static class Tiff public const string Flower16BitGray = "Tiff/flower-minisblack-16.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 Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16_msb.tiff"; + public const string Flower24BitGray = "Tiff/flower-minisblack-24_msb.tiff"; public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff"; - public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff"; + public const string Flower32BitGray = "Tiff/flower-minisblack-32_msb.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 Flower32BitFloatGrayMinIsWhite = "Tiff/flower-miniswhite-float32_msb.tiff"; public const string Flower32BitFloatGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-float32_lsb.tiff"; - public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32.tiff"; + public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32_msb.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-minisblack-24.tiff b/tests/Images/Input/Tiff/flower-minisblack-24_msb.tiff similarity index 100% rename from tests/Images/Input/Tiff/flower-minisblack-24.tiff rename to tests/Images/Input/Tiff/flower-minisblack-24_msb.tiff diff --git a/tests/Images/Input/Tiff/flower-minisblack-32.tiff b/tests/Images/Input/Tiff/flower-minisblack-32_msb.tiff similarity index 100% rename from tests/Images/Input/Tiff/flower-minisblack-32.tiff rename to tests/Images/Input/Tiff/flower-minisblack-32_msb.tiff diff --git a/tests/Images/Input/Tiff/flower-miniswhite-16.tiff b/tests/Images/Input/Tiff/flower-miniswhite-16_msb.tiff similarity index 100% rename from tests/Images/Input/Tiff/flower-miniswhite-16.tiff rename to tests/Images/Input/Tiff/flower-miniswhite-16_msb.tiff diff --git a/tests/Images/Input/Tiff/flower-miniswhite-32.tiff b/tests/Images/Input/Tiff/flower-miniswhite-32.tiff deleted file mode 100644 index f8cf87553e..0000000000 --- a/tests/Images/Input/Tiff/flower-miniswhite-32.tiff +++ /dev/null @@ -1,3 +0,0 @@ -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 index 8c99dda7fb..d44ae9b915 100644 --- a/tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff +++ b/tests/Images/Input/Tiff/flower-miniswhite-32_lsb.tiff @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64c948aa03bc4a24cd1d68bb18b5031c119936154a90f1cb1d9aaabd854c5d9b +oid sha256:17139bc00d9fe2905fbac9226120d2823ea17a10a39b140970122153ec23265d size 12778 diff --git a/tests/Images/Input/Tiff/flower-miniswhite-32_msb.tiff b/tests/Images/Input/Tiff/flower-miniswhite-32_msb.tiff new file mode 100644 index 0000000000..d44ae9b915 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-miniswhite-32_msb.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17139bc00d9fe2905fbac9226120d2823ea17a10a39b140970122153ec23265d +size 12778