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 000000000..9dc989c38 --- /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 7d13fdcdf..ce8d2db64 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 c322b35b3..cd94f8e81 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 1167d4784..20b3b1340 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 b49bcc219..81418df29 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 000000000..143a684c2 --- /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 4563e2317..2186f2f9a 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 9514e4301..c8fd98021 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 91fc69950..90d17c959 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 2f03e067a..641ff4671 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 000000000..7f9dd009d --- /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