From 3b4be3631365ab9b3794a63decaf9d0cc18b9a9e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 4 Jun 2021 18:55:49 +0200 Subject: [PATCH 01/33] Keep BitsPerSample array when decoding tiff, otherwise bits per channel would be ambiguous if we only keep bits per pixel --- .../Formats/Tiff/TiffBitsPerSample.cs | 56 --------- .../Tiff/TiffBitsPerSampleExtensions.cs | 111 ------------------ .../Formats/Tiff/TiffDecoderCore.cs | 12 +- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 54 ++++----- .../Formats/Tiff/TiffFrameMetadata.cs | 9 +- .../Formats/Tiff/TiffMetadataTests.cs | 2 +- 6 files changed, 36 insertions(+), 208 deletions(-) delete mode 100644 src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs delete mode 100644 src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs deleted file mode 100644 index 088ef5d6f8..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Tiff -{ - /// - /// The number of bits per component. - /// - public enum TiffBitsPerSample - { - /// - /// The Bits per samples is not known. - /// - Unknown = 0, - - /// - /// One bit per sample for bicolor images. - /// - Bit1 = 1, - - /// - /// Four bits per sample for grayscale images with 16 different levels of gray or paletted images with a palette of 16 colors. - /// - Bit4 = 4, - - /// - /// Eight bits per sample for grayscale images with 256 different levels of gray or paletted images with a palette of 256 colors. - /// - Bit8 = 8, - - /// - /// Six bits per sample, each channel has 2 bits. - /// - Bit6 = 6, - - /// - /// Twelve bits per sample, each channel has 4 bits. - /// - Bit12 = 12, - - /// - /// 24 bits per sample, each color channel has 8 Bits. - /// - Bit24 = 24, - - /// - /// Thirty bits per sample, each channel has 10 bits. - /// - Bit30 = 30, - - /// - /// Forty two bits per sample, each channel has 14 bits. - /// - Bit42 = 42, - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs deleted file mode 100644 index ca0f0befcc..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Formats.Tiff.Constants; - -namespace SixLabors.ImageSharp.Formats.Tiff -{ - internal static class TiffBitsPerSampleExtensions - { - /// - /// Gets the bits per channel array for a given BitsPerSample value, e,g, for RGB888: [8, 8, 8] - /// - /// The tiff bits per sample. - /// Bits per sample array. - public static ushort[] Bits(this TiffBitsPerSample tiffBitsPerSample) - { - switch (tiffBitsPerSample) - { - case TiffBitsPerSample.Bit1: - return TiffConstants.BitsPerSample1Bit; - case TiffBitsPerSample.Bit4: - return TiffConstants.BitsPerSample4Bit; - case TiffBitsPerSample.Bit6: - return TiffConstants.BitsPerSampleRgb2Bit; - case TiffBitsPerSample.Bit8: - return TiffConstants.BitsPerSample8Bit; - case TiffBitsPerSample.Bit12: - return TiffConstants.BitsPerSampleRgb4Bit; - case TiffBitsPerSample.Bit24: - return TiffConstants.BitsPerSampleRgb8Bit; - case TiffBitsPerSample.Bit30: - return TiffConstants.BitsPerSampleRgb10Bit; - case TiffBitsPerSample.Bit42: - return TiffConstants.BitsPerSampleRgb14Bit; - - default: - return Array.Empty(); - } - } - - /// - /// Maps an array of bits per sample to a concrete enum value. - /// - /// The bits per sample array. - /// TiffBitsPerSample enum value. - public static TiffBitsPerSample GetBitsPerSample(this ushort[] bitsPerSample) - { - switch (bitsPerSample.Length) - { - case 3: - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb14Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb14Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb14Bit[0]) - { - return TiffBitsPerSample.Bit42; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb10Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb10Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb10Bit[0]) - { - return TiffBitsPerSample.Bit30; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit[0]) - { - return TiffBitsPerSample.Bit24; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb4Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb4Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb4Bit[0]) - { - return TiffBitsPerSample.Bit12; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb2Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb2Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb2Bit[0]) - { - return TiffBitsPerSample.Bit6; - } - - break; - - case 1: - if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit[0]) - { - return TiffBitsPerSample.Bit1; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit[0]) - { - return TiffBitsPerSample.Bit4; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit[0]) - { - return TiffBitsPerSample.Bit8; - } - - break; - } - - return TiffBitsPerSample.Unknown; - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index fadb4f7c2e..294407ef97 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -50,9 +50,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff } /// - /// Gets or sets the number of bits per component of the pixel format used to decode the image. + /// Gets or sets the bits per sample. /// - public TiffBitsPerSample BitsPerSample { get; set; } + public ushort[] BitsPerSample { get; set; } /// /// Gets or sets the bits per pixel. @@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - bitsPerPixel = this.BitsPerSample.Bits()[plane]; + bitsPerPixel = this.BitsPerSample[plane]; } int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; @@ -225,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts) where TPixel : unmanaged, IPixel { - int stripsPerPixel = this.BitsPerSample.Bits().Length; + int stripsPerPixel = this.BitsPerSample.Length; int stripsPerPlane = stripOffsets.Length / stripsPerPixel; int bitsPerPixel = this.BitsPerPixel; @@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); - RgbPlanarTiffColor colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); + RgbPlanarTiffColor colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap); for (int i = 0; i < stripsPerPlane; i++) { @@ -286,7 +286,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); - TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); + TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap); for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) { diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index eeac6a33c2..a71c4cb054 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; - options.BitsPerSample = GetBitsPerSample(frameMetadata.BitsPerPixel); + options.BitsPerSample = frameMetadata.BitsPerSample ?? Array.Empty(); options.ParseColorType(exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile); @@ -99,26 +99,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff { case TiffPhotometricInterpretation.WhiteIsZero: { - if (options.BitsPerSample.Bits().Length != 1) + if (options.BitsPerSample.Length != 1) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } - switch (options.BitsPerSample) + ushort bitsPerChannel = options.BitsPerSample[0]; + switch (bitsPerChannel) { - case TiffBitsPerSample.Bit8: + case 8: { options.ColorType = TiffColorType.WhiteIsZero8; break; } - case TiffBitsPerSample.Bit4: + case 4: { options.ColorType = TiffColorType.WhiteIsZero4; break; } - case TiffBitsPerSample.Bit1: + case 1: { options.ColorType = TiffColorType.WhiteIsZero1; break; @@ -136,26 +137,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffPhotometricInterpretation.BlackIsZero: { - if (options.BitsPerSample.Bits().Length != 1) + if (options.BitsPerSample.Length != 1) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } - switch (options.BitsPerSample) + ushort bitsPerChannel = options.BitsPerSample[0]; + switch (bitsPerChannel) { - case TiffBitsPerSample.Bit8: + case 8: { options.ColorType = TiffColorType.BlackIsZero8; break; } - case TiffBitsPerSample.Bit4: + case 4: { options.ColorType = TiffColorType.BlackIsZero4; break; } - case TiffBitsPerSample.Bit1: + case 1: { options.ColorType = TiffColorType.BlackIsZero1; break; @@ -173,30 +175,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffPhotometricInterpretation.Rgb: { - if (options.BitsPerSample.Bits().Length != 3) + if (options.BitsPerSample.Length != 3) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) { - switch (options.BitsPerSample) + ushort bitsPerChannel = options.BitsPerSample[0]; + switch (bitsPerChannel) { - case TiffBitsPerSample.Bit42: + case 14: options.ColorType = TiffColorType.Rgb141414; break; - case TiffBitsPerSample.Bit30: + case 10: options.ColorType = TiffColorType.Rgb101010; break; - case TiffBitsPerSample.Bit24: + case 8: options.ColorType = TiffColorType.Rgb888; break; - case TiffBitsPerSample.Bit12: + case 4: options.ColorType = TiffColorType.Rgb444; break; - case TiffBitsPerSample.Bit6: + case 2: options.ColorType = TiffColorType.Rgb222; break; default: @@ -217,7 +220,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; if (options.ColorMap != null) { - if (options.BitsPerSample.Bits().Length != 1) + if (options.BitsPerSample.Length != 1) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } @@ -291,18 +294,5 @@ namespace SixLabors.ImageSharp.Formats.Tiff } } } - - private static TiffBitsPerSample GetBitsPerSample(TiffBitsPerPixel? bitsPerPixel) => bitsPerPixel switch - { - TiffBitsPerPixel.Bit1 => TiffBitsPerSample.Bit1, - TiffBitsPerPixel.Bit4 => TiffBitsPerSample.Bit4, - TiffBitsPerPixel.Bit6 => TiffBitsPerSample.Bit6, - TiffBitsPerPixel.Bit8 => TiffBitsPerSample.Bit8, - TiffBitsPerPixel.Bit12 => TiffBitsPerSample.Bit12, - TiffBitsPerPixel.Bit24 => TiffBitsPerSample.Bit24, - TiffBitsPerPixel.Bit30 => TiffBitsPerSample.Bit30, - TiffBitsPerPixel.Bit42 => TiffBitsPerSample.Bit42, - _ => throw new NotSupportedException("The bits per pixel are not supported"), - }; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index 25a0578e91..ef7573d3e0 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -35,6 +35,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public TiffBitsPerPixel? BitsPerPixel { get; set; } + /// + /// Gets or sets number of bits per component. + /// + public ushort[] BitsPerSample { get; set; } + /// /// Gets or sets the compression scheme used on the image data. /// @@ -72,8 +77,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff { if (profile != null) { - ushort[] bitsPerSample = profile.GetValue(ExifTag.BitsPerSample)?.Value; - meta.BitsPerPixel = BitsPerPixelFromBitsPerSample(bitsPerSample); + meta.BitsPerSample = profile.GetValue(ExifTag.BitsPerSample)?.Value; + meta.BitsPerPixel = BitsPerPixelFromBitsPerSample(meta.BitsPerSample); meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value; meta.PhotometricInterpretation = (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index ab350f720e..c80d9fc165 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { Assert.NotNull(frameMetaData); Assert.NotNull(frameMetaData.BitsPerPixel); - Assert.Equal(TiffBitsPerSample.Bit4, (TiffBitsPerSample)frameMetaData.BitsPerPixel); + Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaData.BitsPerPixel); Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); Assert.Equal(TiffPredictor.None, frameMetaData.Predictor); From 3b18d705e3e90b4cb4f83ceb59a4e88afffd2f20 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 5 Jun 2021 08:31:46 +0200 Subject: [PATCH 02/33] Additional tests for gray tiff images --- .../Formats/Tiff/TiffBitsPerPixel.cs | 21 ++++++++++++ .../Formats/Tiff/TiffEncoderCore.cs | 5 ++- .../Formats/Tiff/TiffDecoderTests.cs | 32 +++++++++++++++++++ .../Formats/Tiff/TiffEncoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 8 +++++ .../ReferenceCodecs/MagickReferenceDecoder.cs | 2 +- .../Input/Tiff/flower-minisblack-02.tiff | 3 ++ .../Input/Tiff/flower-minisblack-06.tiff | 3 ++ .../Input/Tiff/flower-minisblack-08.tiff | 3 ++ .../Input/Tiff/flower-minisblack-10.tiff | 3 ++ .../Input/Tiff/flower-minisblack-12.tiff | 3 ++ .../Input/Tiff/flower-minisblack-14.tiff | 3 ++ .../Input/Tiff/flower-minisblack-16.tiff | 3 ++ .../Images/Input/Tiff/flower-palette-02.tiff | 3 ++ 14 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 tests/Images/Input/Tiff/flower-minisblack-02.tiff create mode 100644 tests/Images/Input/Tiff/flower-minisblack-06.tiff create mode 100644 tests/Images/Input/Tiff/flower-minisblack-08.tiff create mode 100644 tests/Images/Input/Tiff/flower-minisblack-10.tiff create mode 100644 tests/Images/Input/Tiff/flower-minisblack-12.tiff create mode 100644 tests/Images/Input/Tiff/flower-minisblack-14.tiff create mode 100644 tests/Images/Input/Tiff/flower-minisblack-16.tiff create mode 100644 tests/Images/Input/Tiff/flower-palette-02.tiff diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs index ab9f3cbec0..d2a57e7b8a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs @@ -30,6 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// Bit8 = 8, + /// + /// 10 bits per pixel, for gray images. + /// + /// Note: The TiffEncoder does not yet support 10 bits per pixel and will default to 24 bits per pixel instead. + /// + Bit10 = 10, + /// /// 12 bits per pixel. 4 bit for each color channel. /// @@ -37,6 +44,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// Bit12 = 12, + /// + /// 14 bits per pixel, for gray images. + /// + /// Note: The TiffEncoder does not yet support 14 bits per pixel images and will default to 24 bits per pixel instead. + /// + Bit14 = 14, + + /// + /// 16 bits per pixel, for gray images. + /// + /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit16 = 16, + /// /// 24 bits per pixel. One byte for each color channel. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index d5137c4357..047575c871 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -320,10 +320,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.SetEncoderOptions(bitsPerPixel, photometricInterpretation ?? TiffPhotometricInterpretation.BlackIsZero, compression, predictor); break; case TiffBitsPerPixel.Bit6: + case TiffBitsPerPixel.Bit10: case TiffBitsPerPixel.Bit12: + case TiffBitsPerPixel.Bit14: + case TiffBitsPerPixel.Bit16: case TiffBitsPerPixel.Bit30: case TiffBitsPerPixel.Bit42: - // Encoding 42, 30, 12 and 6 bits per pixel is not yet supported. Default to 24 bits. + // Encoding not yet supported bits per pixel will default to 24 bits. this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None); break; default: diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 02b7f97d94..04749159da 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -99,18 +99,50 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_4Bit_WithPalette(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); + [Theory] + [WithFile(Flower2BitPalette, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_2Bit_WithPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); + + [Theory] + [WithFile(Flower2BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_2Bit(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(Flower8BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower10BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_10Bit(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(Flower14BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_14Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower16BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 7c386a6a9a..9d360fb7ec 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -81,6 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [InlineData(TiffBitsPerPixel.Bit42)] [InlineData(TiffBitsPerPixel.Bit30)] [InlineData(TiffBitsPerPixel.Bit12)] + [InlineData(TiffBitsPerPixel.Bit10)] [InlineData(TiffBitsPerPixel.Bit6)] public void EncoderOptions_UnsupportedBitPerPixel_DefaultTo24Bits(TiffBitsPerPixel bitsPerPixel) { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 9471a63937..e31a1cf5ca 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -568,6 +568,14 @@ namespace SixLabors.ImageSharp.Tests public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff"; public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff"; public const string FlowerRgb222Planar = "Tiff/flower-rgb-planar-02.tiff"; + public const string Flower2BitGray = "Tiff/flower-minisblack-02.tiff"; + public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff"; + public const string Flower6BitGray = "Tiff/flower-minisblack-06.tiff"; + public const string Flower8BitGray = "Tiff/flower-minisblack-08.tiff"; + public const string Flower10BitGray = "Tiff/flower-minisblack-10.tiff"; + 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 SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index dffbeac497..294bd20fb5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); - if (magicFrame.Depth == 8 || magicFrame.Depth == 4 || magicFrame.Depth == 2 || magicFrame.Depth == 1 || magicFrame.Depth == 10) + if (magicFrame.Depth == 8 || magicFrame.Depth == 6 || magicFrame.Depth == 4 || magicFrame.Depth == 2 || magicFrame.Depth == 1 || magicFrame.Depth == 10 || magicFrame.Depth == 12) { byte[] data = pixels.ToByteArray(PixelMapping.RGBA); diff --git a/tests/Images/Input/Tiff/flower-minisblack-02.tiff b/tests/Images/Input/Tiff/flower-minisblack-02.tiff new file mode 100644 index 0000000000..d6ce305fe6 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-02.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3122afede012fa00b8cb379b2f9125a34a38188c3346ec5e18d3b4bddcbb451b +size 1131 diff --git a/tests/Images/Input/Tiff/flower-minisblack-06.tiff b/tests/Images/Input/Tiff/flower-minisblack-06.tiff new file mode 100644 index 0000000000..53db4e1126 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-06.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0c13012d8d35215b01192eb38058db4543486c60b4918beec8719a94d1e208e +size 2679 diff --git a/tests/Images/Input/Tiff/flower-minisblack-08.tiff b/tests/Images/Input/Tiff/flower-minisblack-08.tiff new file mode 100644 index 0000000000..02acb15113 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-08.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1268d843a2338409ec3a9f5a5a62e23d38c3a898035619994a02f21eff7590bf +size 3453 diff --git a/tests/Images/Input/Tiff/flower-minisblack-10.tiff b/tests/Images/Input/Tiff/flower-minisblack-10.tiff new file mode 100644 index 0000000000..770197726f --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-10.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a91d6946730604dd65c63f1653fb33031682f26218de33ebf3d0b362cb6883af +size 4269 diff --git a/tests/Images/Input/Tiff/flower-minisblack-12.tiff b/tests/Images/Input/Tiff/flower-minisblack-12.tiff new file mode 100644 index 0000000000..320083c323 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-12.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86fc9309872f4e4668350b95fae315d878ec9658046d738050a2743f5fa44446 +size 5043 diff --git a/tests/Images/Input/Tiff/flower-minisblack-14.tiff b/tests/Images/Input/Tiff/flower-minisblack-14.tiff new file mode 100644 index 0000000000..34fca95b56 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-14.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcd07668c73f24c2a13133ac4910b59a568502a6d3762675eef61a7e3b090165 +size 5817 diff --git a/tests/Images/Input/Tiff/flower-minisblack-16.tiff b/tests/Images/Input/Tiff/flower-minisblack-16.tiff new file mode 100644 index 0000000000..0791941f99 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-minisblack-16.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79531a10710dee89b86e2467818b7c03a24ff28ebd98c7bdcc292559671e1887 +size 6591 diff --git a/tests/Images/Input/Tiff/flower-palette-02.tiff b/tests/Images/Input/Tiff/flower-palette-02.tiff new file mode 100644 index 0000000000..eb80e4de85 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-palette-02.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75e74d8816942ff6e9dfda411f9171f0f1dd1a5a88cb1410238b55a2b2aeeb71 +size 1164 From bbd71e2ce756d9cbc37c269bf88814a0f3eadb2a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 5 Jun 2021 15:36:59 +0200 Subject: [PATCH 03/33] Add support decoding for 12 bits per channel tiff's --- .../TiffColorDecoderFactory{TPixel}.cs | 10 ++++++++++ .../Tiff/PhotometricInterpretation/TiffColorType.cs | 5 +++++ src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs | 7 +++++++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 4 ++++ src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs | 1 + .../ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs | 5 +++++ .../ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Tiff/flower-rgb-contig-12.tiff | 3 +++ 9 files changed, 37 insertions(+) create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-12.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 5555eb537c..548ee2d4d2 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -97,6 +97,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgb121212: + DebugGuard.IsTrue( + bitsPerSample.Length == 3 + && bitsPerSample[2] == 12 + && bitsPerSample[1] == 12 + && bitsPerSample[0] == 12, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgb141414: DebugGuard.IsTrue( bitsPerSample.Length == 3 diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index 22d8199533..37a878fed5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -78,6 +78,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// Rgb101010, + /// + /// RGB color image with 12 bits for each channel. + /// + Rgb121212, + /// /// RGB color image with 14 bits for each channel. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs index d2a57e7b8a..08f0777ea5 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs @@ -70,6 +70,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// Bit30 = 30, + /// + /// 36 bits per pixel. 12 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 12 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit36 = 36, + /// /// 42 bits per pixel. 14 bit for each color channel. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index a71c4cb054..8b7e0cf455 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -189,6 +189,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff options.ColorType = TiffColorType.Rgb141414; break; + case 12: + options.ColorType = TiffColorType.Rgb121212; + break; + case 10: options.ColorType = TiffColorType.Rgb101010; break; diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 047575c871..281f61c7fe 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -325,6 +325,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffBitsPerPixel.Bit14: case TiffBitsPerPixel.Bit16: case TiffBitsPerPixel.Bit30: + case TiffBitsPerPixel.Bit36: case TiffBitsPerPixel.Bit42: // Encoding not yet supported bits per pixel will default to 24 bits. this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 04749159da..9b2cd9a008 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -149,6 +149,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_30Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_36Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 9d360fb7ec..f722f53842 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -79,6 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [InlineData(TiffBitsPerPixel.Bit42)] + [InlineData(TiffBitsPerPixel.Bit36)] [InlineData(TiffBitsPerPixel.Bit30)] [InlineData(TiffBitsPerPixel.Bit12)] [InlineData(TiffBitsPerPixel.Bit10)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e31a1cf5ca..00d0a0219d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -564,6 +564,7 @@ namespace SixLabors.ImageSharp.Tests public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff"; public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff"; public const string FlowerRgb101010Planar = "Tiff/flower-rgb-planar-10.tiff"; + public const string FlowerRgb121212Contiguous = "Tiff/flower-rgb-contig-12.tiff"; public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff"; public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff"; public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff"; diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-12.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-12.tiff new file mode 100644 index 0000000000..c890c777a6 --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-12.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f7a63eb8636e2b1ee39dfda4d0bddfc98bdc9eb94bea2dd657619331fa38b5b +size 14483 From 6281743b3bb36d2f7331832f87f2ad0f51da61ee Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 5 Jun 2021 16:47:34 +0200 Subject: [PATCH 04/33] Add support decoding for 16 bits per channel tiff's --- .../TiffColorDecoderFactory{TPixel}.cs | 10 ++++++++++ .../Tiff/PhotometricInterpretation/TiffColorType.cs | 5 +++++ src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs | 7 +++++++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 4 ++++ src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs | 1 + .../ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs | 6 ++++++ .../ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 2 ++ tests/Images/Input/Tiff/flower-rgb-contig-16.tiff | 3 +++ tests/Images/Input/Tiff/flower-rgb-planar-16.tiff | 3 +++ 10 files changed, 42 insertions(+) create mode 100644 tests/Images/Input/Tiff/flower-rgb-contig-16.tiff create mode 100644 tests/Images/Input/Tiff/flower-rgb-planar-16.tiff diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 548ee2d4d2..4ca7ed9159 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -117,6 +117,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); + case TiffColorType.Rgb161616: + DebugGuard.IsTrue( + bitsPerSample.Length == 3 + && bitsPerSample[2] == 16 + && bitsPerSample[1] == 16 + && bitsPerSample[0] == 16, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + case TiffColorType.PaletteColor: DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); DebugGuard.NotNull(colorMap, "colorMap"); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index 37a878fed5..517926c239 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -88,6 +88,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// Rgb141414, + /// + /// RGB color image with 16 bits for each channel. + /// + Rgb161616, + /// /// RGB Full Color. Planar configuration of data. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs index 08f0777ea5..73f3f4b77e 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs @@ -83,5 +83,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// Note: The TiffEncoder does not yet support 14 bits per color channel and will default to 24 bits per pixel instead. /// Bit42 = 42, + + /// + /// 48 bits per pixel. 16 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit48 = 48, } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 8b7e0cf455..3ba64b18cb 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -185,6 +185,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff ushort bitsPerChannel = options.BitsPerSample[0]; switch (bitsPerChannel) { + case 16: + options.ColorType = TiffColorType.Rgb161616; + break; + case 14: options.ColorType = TiffColorType.Rgb141414; break; diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 281f61c7fe..2273d759f0 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -327,6 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffBitsPerPixel.Bit30: case TiffBitsPerPixel.Bit36: case TiffBitsPerPixel.Bit42: + case TiffBitsPerPixel.Bit48: // Encoding not yet supported bits per pixel will default to 24 bits. this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None); break; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 9b2cd9a008..6b82f4281c 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -160,6 +160,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_42Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index f722f53842..acbed8ac2e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -78,6 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } [Theory] + [InlineData(TiffBitsPerPixel.Bit48)] [InlineData(TiffBitsPerPixel.Bit42)] [InlineData(TiffBitsPerPixel.Bit36)] [InlineData(TiffBitsPerPixel.Bit30)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 00d0a0219d..28ef20cf4c 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -560,6 +560,8 @@ namespace SixLabors.ImageSharp.Tests public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff"; public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; + public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff"; + public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff"; public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff"; public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff"; public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff"; diff --git a/tests/Images/Input/Tiff/flower-rgb-contig-16.tiff b/tests/Images/Input/Tiff/flower-rgb-contig-16.tiff new file mode 100644 index 0000000000..125de5b9fd --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-contig-16.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab3d6b619a198ff2e5fdd8f9752bf43c5b03a782625b1f0e3f2cfe0f20c4b24a +size 19177 diff --git a/tests/Images/Input/Tiff/flower-rgb-planar-16.tiff b/tests/Images/Input/Tiff/flower-rgb-planar-16.tiff new file mode 100644 index 0000000000..939fd9471b --- /dev/null +++ b/tests/Images/Input/Tiff/flower-rgb-planar-16.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a143fb6c5792fa7755e06feb757c745ad68944336985dc5be8a0c37247fe36d +size 19177 From aba1050bae1a1dc0aee0a72d275933a8f6b41254 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 5 Jun 2021 17:11:35 +0200 Subject: [PATCH 05/33] Throw exception for single channel tiff when bits per sample is larger then 16 --- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 3ba64b18cb..014dd55380 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -105,6 +105,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff } ushort bitsPerChannel = options.BitsPerSample[0]; + if (bitsPerChannel > 16) + { + TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); + } + switch (bitsPerChannel) { case 8: @@ -143,6 +148,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff } ushort bitsPerChannel = options.BitsPerSample[0]; + if (bitsPerChannel > 16) + { + TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); + } + switch (bitsPerChannel) { case 8: From 67f7b78293ab0d4bd7798ca7837c54f71352f1e2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 11 Jun 2021 12:25:13 +0200 Subject: [PATCH 06/33] Re-Introduce TiffBitsPerSample --- .../Formats/Tiff/Constants/TiffConstants.cs | 40 +++++ .../Formats/Tiff/TiffBitsPerSample.cs | 96 ++++++++++ .../Tiff/TiffBitsPerSampleExtensions.cs | 170 ++++++++++++++++++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 2 +- .../Formats/Tiff/TiffFrameMetadata.cs | 9 +- tests/ImageSharp.Tests/TestImages.cs | 4 +- 6 files changed, 313 insertions(+), 8 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs create mode 100644 src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs index 6fe412b925..5733bada97 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -85,16 +85,46 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants /// public static readonly ushort[] BitsPerSample1Bit = { 1 }; + /// + /// The bits per sample for images with a 2 color palette. + /// + public static readonly ushort[] BitsPerSample2Bit = { 2 }; + /// /// The bits per sample for images with a 4 color palette. /// public static readonly ushort[] BitsPerSample4Bit = { 4 }; + /// + /// The bits per sample for 6 bit gray images. + /// + public static readonly ushort[] BitsPerSample6Bit = { 6 }; + /// /// The bits per sample for 8 bit images. /// public static readonly ushort[] BitsPerSample8Bit = { 8 }; + /// + /// The bits per sample for 10 bit gray images. + /// + public static readonly ushort[] BitsPerSample10Bit = { 10 }; + + /// + /// The bits per sample for 12 bit gray images. + /// + public static readonly ushort[] BitsPerSample12Bit = { 12 }; + + /// + /// The bits per sample for 14 bit gray images. + /// + public static readonly ushort[] BitsPerSample14Bit = { 14 }; + + /// + /// The bits per sample for 16 bit gray images. + /// + public static readonly ushort[] BitsPerSample16Bit = { 16 }; + /// /// The bits per sample for color images with 2 bits for each color channel. /// @@ -115,11 +145,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants /// public static readonly ushort[] BitsPerSampleRgb10Bit = { 10, 10, 10 }; + /// + /// The bits per sample for color images with 12 bits for each color channel. + /// + public static readonly ushort[] BitsPerSampleRgb12Bit = { 12, 12, 12 }; + /// /// The bits per sample for color images with 14 bits for each color channel. /// public static readonly ushort[] BitsPerSampleRgb14Bit = { 14, 14, 14 }; + /// + /// The bits per sample for color images with 14 bits for each color channel. + /// + public static readonly ushort[] BitsPerSampleRgb16Bit = { 16, 16, 16 }; + /// /// The list of mimetypes that equate to a tiff. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs new file mode 100644 index 0000000000..71f6b5bf9f --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// The number of bits per component. + /// + public enum TiffBitsPerSample + { + /// + /// The bits per samples is not known. + /// + Unknown = 0, + + /// + /// One bit per sample for bicolor images. + /// + Bit1, + + /// + /// Two bits per sample for grayscale images with 4 different levels of gray or paletted images with a palette of 4 colors. + /// + Bit2, + + /// + /// Four bits per sample for grayscale images with 16 different levels of gray or paletted images with a palette of 16 colors. + /// + Bit4, + + /// + /// Six bits per sample for grayscale images. + /// + Bit6, + + /// + /// Eight bits per sample for grayscale images with 256 different levels of gray or paletted images with a palette of 256 colors. + /// + Bit8, + + /// + /// Ten bits per sample for grayscale images. + /// + Bit10, + + /// + /// Twelve bits per sample for grayscale images. + /// + Bit12, + + /// + /// Fourteen bits per sample for grayscale images. + /// + Bit14, + + /// + /// Sixteen bits per sample for grayscale images. + /// + Bit16, + + /// + /// 6 bits per sample, each channel has 2 bits. + /// + Rgb222, + + /// + /// Twelve bits per sample, each channel has 4 bits. + /// + Rgb444, + + /// + /// 24 bits per sample, each color channel has 8 Bits. + /// + Rgb888, + + /// + /// Thirty bits per sample, each channel has 10 bits. + /// + Rgb101010, + + /// + /// Thirty six bits per sample, each channel has 12 bits. + /// + Rgb121212, + + /// + /// Forty two bits per sample, each channel has 14 bits. + /// + Rgb141414, + + /// + /// Forty eight bits per sample, each channel has 16 bits. + /// + Rgb161616, + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs new file mode 100644 index 0000000000..5ec1331b31 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs @@ -0,0 +1,170 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Tiff.Constants; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + internal static class TiffBitsPerSampleExtensions + { + /// + /// Gets the bits per channel array for a given BitsPerSample value, e,g, for RGB888: [8, 8, 8] + /// + /// The tiff bits per sample. + /// Bits per sample array. + public static ushort[] BitsPerChannel(this TiffBitsPerSample tiffBitsPerSample) + { + switch (tiffBitsPerSample) + { + case TiffBitsPerSample.Bit1: + return TiffConstants.BitsPerSample1Bit; + case TiffBitsPerSample.Bit2: + return TiffConstants.BitsPerSample2Bit; + case TiffBitsPerSample.Bit4: + return TiffConstants.BitsPerSample4Bit; + case TiffBitsPerSample.Bit6: + return TiffConstants.BitsPerSample6Bit; + case TiffBitsPerSample.Bit8: + return TiffConstants.BitsPerSample8Bit; + case TiffBitsPerSample.Bit10: + return TiffConstants.BitsPerSample10Bit; + case TiffBitsPerSample.Bit12: + return TiffConstants.BitsPerSample12Bit; + case TiffBitsPerSample.Bit14: + return TiffConstants.BitsPerSample14Bit; + case TiffBitsPerSample.Bit16: + return TiffConstants.BitsPerSample16Bit; + case TiffBitsPerSample.Rgb222: + return TiffConstants.BitsPerSampleRgb2Bit; + case TiffBitsPerSample.Rgb444: + return TiffConstants.BitsPerSampleRgb4Bit; + case TiffBitsPerSample.Rgb888: + return TiffConstants.BitsPerSampleRgb8Bit; + case TiffBitsPerSample.Rgb101010: + return TiffConstants.BitsPerSampleRgb10Bit; + case TiffBitsPerSample.Rgb121212: + return TiffConstants.BitsPerSampleRgb12Bit; + case TiffBitsPerSample.Rgb141414: + return TiffConstants.BitsPerSampleRgb14Bit; + case TiffBitsPerSample.Rgb161616: + return TiffConstants.BitsPerSampleRgb16Bit; + default: + return Array.Empty(); + } + } + + /// + /// Maps an array of bits per sample to a concrete enum value. + /// + /// The bits per sample array. + /// TiffBitsPerSample enum value. + public static TiffBitsPerSample GetBitsPerSample(this ushort[] bitsPerSample) + { + switch (bitsPerSample.Length) + { + case 3: + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb16Bit[2] && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb16Bit[1] && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb16Bit[0]) + { + return TiffBitsPerSample.Rgb161616; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb14Bit[2] && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb14Bit[1] && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb14Bit[0]) + { + return TiffBitsPerSample.Rgb141414; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb12Bit[2] && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb12Bit[1] && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb12Bit[0]) + { + return TiffBitsPerSample.Rgb121212; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb10Bit[2] && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb10Bit[1] && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb10Bit[0]) + { + return TiffBitsPerSample.Rgb101010; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit[2] && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit[1] && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit[0]) + { + return TiffBitsPerSample.Rgb888; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb4Bit[2] && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb4Bit[1] && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb4Bit[0]) + { + return TiffBitsPerSample.Rgb444; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb2Bit[2] && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb2Bit[1] && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb2Bit[0]) + { + return TiffBitsPerSample.Rgb222; + } + + break; + + case 1: + if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit[0]) + { + return TiffBitsPerSample.Bit1; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample2Bit[0]) + { + return TiffBitsPerSample.Bit2; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit[0]) + { + return TiffBitsPerSample.Bit4; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample6Bit[0]) + { + return TiffBitsPerSample.Bit6; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit[0]) + { + return TiffBitsPerSample.Bit8; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample10Bit[0]) + { + return TiffBitsPerSample.Bit10; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample12Bit[0]) + { + return TiffBitsPerSample.Bit12; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample14Bit[0]) + { + return TiffBitsPerSample.Bit14; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample16Bit[0]) + { + return TiffBitsPerSample.Bit16; + } + + break; + } + + return TiffBitsPerSample.Unknown; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 014dd55380..1efc826027 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; - options.BitsPerSample = frameMetadata.BitsPerSample ?? Array.Empty(); + options.BitsPerSample = frameMetadata.BitsPerSample != null ? frameMetadata.BitsPerSample?.BitsPerChannel() : Array.Empty(); options.ParseColorType(exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile); diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index ef7573d3e0..62e9fb4e21 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Gets or sets number of bits per component. /// - public ushort[] BitsPerSample { get; set; } + public TiffBitsPerSample? BitsPerSample { get; set; } /// /// Gets or sets the compression scheme used on the image data. @@ -77,11 +77,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff { if (profile != null) { - meta.BitsPerSample = profile.GetValue(ExifTag.BitsPerSample)?.Value; - meta.BitsPerPixel = BitsPerPixelFromBitsPerSample(meta.BitsPerSample); + meta.BitsPerSample = profile.GetValue(ExifTag.BitsPerSample) != null ? profile.GetValue(ExifTag.BitsPerSample)?.Value.GetBitsPerSample() : null; + meta.BitsPerPixel = BitsPerPixelFromBitsPerSample(meta.BitsPerSample?.BitsPerChannel()); meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value; - meta.PhotometricInterpretation = - (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value; + meta.PhotometricInterpretation = (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value; meta.Predictor = (TiffPredictor?)profile.GetValue(ExifTag.Predictor)?.Value; profile.RemoveValue(ExifTag.BitsPerSample); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 28ef20cf4c..7eca4795df 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -558,8 +558,6 @@ 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 Flower4BitPalette = "Tiff/flower-palette-04.tiff"; - public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff"; public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff"; public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff"; @@ -573,6 +571,8 @@ namespace SixLabors.ImageSharp.Tests public const string FlowerRgb222Planar = "Tiff/flower-rgb-planar-02.tiff"; public const string Flower2BitGray = "Tiff/flower-minisblack-02.tiff"; public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff"; + public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff"; + public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; public const string Flower6BitGray = "Tiff/flower-minisblack-06.tiff"; public const string Flower8BitGray = "Tiff/flower-minisblack-08.tiff"; public const string Flower10BitGray = "Tiff/flower-minisblack-10.tiff"; From aa848d74e9e77496ba5a2c13343efb9709629e83 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 11 Jun 2021 19:10:23 +0200 Subject: [PATCH 07/33] Change BitsPerSample to a struct --- .../Formats/Tiff/Constants/TiffConstants.cs | 32 +- .../Formats/Tiff/TiffBitsPerSample.cs | 302 +++++++++++++----- .../Tiff/TiffBitsPerSampleExtensions.cs | 170 ---------- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 2 +- .../Tiff/TiffEncoderEntriesCollector.cs | 16 +- .../Formats/Tiff/TiffFrameMetadata.cs | 27 +- 6 files changed, 252 insertions(+), 297 deletions(-) delete mode 100644 src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs index 5733bada97..8d9fb94a44 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -83,82 +83,82 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants /// /// The bits per sample for 1 bit bicolor images. /// - public static readonly ushort[] BitsPerSample1Bit = { 1 }; + public static readonly TiffBitsPerSample BitsPerSample1Bit = new TiffBitsPerSample(1, 0, 0); /// /// The bits per sample for images with a 2 color palette. /// - public static readonly ushort[] BitsPerSample2Bit = { 2 }; + public static readonly TiffBitsPerSample BitsPerSample2Bit = new TiffBitsPerSample(2, 0, 0); /// /// The bits per sample for images with a 4 color palette. /// - public static readonly ushort[] BitsPerSample4Bit = { 4 }; + public static readonly TiffBitsPerSample BitsPerSample4Bit = new TiffBitsPerSample(4, 0, 0); /// /// The bits per sample for 6 bit gray images. /// - public static readonly ushort[] BitsPerSample6Bit = { 6 }; + public static readonly TiffBitsPerSample BitsPerSample6Bit = new TiffBitsPerSample(6, 0, 0); /// /// The bits per sample for 8 bit images. /// - public static readonly ushort[] BitsPerSample8Bit = { 8 }; + public static readonly TiffBitsPerSample BitsPerSample8Bit = new TiffBitsPerSample(8, 0, 0); /// /// The bits per sample for 10 bit gray images. /// - public static readonly ushort[] BitsPerSample10Bit = { 10 }; + public static readonly TiffBitsPerSample BitsPerSample10Bit = new TiffBitsPerSample(10, 0, 0); /// /// The bits per sample for 12 bit gray images. /// - public static readonly ushort[] BitsPerSample12Bit = { 12 }; + public static readonly TiffBitsPerSample BitsPerSample12Bit = new TiffBitsPerSample(12, 0, 0); /// /// The bits per sample for 14 bit gray images. /// - public static readonly ushort[] BitsPerSample14Bit = { 14 }; + public static readonly TiffBitsPerSample BitsPerSample14Bit = new TiffBitsPerSample(14, 0, 0); /// /// The bits per sample for 16 bit gray images. /// - public static readonly ushort[] BitsPerSample16Bit = { 16 }; + public static readonly TiffBitsPerSample BitsPerSample16Bit = new TiffBitsPerSample(16, 0, 0); /// /// The bits per sample for color images with 2 bits for each color channel. /// - public static readonly ushort[] BitsPerSampleRgb2Bit = { 2, 2, 2 }; + public static readonly TiffBitsPerSample BitsPerSampleRgb2Bit = new TiffBitsPerSample(2, 2, 2); /// /// The bits per sample for color images with 4 bits for each color channel. /// - public static readonly ushort[] BitsPerSampleRgb4Bit = { 4, 4, 4 }; + public static readonly TiffBitsPerSample BitsPerSampleRgb4Bit = new TiffBitsPerSample(4, 4, 4); /// /// The bits per sample for color images with 8 bits for each color channel. /// - public static readonly ushort[] BitsPerSampleRgb8Bit = { 8, 8, 8 }; + public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new TiffBitsPerSample(8, 8, 8); /// /// The bits per sample for color images with 10 bits for each color channel. /// - public static readonly ushort[] BitsPerSampleRgb10Bit = { 10, 10, 10 }; + public static readonly TiffBitsPerSample BitsPerSampleRgb10Bit = new TiffBitsPerSample(10, 10, 10); /// /// The bits per sample for color images with 12 bits for each color channel. /// - public static readonly ushort[] BitsPerSampleRgb12Bit = { 12, 12, 12 }; + public static readonly TiffBitsPerSample BitsPerSampleRgb12Bit = new TiffBitsPerSample(12, 12, 12); /// /// The bits per sample for color images with 14 bits for each color channel. /// - public static readonly ushort[] BitsPerSampleRgb14Bit = { 14, 14, 14 }; + public static readonly TiffBitsPerSample BitsPerSampleRgb14Bit = new TiffBitsPerSample(14, 14, 14); /// /// The bits per sample for color images with 14 bits for each color channel. /// - public static readonly ushort[] BitsPerSampleRgb16Bit = { 16, 16, 16 }; + public static readonly TiffBitsPerSample BitsPerSampleRgb16Bit = new TiffBitsPerSample(16, 16, 16); /// /// The list of mimetypes that equate to a tiff. diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs index 71f6b5bf9f..b79730a127 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs @@ -1,96 +1,242 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using SixLabors.ImageSharp.Formats.Tiff.Constants; + namespace SixLabors.ImageSharp.Formats.Tiff { /// /// The number of bits per component. /// - public enum TiffBitsPerSample + public readonly struct TiffBitsPerSample : IEquatable { /// - /// The bits per samples is not known. - /// - Unknown = 0, - - /// - /// One bit per sample for bicolor images. - /// - Bit1, - - /// - /// Two bits per sample for grayscale images with 4 different levels of gray or paletted images with a palette of 4 colors. - /// - Bit2, - - /// - /// Four bits per sample for grayscale images with 16 different levels of gray or paletted images with a palette of 16 colors. - /// - Bit4, - - /// - /// Six bits per sample for grayscale images. - /// - Bit6, - - /// - /// Eight bits per sample for grayscale images with 256 different levels of gray or paletted images with a palette of 256 colors. - /// - Bit8, - - /// - /// Ten bits per sample for grayscale images. - /// - Bit10, - - /// - /// Twelve bits per sample for grayscale images. + /// The bits for the channel 0. /// - Bit12, + public readonly ushort Channel0; /// - /// Fourteen bits per sample for grayscale images. + /// The bits for the channel 1. /// - Bit14, + public readonly ushort Channel1; /// - /// Sixteen bits per sample for grayscale images. - /// - Bit16, - - /// - /// 6 bits per sample, each channel has 2 bits. - /// - Rgb222, - - /// - /// Twelve bits per sample, each channel has 4 bits. - /// - Rgb444, - - /// - /// 24 bits per sample, each color channel has 8 Bits. - /// - Rgb888, - - /// - /// Thirty bits per sample, each channel has 10 bits. - /// - Rgb101010, - - /// - /// Thirty six bits per sample, each channel has 12 bits. - /// - Rgb121212, - - /// - /// Forty two bits per sample, each channel has 14 bits. - /// - Rgb141414, - - /// - /// Forty eight bits per sample, each channel has 16 bits. - /// - Rgb161616, + /// The bits for the channel 2. + /// + public readonly ushort Channel2; + + /// + /// Initializes a new instance of the struct. + /// + /// The bits for the channel 0. + /// The bits for the channel 1. + /// The bits for the channel 2. + public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2) + { + this.Channel0 = (ushort)Numerics.Clamp(channel0, 1, 32); + this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32); + this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32); + } + + /// + /// Tries to parse a ushort array and convert it into a TiffBitsPerSample struct. + /// + /// The value to parse. + /// The tiff bits per sample. + /// True, if the value could be parsed. + public static bool TryParse(ushort[] value, out TiffBitsPerSample sample) + { + if (value is null || value.Length == 0) + { + sample = default; + return false; + } + + ushort c2; + ushort c1; + ushort c0; + switch (value.Length) + { + case 3: + c2 = value[2]; + c1 = value[1]; + c0 = value[0]; + break; + case 2: + c2 = 0; + c1 = value[1]; + c0 = value[0]; + break; + default: + c2 = 0; + c1 = 0; + c0 = value[0]; + break; + } + + sample = new TiffBitsPerSample(c0, c1, c2); + return true; + } + + /// + public override bool Equals(object obj) + => obj is TiffBitsPerSample sample && this.Equals(sample); + + /// + public bool Equals(TiffBitsPerSample other) + => this.Channel0 == other.Channel0 + && this.Channel1 == other.Channel1 + && this.Channel2 == other.Channel2; + + /// + public override int GetHashCode() + => HashCode.Combine(this.Channel0, this.Channel1, this.Channel2); + + /// + /// Converts the bits per sample struct to an ushort array. + /// + /// Bits per sample as ushort array. + public ushort[] ToArray() + { + if (this.Channel1 == 0) + { + return new[] { this.Channel0 }; + } + + if (this.Channel2 == 0) + { + return new[] { this.Channel0, this.Channel1 }; + } + + return new[] { this.Channel0, this.Channel1, this.Channel2 }; + } + + /// + /// Maps an array of bits per sample to a concrete struct value. + /// + /// The bits per sample array. + /// TiffBitsPerSample enum value. + public static TiffBitsPerSample? GetBitsPerSample(ushort[] bitsPerSample) + { + switch (bitsPerSample.Length) + { + case 3: + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb16Bit.Channel2 && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb16Bit.Channel1 && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb16Bit.Channel0) + { + return TiffConstants.BitsPerSampleRgb16Bit; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb14Bit.Channel2 && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb14Bit.Channel1 && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb14Bit.Channel0) + { + return TiffConstants.BitsPerSampleRgb14Bit; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb12Bit.Channel2 && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb12Bit.Channel1 && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb12Bit.Channel0) + { + return TiffConstants.BitsPerSampleRgb12Bit; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb10Bit.Channel2 && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb10Bit.Channel1 && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb10Bit.Channel0) + { + return TiffConstants.BitsPerSampleRgb10Bit; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit.Channel2 && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit.Channel1 && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit.Channel0) + { + return TiffConstants.BitsPerSampleRgb8Bit; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb4Bit.Channel2 && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb4Bit.Channel1 && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb4Bit.Channel0) + { + return TiffConstants.BitsPerSampleRgb4Bit; + } + + if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb2Bit.Channel2 && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb2Bit.Channel1 && + bitsPerSample[0] == TiffConstants.BitsPerSampleRgb2Bit.Channel0) + { + return TiffConstants.BitsPerSampleRgb2Bit; + } + + break; + + case 1: + if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit.Channel0) + { + return TiffConstants.BitsPerSample1Bit; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample2Bit.Channel0) + { + return TiffConstants.BitsPerSample2Bit; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit.Channel0) + { + return TiffConstants.BitsPerSample4Bit; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample6Bit.Channel0) + { + return TiffConstants.BitsPerSample6Bit; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit.Channel0) + { + return TiffConstants.BitsPerSample8Bit; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample10Bit.Channel0) + { + return TiffConstants.BitsPerSample10Bit; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample12Bit.Channel0) + { + return TiffConstants.BitsPerSample12Bit; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample14Bit.Channel0) + { + return TiffConstants.BitsPerSample14Bit; + } + + if (bitsPerSample[0] == TiffConstants.BitsPerSample16Bit.Channel0) + { + return TiffConstants.BitsPerSample16Bit; + } + + break; + } + + return null; + } + + /// + /// Gets the bits per pixel for the given bits per sample. + /// + /// Bits per pixel. + public TiffBitsPerPixel BitsPerPixel() + { + int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2; + return (TiffBitsPerPixel)bitsPerPixel; + } + + /// + public override string ToString() + => $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})"; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs deleted file mode 100644 index 5ec1331b31..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Formats.Tiff.Constants; - -namespace SixLabors.ImageSharp.Formats.Tiff -{ - internal static class TiffBitsPerSampleExtensions - { - /// - /// Gets the bits per channel array for a given BitsPerSample value, e,g, for RGB888: [8, 8, 8] - /// - /// The tiff bits per sample. - /// Bits per sample array. - public static ushort[] BitsPerChannel(this TiffBitsPerSample tiffBitsPerSample) - { - switch (tiffBitsPerSample) - { - case TiffBitsPerSample.Bit1: - return TiffConstants.BitsPerSample1Bit; - case TiffBitsPerSample.Bit2: - return TiffConstants.BitsPerSample2Bit; - case TiffBitsPerSample.Bit4: - return TiffConstants.BitsPerSample4Bit; - case TiffBitsPerSample.Bit6: - return TiffConstants.BitsPerSample6Bit; - case TiffBitsPerSample.Bit8: - return TiffConstants.BitsPerSample8Bit; - case TiffBitsPerSample.Bit10: - return TiffConstants.BitsPerSample10Bit; - case TiffBitsPerSample.Bit12: - return TiffConstants.BitsPerSample12Bit; - case TiffBitsPerSample.Bit14: - return TiffConstants.BitsPerSample14Bit; - case TiffBitsPerSample.Bit16: - return TiffConstants.BitsPerSample16Bit; - case TiffBitsPerSample.Rgb222: - return TiffConstants.BitsPerSampleRgb2Bit; - case TiffBitsPerSample.Rgb444: - return TiffConstants.BitsPerSampleRgb4Bit; - case TiffBitsPerSample.Rgb888: - return TiffConstants.BitsPerSampleRgb8Bit; - case TiffBitsPerSample.Rgb101010: - return TiffConstants.BitsPerSampleRgb10Bit; - case TiffBitsPerSample.Rgb121212: - return TiffConstants.BitsPerSampleRgb12Bit; - case TiffBitsPerSample.Rgb141414: - return TiffConstants.BitsPerSampleRgb14Bit; - case TiffBitsPerSample.Rgb161616: - return TiffConstants.BitsPerSampleRgb16Bit; - default: - return Array.Empty(); - } - } - - /// - /// Maps an array of bits per sample to a concrete enum value. - /// - /// The bits per sample array. - /// TiffBitsPerSample enum value. - public static TiffBitsPerSample GetBitsPerSample(this ushort[] bitsPerSample) - { - switch (bitsPerSample.Length) - { - case 3: - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb16Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb16Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb16Bit[0]) - { - return TiffBitsPerSample.Rgb161616; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb14Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb14Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb14Bit[0]) - { - return TiffBitsPerSample.Rgb141414; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb12Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb12Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb12Bit[0]) - { - return TiffBitsPerSample.Rgb121212; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb10Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb10Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb10Bit[0]) - { - return TiffBitsPerSample.Rgb101010; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit[0]) - { - return TiffBitsPerSample.Rgb888; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb4Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb4Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb4Bit[0]) - { - return TiffBitsPerSample.Rgb444; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb2Bit[2] && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb2Bit[1] && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb2Bit[0]) - { - return TiffBitsPerSample.Rgb222; - } - - break; - - case 1: - if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit[0]) - { - return TiffBitsPerSample.Bit1; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample2Bit[0]) - { - return TiffBitsPerSample.Bit2; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit[0]) - { - return TiffBitsPerSample.Bit4; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample6Bit[0]) - { - return TiffBitsPerSample.Bit6; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit[0]) - { - return TiffBitsPerSample.Bit8; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample10Bit[0]) - { - return TiffBitsPerSample.Bit10; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample12Bit[0]) - { - return TiffBitsPerSample.Bit12; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample14Bit[0]) - { - return TiffBitsPerSample.Bit14; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample16Bit[0]) - { - return TiffBitsPerSample.Bit16; - } - - break; - } - - return TiffBitsPerSample.Unknown; - } - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 1efc826027..0699359c07 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; - options.BitsPerSample = frameMetadata.BitsPerSample != null ? frameMetadata.BitsPerSample?.BitsPerChannel() : Array.Empty(); + options.BitsPerSample = frameMetadata.BitsPerSample != null ? frameMetadata.BitsPerSample?.ToArray() : Array.Empty(); options.ParseColorType(exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile); diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index 9bc0792c40..43a0868496 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -318,34 +318,34 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffPhotometricInterpretation.PaletteColor: if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit4) { - return TiffConstants.BitsPerSample4Bit; + return TiffConstants.BitsPerSample4Bit.ToArray(); } else { - return TiffConstants.BitsPerSample8Bit; + return TiffConstants.BitsPerSample8Bit.ToArray(); } case TiffPhotometricInterpretation.Rgb: - return TiffConstants.BitsPerSampleRgb8Bit; + return TiffConstants.BitsPerSampleRgb8Bit.ToArray(); case TiffPhotometricInterpretation.WhiteIsZero: if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) { - return TiffConstants.BitsPerSample1Bit; + return TiffConstants.BitsPerSample1Bit.ToArray(); } - return TiffConstants.BitsPerSample8Bit; + return TiffConstants.BitsPerSample8Bit.ToArray(); case TiffPhotometricInterpretation.BlackIsZero: if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) { - return TiffConstants.BitsPerSample1Bit; + return TiffConstants.BitsPerSample1Bit.ToArray(); } - return TiffConstants.BitsPerSample8Bit; + return TiffConstants.BitsPerSample8Bit.ToArray(); default: - return TiffConstants.BitsPerSampleRgb8Bit; + return TiffConstants.BitsPerSampleRgb8Bit.ToArray(); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index 62e9fb4e21..76db6e75f7 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } /// - /// Parses the given Exif profile to populate the properties of the tiff frame meta data.. + /// Parses the given Exif profile to populate the properties of the tiff frame meta data. /// /// The tiff frame meta data. /// The Exif profile containing tiff frame directory tags. @@ -77,8 +77,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff { if (profile != null) { - meta.BitsPerSample = profile.GetValue(ExifTag.BitsPerSample) != null ? profile.GetValue(ExifTag.BitsPerSample)?.Value.GetBitsPerSample() : null; - meta.BitsPerPixel = BitsPerPixelFromBitsPerSample(meta.BitsPerSample?.BitsPerChannel()); + meta.BitsPerSample = profile.GetValue(ExifTag.BitsPerSample) != null ? TiffBitsPerSample.GetBitsPerSample(profile.GetValue(ExifTag.BitsPerSample)?.Value) : null; + meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel(); meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value; meta.PhotometricInterpretation = (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value; meta.Predictor = (TiffPredictor?)profile.GetValue(ExifTag.Predictor)?.Value; @@ -90,27 +90,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff } } - /// - /// Gets the bits per pixel for the given bits per sample. - /// - /// The tiff bits per sample. - /// Bits per pixel. - private static TiffBitsPerPixel? BitsPerPixelFromBitsPerSample(ushort[] bitsPerSample) - { - if (bitsPerSample == null) - { - return null; - } - - int bitsPerPixel = 0; - foreach (ushort bits in bitsPerSample) - { - bitsPerPixel += bits; - } - - return (TiffBitsPerPixel)bitsPerPixel; - } - /// public IDeepCloneable DeepClone() => new TiffFrameMetadata(this); } From 9891a2ef3b0249cf795fefc6ab4d4ece3d0d9c5b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 12 Jun 2021 16:51:54 +0200 Subject: [PATCH 08/33] Remove not needed GetBitsPerSample method --- .../Formats/Tiff/TiffBitsPerSample.cs | 114 ------------------ .../Formats/Tiff/TiffFrameMetadata.cs | 7 +- 2 files changed, 6 insertions(+), 115 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs index b79730a127..bdf5a20c1d 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Tiff.Constants; namespace SixLabors.ImageSharp.Formats.Tiff { @@ -112,119 +111,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff return new[] { this.Channel0, this.Channel1, this.Channel2 }; } - /// - /// Maps an array of bits per sample to a concrete struct value. - /// - /// The bits per sample array. - /// TiffBitsPerSample enum value. - public static TiffBitsPerSample? GetBitsPerSample(ushort[] bitsPerSample) - { - switch (bitsPerSample.Length) - { - case 3: - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb16Bit.Channel2 && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb16Bit.Channel1 && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb16Bit.Channel0) - { - return TiffConstants.BitsPerSampleRgb16Bit; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb14Bit.Channel2 && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb14Bit.Channel1 && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb14Bit.Channel0) - { - return TiffConstants.BitsPerSampleRgb14Bit; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb12Bit.Channel2 && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb12Bit.Channel1 && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb12Bit.Channel0) - { - return TiffConstants.BitsPerSampleRgb12Bit; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb10Bit.Channel2 && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb10Bit.Channel1 && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb10Bit.Channel0) - { - return TiffConstants.BitsPerSampleRgb10Bit; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit.Channel2 && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit.Channel1 && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit.Channel0) - { - return TiffConstants.BitsPerSampleRgb8Bit; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb4Bit.Channel2 && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb4Bit.Channel1 && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb4Bit.Channel0) - { - return TiffConstants.BitsPerSampleRgb4Bit; - } - - if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb2Bit.Channel2 && - bitsPerSample[1] == TiffConstants.BitsPerSampleRgb2Bit.Channel1 && - bitsPerSample[0] == TiffConstants.BitsPerSampleRgb2Bit.Channel0) - { - return TiffConstants.BitsPerSampleRgb2Bit; - } - - break; - - case 1: - if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit.Channel0) - { - return TiffConstants.BitsPerSample1Bit; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample2Bit.Channel0) - { - return TiffConstants.BitsPerSample2Bit; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit.Channel0) - { - return TiffConstants.BitsPerSample4Bit; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample6Bit.Channel0) - { - return TiffConstants.BitsPerSample6Bit; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit.Channel0) - { - return TiffConstants.BitsPerSample8Bit; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample10Bit.Channel0) - { - return TiffConstants.BitsPerSample10Bit; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample12Bit.Channel0) - { - return TiffConstants.BitsPerSample12Bit; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample14Bit.Channel0) - { - return TiffConstants.BitsPerSample14Bit; - } - - if (bitsPerSample[0] == TiffConstants.BitsPerSample16Bit.Channel0) - { - return TiffConstants.BitsPerSample16Bit; - } - - break; - } - - return null; - } - /// /// Gets the bits per pixel for the given bits per sample. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index 76db6e75f7..e2a55b94bb 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -77,7 +77,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff { if (profile != null) { - meta.BitsPerSample = profile.GetValue(ExifTag.BitsPerSample) != null ? TiffBitsPerSample.GetBitsPerSample(profile.GetValue(ExifTag.BitsPerSample)?.Value) : null; + ushort[] bitsPerSampleValue = profile.GetValue(ExifTag.BitsPerSample)?.Value; + if (bitsPerSampleValue != null && TiffBitsPerSample.TryParse(bitsPerSampleValue, out TiffBitsPerSample bitsPerSample)) + { + meta.BitsPerSample = bitsPerSample; + } + meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel(); meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value; meta.PhotometricInterpretation = (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value; From 3b8bed5e9966d007d72275052431c39a76265702 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 12 Jun 2021 16:53:59 +0200 Subject: [PATCH 09/33] Remove not used constants --- .../Formats/Tiff/Constants/TiffConstants.cs | 90 ------------------- 1 file changed, 90 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs index 8d9fb94a44..b545451412 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -40,41 +40,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants /// public const int RowsPerStripInfinity = 2147483647; - /// - /// Size (in bytes) of the TIFF file header. - /// - public const int SizeOfTiffHeader = 8; - - /// - /// Size (in bytes) of each individual TIFF IFD entry - /// - public const int SizeOfIfdEntry = 12; - - /// - /// Size (in bytes) of the Short and SShort data types - /// - public const int SizeOfShort = 2; - - /// - /// Size (in bytes) of the Long and SLong data types - /// - public const int SizeOfLong = 4; - /// /// Size (in bytes) of the Rational and SRational data types /// public const int SizeOfRational = 8; - /// - /// Size (in bytes) of the Float data type - /// - public const int SizeOfFloat = 4; - - /// - /// Size (in bytes) of the Double data type - /// - public const int SizeOfDouble = 8; - /// /// The default strip size is 8k. /// @@ -85,81 +55,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants /// public static readonly TiffBitsPerSample BitsPerSample1Bit = new TiffBitsPerSample(1, 0, 0); - /// - /// The bits per sample for images with a 2 color palette. - /// - public static readonly TiffBitsPerSample BitsPerSample2Bit = new TiffBitsPerSample(2, 0, 0); - /// /// The bits per sample for images with a 4 color palette. /// public static readonly TiffBitsPerSample BitsPerSample4Bit = new TiffBitsPerSample(4, 0, 0); - /// - /// The bits per sample for 6 bit gray images. - /// - public static readonly TiffBitsPerSample BitsPerSample6Bit = new TiffBitsPerSample(6, 0, 0); - /// /// The bits per sample for 8 bit images. /// public static readonly TiffBitsPerSample BitsPerSample8Bit = new TiffBitsPerSample(8, 0, 0); - /// - /// The bits per sample for 10 bit gray images. - /// - public static readonly TiffBitsPerSample BitsPerSample10Bit = new TiffBitsPerSample(10, 0, 0); - - /// - /// The bits per sample for 12 bit gray images. - /// - public static readonly TiffBitsPerSample BitsPerSample12Bit = new TiffBitsPerSample(12, 0, 0); - - /// - /// The bits per sample for 14 bit gray images. - /// - public static readonly TiffBitsPerSample BitsPerSample14Bit = new TiffBitsPerSample(14, 0, 0); - - /// - /// The bits per sample for 16 bit gray images. - /// - public static readonly TiffBitsPerSample BitsPerSample16Bit = new TiffBitsPerSample(16, 0, 0); - - /// - /// The bits per sample for color images with 2 bits for each color channel. - /// - public static readonly TiffBitsPerSample BitsPerSampleRgb2Bit = new TiffBitsPerSample(2, 2, 2); - - /// - /// The bits per sample for color images with 4 bits for each color channel. - /// - public static readonly TiffBitsPerSample BitsPerSampleRgb4Bit = new TiffBitsPerSample(4, 4, 4); - /// /// The bits per sample for color images with 8 bits for each color channel. /// public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new TiffBitsPerSample(8, 8, 8); - /// - /// The bits per sample for color images with 10 bits for each color channel. - /// - public static readonly TiffBitsPerSample BitsPerSampleRgb10Bit = new TiffBitsPerSample(10, 10, 10); - - /// - /// The bits per sample for color images with 12 bits for each color channel. - /// - public static readonly TiffBitsPerSample BitsPerSampleRgb12Bit = new TiffBitsPerSample(12, 12, 12); - - /// - /// The bits per sample for color images with 14 bits for each color channel. - /// - public static readonly TiffBitsPerSample BitsPerSampleRgb14Bit = new TiffBitsPerSample(14, 14, 14); - - /// - /// The bits per sample for color images with 14 bits for each color channel. - /// - public static readonly TiffBitsPerSample BitsPerSampleRgb16Bit = new TiffBitsPerSample(16, 16, 16); - /// /// The list of mimetypes that equate to a tiff. /// From 22f4b7c12cc9041254b4b7960bd2d2da72db28eb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 12 Jun 2021 19:18:23 +0200 Subject: [PATCH 10/33] Remove not needed null check for bits per sample --- src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index e2a55b94bb..002dbf039e 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -77,8 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { if (profile != null) { - ushort[] bitsPerSampleValue = profile.GetValue(ExifTag.BitsPerSample)?.Value; - if (bitsPerSampleValue != null && TiffBitsPerSample.TryParse(bitsPerSampleValue, out TiffBitsPerSample bitsPerSample)) + if (TiffBitsPerSample.TryParse(profile.GetValue(ExifTag.BitsPerSample)?.Value, out TiffBitsPerSample bitsPerSample)) { meta.BitsPerSample = bitsPerSample; } From a1b16e39aab5f3c597f355ae93c3656f40f68f6d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 13 Jun 2021 13:06:08 +0200 Subject: [PATCH 11/33] add failing test --- .../TestUtilities/Tests/TestEnvironmentTests.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 84b9297295..aae5cd6846 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -3,7 +3,7 @@ using System; using System.IO; - +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; @@ -114,5 +114,16 @@ namespace SixLabors.ImageSharp.Tests IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); Assert.IsType(expectedDecoderType, decoder); } + + [Fact] + public void RemoteExecutor_FailingRemoteTestShouldFailLocalTest() + { + static void FailingCode() + { + Assert.False(true); + } + + Assert.ThrowsAny(() => RemoteExecutor.Invoke(FailingCode).Dispose()); + } } } From 2ec796ff8ffee8f4db6bc0f86201342f1fca641e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 13 Jun 2021 13:10:42 +0200 Subject: [PATCH 12/33] Change BitsPerSample from ushort[] to TiffBitsPerSample struct --- .../BlackIsZeroTiffColor{TPixel}.cs | 4 +- .../PaletteTiffColor{TPixel}.cs | 4 +- .../RgbPlanarTiffColor{TPixel}.cs | 8 +- .../RgbTiffColor{TPixel}.cs | 8 +- .../TiffColorDecoderFactory{TPixel}.cs | 79 +++++++++---------- .../WhiteIsZeroTiffColor{TPixel}.cs | 4 +- .../Formats/Tiff/TiffBitsPerSample.cs | 12 ++- .../Formats/Tiff/TiffDecoderCore.cs | 24 +++++- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 17 ++-- .../BlackIsZeroTiffColorTests.cs | 6 +- .../PaletteTiffColorTests.cs | 11 +-- .../RgbPlanarTiffColorTests.cs | 46 +++++------ .../RgbTiffColorTests.cs | 48 +++++------ .../WhiteIsZeroTiffColorTests.cs | 6 +- 14 files changed, 150 insertions(+), 127 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs index 83cef8e758..a4e5e45dfb 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs @@ -19,9 +19,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation private readonly float factor; - public BlackIsZeroTiffColor(ushort[] bitsPerSample) + public BlackIsZeroTiffColor(TiffBitsPerSample bitsPerSample) { - this.bitsPerSample0 = bitsPerSample[0]; + this.bitsPerSample0 = bitsPerSample.Channel0; this.factor = (1 << this.bitsPerSample0) - 1.0f; } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs index 7ed25f8221..796227953e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -21,9 +21,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation /// The number of bits per sample for each pixel. /// The RGB color lookup table to use for decoding the image. - public PaletteTiffColor(ushort[] bitsPerSample, ushort[] colorMap) + public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap) { - this.bitsPerSample0 = bitsPerSample[0]; + this.bitsPerSample0 = bitsPerSample.Channel0; int colorCount = 1 << this.bitsPerSample0; this.palette = GeneratePalette(colorMap, colorCount); } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs index b40158fcee..8dda0cf38f 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs @@ -26,11 +26,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation private readonly ushort bitsPerSampleB; - public RgbPlanarTiffColor(ushort[] bitsPerSample) + public RgbPlanarTiffColor(TiffBitsPerSample bitsPerSample) { - this.bitsPerSampleR = bitsPerSample[0]; - this.bitsPerSampleG = bitsPerSample[1]; - this.bitsPerSampleB = bitsPerSample[2]; + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs index 816ba67b76..259bb8efa7 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs @@ -27,11 +27,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation private readonly ushort bitsPerSampleB; - public RgbTiffColor(ushort[] bitsPerSample) + public RgbTiffColor(TiffBitsPerSample bitsPerSample) { - this.bitsPerSampleR = bitsPerSample[0]; - this.bitsPerSampleG = bitsPerSample[1]; - this.bitsPerSampleB = bitsPerSample[2]; + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 4ca7ed9159..36d2ab746e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -8,127 +8,125 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation internal static class TiffColorDecoderFactory where TPixel : unmanaged, IPixel { - public static TiffBaseColorDecoder Create(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap) + public static TiffBaseColorDecoder Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap) { switch (colorType) { case TiffColorType.WhiteIsZero: - DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample"); + DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new WhiteIsZeroTiffColor(bitsPerSample); case TiffColorType.WhiteIsZero1: - DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample"); + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new WhiteIsZero1TiffColor(); case TiffColorType.WhiteIsZero4: - DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample"); + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new WhiteIsZero4TiffColor(); case TiffColorType.WhiteIsZero8: - DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample"); + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new WhiteIsZero8TiffColor(); case TiffColorType.BlackIsZero: - DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample"); + DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new BlackIsZeroTiffColor(bitsPerSample); case TiffColorType.BlackIsZero1: - DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample"); + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new BlackIsZero1TiffColor(); case TiffColorType.BlackIsZero4: - DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample"); + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new BlackIsZero4TiffColor(); case TiffColorType.BlackIsZero8: - DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample"); + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new BlackIsZero8TiffColor(); case TiffColorType.Rgb: - DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); case TiffColorType.Rgb222: DebugGuard.IsTrue( - bitsPerSample.Length == 3 - && bitsPerSample[2] == 2 - && bitsPerSample[1] == 2 - && bitsPerSample[0] == 2, + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 2 + && bitsPerSample.Channel1 == 2 + && bitsPerSample.Channel0 == 2, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); case TiffColorType.Rgb444: DebugGuard.IsTrue( - bitsPerSample.Length == 3 - && bitsPerSample[2] == 4 - && bitsPerSample[1] == 4 - && bitsPerSample[0] == 4, + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 4 + && bitsPerSample.Channel1 == 4 + && bitsPerSample.Channel0 == 4, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb444TiffColor(); case TiffColorType.Rgb888: DebugGuard.IsTrue( - bitsPerSample.Length == 3 - && bitsPerSample[2] == 8 - && bitsPerSample[1] == 8 - && bitsPerSample[0] == 8, + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 8 + && bitsPerSample.Channel1 == 8 + && bitsPerSample.Channel0 == 8, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new Rgb888TiffColor(); case TiffColorType.Rgb101010: DebugGuard.IsTrue( - bitsPerSample.Length == 3 - && bitsPerSample[2] == 10 - && bitsPerSample[1] == 10 - && bitsPerSample[0] == 10, + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 10 + && bitsPerSample.Channel1 == 10 + && bitsPerSample.Channel0 == 10, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); case TiffColorType.Rgb121212: DebugGuard.IsTrue( - bitsPerSample.Length == 3 - && bitsPerSample[2] == 12 - && bitsPerSample[1] == 12 - && bitsPerSample[0] == 12, + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 12 + && bitsPerSample.Channel1 == 12 + && bitsPerSample.Channel0 == 12, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); case TiffColorType.Rgb141414: DebugGuard.IsTrue( - bitsPerSample.Length == 3 - && bitsPerSample[2] == 14 - && bitsPerSample[1] == 14 - && bitsPerSample[0] == 14, + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 14 + && bitsPerSample.Channel1 == 14 + && bitsPerSample.Channel0 == 14, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); case TiffColorType.Rgb161616: DebugGuard.IsTrue( - bitsPerSample.Length == 3 - && bitsPerSample[2] == 16 - && bitsPerSample[1] == 16 - && bitsPerSample[0] == 16, + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 16 + && bitsPerSample.Channel1 == 16 + && bitsPerSample.Channel0 == 16, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbTiffColor(bitsPerSample); case TiffColorType.PaletteColor: - DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); DebugGuard.NotNull(colorMap, "colorMap"); return new PaletteTiffColor(bitsPerSample, colorMap); @@ -137,12 +135,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation } } - public static RgbPlanarTiffColor CreatePlanar(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap) + public static RgbPlanarTiffColor CreatePlanar(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap) { switch (colorType) { case TiffColorType.RgbPlanar: - DebugGuard.NotNull(bitsPerSample, "bitsPerSample"); DebugGuard.IsTrue(colorMap == null, "colorMap"); return new RgbPlanarTiffColor(bitsPerSample); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs index 697fe2f073..04b6f98e50 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs @@ -19,9 +19,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation private readonly float factor; - public WhiteIsZeroTiffColor(ushort[] bitsPerSample) + public WhiteIsZeroTiffColor(TiffBitsPerSample bitsPerSample) { - this.bitsPerSample0 = bitsPerSample[0]; + this.bitsPerSample0 = bitsPerSample.Channel0; this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f; } diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs index bdf5a20c1d..8fd26ac13d 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs @@ -25,6 +25,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public readonly ushort Channel2; + /// + /// The number of channels. + /// + public readonly byte Channels; + /// /// Initializes a new instance of the struct. /// @@ -33,9 +38,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The bits for the channel 2. public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2) { - this.Channel0 = (ushort)Numerics.Clamp(channel0, 1, 32); + this.Channel0 = (ushort)Numerics.Clamp(channel0, 0, 32); this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32); this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32); + + this.Channels = 0; + this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0); + this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0); + this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0); } /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 294407ef97..5ce696118d 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Gets or sets the bits per sample. /// - public ushort[] BitsPerSample { get; set; } + public TiffBitsPerSample BitsPerSample { get; set; } /// /// Gets or sets the bits per pixel. @@ -198,7 +198,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The size (in bytes) of the required pixel buffer. private int CalculateStripBufferSize(int width, int height, int plane = -1) { - int bitsPerPixel; + DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane)); + + int bitsPerPixel = 0; if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) { @@ -207,7 +209,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - bitsPerPixel = this.BitsPerSample[plane]; + switch (plane) + { + case 0: + bitsPerPixel = this.BitsPerSample.Channel0; + break; + case 1: + bitsPerPixel = this.BitsPerSample.Channel1; + break; + case 2: + bitsPerPixel = this.BitsPerSample.Channel2; + break; + default: + TiffThrowHelper.ThrowNotSupported("More then 3 color channels are not supported"); + break; + } } int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; @@ -225,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts) where TPixel : unmanaged, IPixel { - int stripsPerPixel = this.BitsPerSample.Length; + int stripsPerPixel = this.BitsPerSample.Channels; int stripsPerPlane = stripOffsets.Length / stripsPerPixel; int bitsPerPixel = this.BitsPerPixel; diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 0699359c07..288f01cd1c 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Linq; using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Formats.Tiff.Constants; @@ -69,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; - options.BitsPerSample = frameMetadata.BitsPerSample != null ? frameMetadata.BitsPerSample?.ToArray() : Array.Empty(); + options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0); options.ParseColorType(exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile); @@ -99,12 +98,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff { case TiffPhotometricInterpretation.WhiteIsZero: { - if (options.BitsPerSample.Length != 1) + if (options.BitsPerSample.Channels != 1) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } - ushort bitsPerChannel = options.BitsPerSample[0]; + ushort bitsPerChannel = options.BitsPerSample.Channel0; if (bitsPerChannel > 16) { TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); @@ -142,12 +141,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffPhotometricInterpretation.BlackIsZero: { - if (options.BitsPerSample.Length != 1) + if (options.BitsPerSample.Channels != 1) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } - ushort bitsPerChannel = options.BitsPerSample[0]; + ushort bitsPerChannel = options.BitsPerSample.Channel0; if (bitsPerChannel > 16) { TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); @@ -185,14 +184,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffPhotometricInterpretation.Rgb: { - if (options.BitsPerSample.Length != 3) + if (options.BitsPerSample.Channels != 3) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) { - ushort bitsPerChannel = options.BitsPerSample[0]; + ushort bitsPerChannel = options.BitsPerSample.Channel0; switch (bitsPerChannel) { case 16: @@ -238,7 +237,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; if (options.ColorMap != null) { - if (options.BitsPerSample.Length != 1) + if (options.BitsPerSample.Channels != 1) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs index 579ee02901..769ab850e4 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; - +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.PixelFormats; @@ -154,11 +154,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation [MemberData(nameof(BilevelData))] [MemberData(nameof(Grayscale4_Data))] [MemberData(nameof(Grayscale8_Data))] - public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) { AssertDecode(expectedResult, pixels => { - new BlackIsZeroTiffColor(new[] { (ushort)bitsPerSample }).Decode(inputData, pixels, left, top, width, height); + new BlackIsZeroTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0)).Decode(inputData, pixels, left, top, width, height); }); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs index 0da1d8bbd4..e368cd5f1e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; - +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.PixelFormats; @@ -83,10 +83,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation [Theory] [MemberData(nameof(Palette4Data))] [MemberData(nameof(Palette8Data))] - public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) => AssertDecode(expectedResult, pixels => - { - new PaletteTiffColor(new[] { bitsPerSample }, colorMap).Decode(inputData, pixels, left, top, width, height); - }); + public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) + => AssertDecode(expectedResult, pixels => + { + new PaletteTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0), colorMap).Decode(inputData, pixels, left, top, width, height); + }); private static uint[][] GeneratePalette(int count) { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs index abfae6ab40..e9c73a6683 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; - +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -101,17 +101,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation { get { - yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Rgb4Result4X4 }; - yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; - - yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Rgb4Result3X4 }; - yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4 }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4 }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; } } @@ -170,11 +170,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation { get { - yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Rgb8Result4X4 }; - yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4 }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; } } @@ -230,11 +230,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation { get { - yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Rgb484Result4X4 }; - yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4 }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; } } @@ -242,7 +242,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation [MemberData(nameof(Rgb4Data))] [MemberData(nameof(Rgb8Data))] [MemberData(nameof(Rgb484_Data))] - public void Decode_WritesPixelData(byte[][] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + public void Decode_WritesPixelData(byte[][] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) { AssertDecode(expectedResult, pixels => { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs index 4abde8f17e..9adf59e484 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; - +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.PixelFormats; @@ -63,17 +63,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation { get { - yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Rgb4Result4X4 }; - yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; - - yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Rgb4Result3X4 }; - yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4 }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4 }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; } } @@ -111,11 +111,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation { get { - yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Rgb8Result4X4 }; - yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4 }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; } } @@ -153,11 +153,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation { get { - yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Rgb484Result4X4 }; - yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4 }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; } } @@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation [MemberData(nameof(Rgb4Data))] [MemberData(nameof(Rgb8Data))] [MemberData(nameof(Rgb484Data))] - public void Decode_WritesPixelData(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + public void Decode_WritesPixelData(byte[] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) { AssertDecode(expectedResult, pixels => { @@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation [Theory] [MemberData(nameof(Rgb8Data))] - public void Decode_WritesPixelData_8Bit(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + public void Decode_WritesPixelData_8Bit(byte[] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) { AssertDecode(expectedResult, pixels => { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs index 620fddd7d0..1d3304e4c8 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; - +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.PixelFormats; @@ -154,11 +154,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation [MemberData(nameof(BilevelData))] [MemberData(nameof(Grayscale4Data))] [MemberData(nameof(Grayscale8Data))] - public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) { AssertDecode(expectedResult, pixels => { - new WhiteIsZeroTiffColor(new[] { (ushort)bitsPerSample }).Decode(inputData, pixels, left, top, width, height); + new WhiteIsZeroTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0)).Decode(inputData, pixels, left, top, width, height); }); } From cee9140e75dff38172f53a2d01125fad14380382 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 13 Jun 2021 13:20:31 +0200 Subject: [PATCH 13/33] update Microsoft.DotNet.RemoteExecutor & XUnitExtensions --- tests/Directory.Build.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index af86f49b09..9c17881452 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -22,8 +22,8 @@ - - + + From 488b486d3ecd21d773f3c1c0374f4475b08a98ea Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 13 Jun 2021 16:01:17 +0200 Subject: [PATCH 14/33] fix BokehBlurFilterProcessor_Bounded --- .../Processing/Processors/Convolution/BokehBlurTest.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs index 2351cbb917..4ab053a310 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -11,6 +11,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using Xunit.Abstractions; @@ -154,8 +155,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution appendSourceFileOrDescription: false); [Theory] - [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] - public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value) + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.AllowAll)] + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.DisableSSE41)] + public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value, HwIntrinsics intrinsicsFilter) { static void RunTest(string arg1, string arg2) { @@ -173,12 +175,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds); }, testOutputDetails: value.ToString(), + ImageComparer.TolerantPercentage(0.05f), appendPixelTypeToFileName: false); } FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.DisableSSE41, + intrinsicsFilter, provider, value); } From 73d273d4257df5db6890f0372cea5a2fa1f1b3d0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 13 Jun 2021 16:37:47 +0200 Subject: [PATCH 15/33] skip RemoteExecutor_FailingRemoteTestShouldFailLocalTest on 32 bit Framework --- .../TestUtilities/Tests/TestEnvironmentTests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index aae5cd6846..0645b7996b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -118,6 +118,14 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void RemoteExecutor_FailingRemoteTestShouldFailLocalTest() { + if (TestEnvironment.IsFramework && !TestEnvironment.Is64BitProcess) + { + // The RemoteExecutor fix does not work well with the "dotnet xunit" call + // we use with Framework on 32 bit: + // https://github.com/SixLabors/ImageSharp/blob/381dff8640b721a34b1227c970fcf6ad6c5e3e72/ci-test.ps1#L30 + return; + } + static void FailingCode() { Assert.False(true); From 816a379218bd4e0e24cac2adfff3501d392fb8db Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 13 Jun 2021 16:40:58 +0200 Subject: [PATCH 16/33] use ConditionalFact to skip the test --- .../TestUtilities/Tests/TestEnvironmentTests.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 0645b7996b..60f4101cc9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -115,17 +115,14 @@ namespace SixLabors.ImageSharp.Tests Assert.IsType(expectedDecoderType, decoder); } - [Fact] + // The RemoteExecutor fix does not work well with the "dotnet xunit" call + // we use with Framework on 32 bit: + // https://github.com/SixLabors/ImageSharp/blob/381dff8640b721a34b1227c970fcf6ad6c5e3e72/ci-test.ps1#L30 + public static bool IsNot32BitNetFramework = !TestEnvironment.IsFramework || TestEnvironment.Is64BitProcess; + + [ConditionalFact(nameof(IsNot32BitNetFramework))] public void RemoteExecutor_FailingRemoteTestShouldFailLocalTest() { - if (TestEnvironment.IsFramework && !TestEnvironment.Is64BitProcess) - { - // The RemoteExecutor fix does not work well with the "dotnet xunit" call - // we use with Framework on 32 bit: - // https://github.com/SixLabors/ImageSharp/blob/381dff8640b721a34b1227c970fcf6ad6c5e3e72/ci-test.ps1#L30 - return; - } - static void FailingCode() { Assert.False(true); From 255802cd9b1e0e72b5c3399b8b19070971dd5328 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 13 Jun 2021 17:07:15 +0200 Subject: [PATCH 17/33] improve comment --- .../TestUtilities/Tests/TestEnvironmentTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 60f4101cc9..05f4f032bf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -115,8 +115,7 @@ namespace SixLabors.ImageSharp.Tests Assert.IsType(expectedDecoderType, decoder); } - // The RemoteExecutor fix does not work well with the "dotnet xunit" call - // we use with Framework on 32 bit: + // RemoteExecutor does not work with "dotnet xunit" used to run tests on 32 bit .NET Framework: // https://github.com/SixLabors/ImageSharp/blob/381dff8640b721a34b1227c970fcf6ad6c5e3e72/ci-test.ps1#L30 public static bool IsNot32BitNetFramework = !TestEnvironment.IsFramework || TestEnvironment.Is64BitProcess; From 3eb43bbda1d87b08ac047c3bde7271b28615ee86 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 14 Jun 2021 17:11:13 +0200 Subject: [PATCH 18/33] Avoid buffer2D.GetSingleSpan() and use GetPixelRowSpan instead --- .../Compressors/T4BitCompressor.cs | 4 +- .../Writers/TiffBaseColorWriter{TPixel}.cs | 4 -- .../Tiff/Writers/TiffBiColorWriter{TPixel}.cs | 38 ++++++++++++------ .../TiffCompositeColorWriter{TPixel}.cs | 19 +++++++-- .../Tiff/Writers/TiffPaletteWriter{TPixel}.cs | 40 ++++++++++++++----- 5 files changed, 73 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs index 3e9b7f4e63..30da537eb2 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs @@ -213,7 +213,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors this.compressedDataBuffer = this.Allocator.Allocate(maxNeededBytes); } - /// Writes a image compressed with CCITT T4 to the stream. + /// + /// Writes a image compressed with CCITT T4 to the stream. + /// /// The pixels as 8-bit gray array. /// The strip height. public override void CompressStrip(Span pixelsAsGray, int height) diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs index 232daa18d6..7100fe9fc8 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs @@ -79,10 +79,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers this.Dispose(true); } - protected static Span GetStripPixels(Buffer2D buffer2D, int y, int height) - where T : struct - => buffer2D.GetSingleSpan().Slice(y * buffer2D.Width, height * buffer2D.Width); - protected abstract void EncodeStrip(int y, int height, TiffBaseCompressor compressor); /// diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs index be5c837eae..662e729ef9 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -36,38 +36,50 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers /// protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) { - this.pixelsAsGray ??= this.MemoryAllocator.Allocate(height * this.Image.Width); - - Span pixelAsGraySpan = this.pixelsAsGray.Slice(0, height * this.Image.Width); - - Span pixelsBlackWhite = GetStripPixels(this.imageBlackWhite.GetRootFramePixelBuffer(), y, height); - - PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhite, pixelAsGraySpan, pixelsBlackWhite.Length); + int width = this.Image.Width; if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D) { // Special case for T4BitCompressor. - compressor.CompressStrip(pixelAsGraySpan, height); + int stripPixels = width * height; + this.pixelsAsGray ??= this.MemoryAllocator.Allocate(stripPixels); + Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); + int lastRow = y + height; + int grayRowIdx = 0; + for (int row = y; row < lastRow; row++) + { + Span pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row); + Span pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); + grayRowIdx++; + } + + compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height); } else { // Write uncompressed image. int bytesPerStrip = this.BytesPerRow * height; this.bitStrip ??= this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip); + this.pixelsAsGray ??= this.MemoryAllocator.Allocate(width); + Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); Span rows = this.bitStrip.Slice(0, bytesPerStrip); rows.Clear(); - int grayPixelIndex = 0; - for (int s = 0; s < height; s++) + int outputRowIdx = 0; + int lastRow = y + height; + for (int row = y; row < lastRow; row++) { int bitIndex = 0; int byteIndex = 0; - Span outputRow = rows.Slice(s * this.BytesPerRow); + Span outputRow = rows.Slice(outputRowIdx * this.BytesPerRow); + Span pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row); + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); for (int x = 0; x < this.Image.Width; x++) { int shift = 7 - bitIndex; - if (pixelAsGraySpan[grayPixelIndex++] == 255) + if (pixelAsGraySpan[x] == 255) { outputRow[byteIndex] |= (byte)(1 << shift); } @@ -79,6 +91,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers bitIndex = 0; } } + + outputRowIdx++; } compressor.CompressStrip(rows, height); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs index 4df57f7e85..43cb666b6a 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -30,12 +31,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers this.rowBuffer.Clear(); - Span rowSpan = this.rowBuffer.GetSpan().Slice(0, this.BytesPerRow * height); + Span outputRowSpan = this.rowBuffer.GetSpan().Slice(0, this.BytesPerRow * height); - Span pixels = GetStripPixels(this.Image.PixelBuffer, y, height); + int width = this.Image.Width; + using IMemoryOwner stripPixelBuffer = this.MemoryAllocator.Allocate(height * width); + Span stripPixels = stripPixelBuffer.GetSpan(); + int lastRow = y + height; + int stripPixelsRowIdx = 0; + for (int row = y; row < lastRow; row++) + { + Span stripPixelsRow = this.Image.PixelBuffer.GetRowSpan(row); + stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width)); + stripPixelsRowIdx++; + } - this.EncodePixels(pixels, rowSpan); - compressor.CompressStrip(rowSpan, height); + this.EncodePixels(stripPixels, outputRowSpan); + compressor.CompressStrip(outputRowSpan, height); } protected abstract void EncodePixels(Span pixels, Span buffer); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs index d1a3dd1ea3..5314490182 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -5,7 +5,6 @@ using System; using System.Buffers; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -21,6 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers private readonly int colorPaletteSize; private readonly int colorPaletteBytes; private readonly IndexedImageFrame quantizedImage; + private IMemoryOwner indexedPixelsBuffer; public TiffPaletteWriter( ImageFrame image, @@ -55,22 +55,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers /// protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) { - Span indexedPixels = GetStripPixels(((IPixelSource)this.quantizedImage).PixelBuffer, y, height); + int width = this.Image.Width; + if (this.BitsPerPixel == 4) { - int width = this.Image.Width; int halfWidth = width >> 1; int excess = (width & 1) * height; // (width % 2) * height int rows4BitBufferLength = (halfWidth * height) + excess; - using IMemoryOwner rows4bitBuffer = this.MemoryAllocator.Allocate(rows4BitBufferLength); - Span rows4bit = rows4bitBuffer.GetSpan(); - int idxPixels = 0; + this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate(rows4BitBufferLength); + Span rows4bit = this.indexedPixelsBuffer.GetSpan(); int idx4bitRows = 0; - for (int row = 0; row < height; row++) + int lastRow = y + height; + for (int row = y; row < lastRow; row++) { + ReadOnlySpan indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row); + int idxPixels = 0; for (int x = 0; x < halfWidth; x++) { - rows4bit[idx4bitRows] = (byte)((indexedPixels[idxPixels] << 4) | (indexedPixels[idxPixels + 1] & 0xF)); + rows4bit[idx4bitRows] = (byte)((indexedPixelRow[idxPixels] << 4) | (indexedPixelRow[idxPixels + 1] & 0xF)); idxPixels += 2; idx4bitRows++; } @@ -78,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers // Make sure rows are byte-aligned. if (width % 2 != 0) { - rows4bit[idx4bitRows++] = (byte)(indexedPixels[idxPixels++] << 4); + rows4bit[idx4bitRows++] = (byte)(indexedPixelRow[idxPixels] << 4); } } @@ -86,12 +88,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers } else { - compressor.CompressStrip(indexedPixels, height); + int stripPixels = width * height; + this.indexedPixelsBuffer ??= this.MemoryAllocator.AllocateManagedByteBuffer(stripPixels); + Span indexedPixels = this.indexedPixelsBuffer.GetSpan(); + int lastRow = y + height; + int indexedPixelsRowIdx = 0; + for (int row = y; row < lastRow; row++) + { + ReadOnlySpan indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row); + indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width)); + indexedPixelsRowIdx++; + } + + compressor.CompressStrip(indexedPixels.Slice(0, stripPixels), height); } } /// - protected override void Dispose(bool disposing) => this.quantizedImage?.Dispose(); + protected override void Dispose(bool disposing) + { + this.quantizedImage?.Dispose(); + this.indexedPixelsBuffer?.Dispose(); + } private void AddColorMapTag() { From ded5b162d903db4c6332ef2f885a47e458ec033f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 04:18:52 +0300 Subject: [PATCH 19/33] Implemented log2 method --- src/ImageSharp/Common/Helpers/Numerics.cs | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index ef457f7ceb..a0ce62f683 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -23,6 +23,7 @@ namespace SixLabors.ImageSharp private const int ShuffleAlphaControl = 0b_11_11_11_11; #endif + // TODO: Obsolete - remove #if !SUPPORTS_BITOPERATIONS /// /// Gets the counts the number of bits needed to hold an integer. @@ -45,6 +46,16 @@ namespace SixLabors.ImageSharp }; #endif +#if !SUPPORTS_BITOPERATIONS + private static ReadOnlySpan Log2DeBruijn => new byte[32] + { + 00, 09, 01, 10, 13, 21, 02, 29, + 11, 14, 16, 18, 22, 25, 03, 30, + 08, 12, 20, 28, 15, 17, 24, 07, + 19, 27, 23, 06, 26, 05, 04, 31 + }; +#endif + /// /// Determine the Greatest CommonDivisor (GCD) of two numbers. /// @@ -868,5 +879,52 @@ namespace SixLabors.ImageSharp return bitInUnsignedInteger - BitOperations.LeadingZeroCount(number); #endif } + + /// + /// Calculates floored log of the specified value, base 2. + /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. + /// + /// The value. + public static int Log2(uint value) + { +#if SUPPORTS_BITOPERATIONS + return BitOperations.Log2(value); +#else + return Log2SoftwareFallback(value); +#endif + } + +#if !SUPPORTS_BITOPERATIONS + /// + /// Calculates floored log of the specified value, base 2. + /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. + /// Bit hacking with deBruijn sequence, extremely fast yet does not use any intrinsics so should work on every platform. + /// + /// + /// Description of this bit hacking can be found here: + /// https://cstheory.stackexchange.com/questions/19524/using-the-de-bruijn-sequence-to-find-the-lceil-log-2-v-rceil-of-an-integer + /// + /// The value. + private static int Log2SoftwareFallback(uint value) + { + // No AggressiveInlining due to large method size + // Has conventional contract 0->0 (Log(0) is undefined) by default, no need for if checking + + + // Fill trailing zeros with ones, eg 00010010 becomes 00011111 + value |= value >> 01; + value |= value >> 02; + value |= value >> 04; + value |= value >> 08; + value |= value >> 16; + + // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check + return Unsafe.AddByteOffset( + ref MemoryMarshal.GetReference(Log2DeBruijn), + + // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here + (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); + } +#endif } } From 83643166bab1c141e93983f34c91410cef1e72ef Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 05:31:09 +0300 Subject: [PATCH 20/33] Renamed MinimumBitsToStore16 metho to something more specific, added comments, added more peformant fallback implementation --- src/ImageSharp/Common/Helpers/Numerics.cs | 32 ++++++++++++------- .../Components/Encoder/HuffmanScanEncoder.cs | 2 +- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index a0ce62f683..28c9d6705b 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -860,23 +860,31 @@ namespace SixLabors.ImageSharp #endif /// - /// Calculates how many minimum bits needed to store given value. + /// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding. + /// This method does not follow the standard convention - it does not support input value of zero. /// - /// Unsigned integer to store - /// Minimum number of bits needed to store given value + /// + /// Passing zero as input value would result in an undefined behaviour. + /// This is done for performance reasons as Huffman encoding code checks for zero value, second identical check could degrade the performance in the hot path. + /// If this method is needed somewhere else apart from jpeg encoding - use explicit if check for zero value case. + /// + /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int MinimumBitsToStore16(uint number) + internal static int GetHuffmanEncodingLegth(uint value) { -#if !SUPPORTS_BITOPERATIONS - if (number < 0x100) - { - return BitCountLut[(int)number]; - } + DebugGuard.IsFalse(value == 0, nameof(value), "Huffman encoding does not encode zero values"); +#if SUPPORTS_BITOPERATIONS + // This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation + // But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value)) - return 8 + BitCountLut[(int)number >> 8]; + // BitOperations.Log2 implementation also checks if input value is zero for the convention + // As this is a very specific method for a specific Huffman encoding code + // We can omit zero check as this is guranteed not to be invoked with value == 0 and guarded in debug builds + return 32 - BitOperations.LeadingZeroCount(value); #else - const int bitInUnsignedInteger = sizeof(uint) * 8; - return bitInUnsignedInteger - BitOperations.LeadingZeroCount(number); + // On the contrary to BitOperations implementation this does follow the convention and supports value == 0 case + // Although it's still won't be called with value == 0 + return Log2SoftwareFallback(value) + 1; #endif } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index ca352397b8..2a21ae75f8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -360,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder b = value - 1; } - int bt = Numerics.MinimumBitsToStore16((uint)a); + int bt = Numerics.GetHuffmanEncodingLegth((uint)a); this.EmitHuff(index, (runLength << 4) | bt); if (bt > 0) From e696b1971fc567e813a4c284a3b146c7ea65615a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 05:32:22 +0300 Subject: [PATCH 21/33] Removed obsolete table --- src/ImageSharp/Common/Helpers/Numerics.cs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 28c9d6705b..f7d8c8014d 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -23,29 +23,6 @@ namespace SixLabors.ImageSharp private const int ShuffleAlphaControl = 0b_11_11_11_11; #endif - // TODO: Obsolete - remove -#if !SUPPORTS_BITOPERATIONS - /// - /// Gets the counts the number of bits needed to hold an integer. - /// - private static ReadOnlySpan BitCountLut => new byte[] - { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, - }; -#endif - #if !SUPPORTS_BITOPERATIONS private static ReadOnlySpan Log2DeBruijn => new byte[32] { From bf61a7dc13314070317e9cd5ce2bcab05fcc6bec Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 05:40:17 +0300 Subject: [PATCH 22/33] Fixed comments --- src/ImageSharp/Common/Helpers/Numerics.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index f7d8c8014d..83f2a1f7c6 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -855,8 +855,9 @@ namespace SixLabors.ImageSharp // But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value)) // BitOperations.Log2 implementation also checks if input value is zero for the convention - // As this is a very specific method for a specific Huffman encoding code - // We can omit zero check as this is guranteed not to be invoked with value == 0 and guarded in debug builds + // As this is a very specific method for a very specific Huffman encoding code + // We can omit zero check as this should not be invoked with value == 0 and guarded in debug builds + // This is also marked as internal so every use of this would be tracable & testable in tests return 32 - BitOperations.LeadingZeroCount(value); #else // On the contrary to BitOperations implementation this does follow the convention and supports value == 0 case From 3c70300a41526201213bcef6549b333f62d1bd62 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 06:40:54 +0300 Subject: [PATCH 23/33] Added Log2 tests --- .../ImageSharp.Tests/Common/NumericsTests.cs | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/ImageSharp.Tests/Common/NumericsTests.cs diff --git a/tests/ImageSharp.Tests/Common/NumericsTests.cs b/tests/ImageSharp.Tests/Common/NumericsTests.cs new file mode 100644 index 0000000000..29eae6d488 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/NumericsTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Common +{ + public class NumericsTests + { + private ITestOutputHelper Output { get; } + + public NumericsTests(ITestOutputHelper output) + { + this.Output = output; + } + + private static int Log2_ReferenceImplementation(uint value) + { + int n = 0; + while ((value >>= 1) != 0) + { + ++n; + } + + return n; + } + + [Fact] + public void Log2_ZeroConvention() + { + uint value = 0; + int expected = 0; + int actual = Numerics.Log2(value); + + Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); + } + + [Fact] + public void Log2_PowersOfTwo() + { + for (int i = 0; i < sizeof(int) * 8; i++) + { + // from 2^0 to 2^32 + uint value = (uint)(1 << i); + int expected = i; + int actual = Numerics.Log2(value); + + Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); + } + } + + [Theory] + [InlineData(1, 100)] + [InlineData(2, 100)] + public void Log2_RandomValues(int seed, int count) + { + var rng = new Random(seed); + byte[] bytes = new byte[4]; + + for (int i = 0; i < count; i++) + { + rng.NextBytes(bytes); + uint value = BitConverter.ToUInt32(bytes, 0); + int expected = Log2_ReferenceImplementation(value); + int actual = Numerics.Log2(value); + + Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}"); + } + } + } +} From ab2a97a9653a6f3de3705b1892b1fa5b0cc85abb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 06:46:11 +0300 Subject: [PATCH 24/33] Moved jpeg specific code from Numerics.cs to the jpeg related code --- src/ImageSharp/Common/Helpers/Numerics.cs | 32 +------------------ .../Components/Encoder/HuffmanScanEncoder.cs | 29 +++++++++++++++++ 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 83f2a1f7c6..eff1372c17 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -836,36 +836,6 @@ namespace SixLabors.ImageSharp } #endif - /// - /// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding. - /// This method does not follow the standard convention - it does not support input value of zero. - /// - /// - /// Passing zero as input value would result in an undefined behaviour. - /// This is done for performance reasons as Huffman encoding code checks for zero value, second identical check could degrade the performance in the hot path. - /// If this method is needed somewhere else apart from jpeg encoding - use explicit if check for zero value case. - /// - /// The value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int GetHuffmanEncodingLegth(uint value) - { - DebugGuard.IsFalse(value == 0, nameof(value), "Huffman encoding does not encode zero values"); -#if SUPPORTS_BITOPERATIONS - // This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation - // But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value)) - - // BitOperations.Log2 implementation also checks if input value is zero for the convention - // As this is a very specific method for a very specific Huffman encoding code - // We can omit zero check as this should not be invoked with value == 0 and guarded in debug builds - // This is also marked as internal so every use of this would be tracable & testable in tests - return 32 - BitOperations.LeadingZeroCount(value); -#else - // On the contrary to BitOperations implementation this does follow the convention and supports value == 0 case - // Although it's still won't be called with value == 0 - return Log2SoftwareFallback(value) + 1; -#endif - } - /// /// Calculates floored log of the specified value, base 2. /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. @@ -891,7 +861,7 @@ namespace SixLabors.ImageSharp /// https://cstheory.stackexchange.com/questions/19524/using-the-de-bruijn-sequence-to-find-the-lceil-log-2-v-rceil-of-an-integer /// /// The value. - private static int Log2SoftwareFallback(uint value) + internal static int Log2SoftwareFallback(uint value) { // No AggressiveInlining due to large method size // Has conventional contract 0->0 (Log(0) is undefined) by default, no need for if checking diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 2a21ae75f8..a8382df2bb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -388,5 +388,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.target.Write(this.emitBuffer, 0, this.emitLen); } } + + /// + /// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding. + /// This method does not follow the standard convention - it does not support input value of zero. + /// + /// + /// Passing zero as input value would result in an undefined behaviour. + /// This is done for performance reasons as Huffman encoding code checks for zero value, second identical check would degrade the performance in the hot path. + /// + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetHuffmanEncodingLegth(uint value) + { + DebugGuard.IsFalse(value == 0, nameof(value), "Huffman encoding does not encode zero values"); +#if SUPPORTS_BITOPERATIONS + // This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation + // But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value)) + + // BitOperations.Log2 implementation also checks if input value is zero for the convention + // As this is a very specific method for a very specific Huffman encoding code + // We can omit zero check as this should not be invoked with value == 0 and guarded in debug builds + // This is also marked as internal so every use of this would be tracable & testable in tests + return 32 - System.Numerics.BitOperations.LeadingZeroCount(value); +#else + // On the contrary to BitOperations implementation this does follow the convention and supports value == 0 case + // Although it's still won't be called with value == 0 + return Numerics.Log2SoftwareFallback(value) + 1; +#endif + } } } From a4475fa3b6e194e60e476ab78c985d19d93f0f1e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 06:57:10 +0300 Subject: [PATCH 25/33] Small docs fixes --- src/ImageSharp/Common/Helpers/Numerics.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index eff1372c17..9faf1cda08 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -854,7 +854,7 @@ namespace SixLabors.ImageSharp /// /// Calculates floored log of the specified value, base 2. /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. - /// Bit hacking with deBruijn sequence, extremely fast yet does not use any intrinsics so should work on every platform. + /// Bit hacking with deBruijn sequence, extremely fast yet does not use any intrinsics so will work on every platform/runtime. /// /// /// Description of this bit hacking can be found here: @@ -866,7 +866,6 @@ namespace SixLabors.ImageSharp // No AggressiveInlining due to large method size // Has conventional contract 0->0 (Log(0) is undefined) by default, no need for if checking - // Fill trailing zeros with ones, eg 00010010 becomes 00011111 value |= value >> 01; value |= value >> 02; From ab8f727f97158922cbe74eeacdcbd8ca345a4a75 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 06:59:48 +0300 Subject: [PATCH 26/33] Yet another docs fixes --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index a8382df2bb..d46b8c62c8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -409,11 +409,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // BitOperations.Log2 implementation also checks if input value is zero for the convention // As this is a very specific method for a very specific Huffman encoding code // We can omit zero check as this should not be invoked with value == 0 and guarded in debug builds - // This is also marked as internal so every use of this would be tracable & testable in tests return 32 - System.Numerics.BitOperations.LeadingZeroCount(value); #else // On the contrary to BitOperations implementation this does follow the convention and supports value == 0 case // Although it's still won't be called with value == 0 + // As these implementations behave differently for the value == 0 case it's documented as undefined behaviour return Numerics.Log2SoftwareFallback(value) + 1; #endif } From 2a48032ab6298fa4392a825ba92c5e7aa52934fb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 07:04:10 +0300 Subject: [PATCH 27/33] Fixed compilation error --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index d46b8c62c8..174ae232c9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -360,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder b = value - 1; } - int bt = Numerics.GetHuffmanEncodingLegth((uint)a); + int bt = GetHuffmanEncodingLegth((uint)a); this.EmitHuff(index, (runLength << 4) | bt); if (bt > 0) From d650073603291f552466f9acd3fd319549030af9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 15 Jun 2021 14:27:38 +1000 Subject: [PATCH 28/33] Fix build config --- Directory.Build.props | 2 +- ImageSharp.sln | 24 +++++++++---------- src/ImageSharp/ImageSharp.csproj | 2 +- .../ImageSharp.Benchmarks.csproj | 2 +- .../ImageSharp.Tests.ProfilingSandbox.csproj | 2 +- .../ImageSharp.Tests/ImageSharp.Tests.csproj | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 3df93fcd40..d70fbc45ae 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -26,5 +26,5 @@ true - + diff --git a/ImageSharp.sln b/ImageSharp.sln index 5555764353..ef6a945f65 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -546,12 +546,12 @@ Global {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.Build.0 = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.ActiveCfg = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.Build.0 = Debug|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU - {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.Build.0 = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x64.ActiveCfg = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x64.Build.0 = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x86.ActiveCfg = Debug|Any CPU + {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x86.Build.0 = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.Build.0 = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.ActiveCfg = Release|Any CPU @@ -570,12 +570,12 @@ Global {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.Build.0 = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.ActiveCfg = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.Build.0 = Debug|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU - {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.Build.0 = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x64.ActiveCfg = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x64.Build.0 = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x86.ActiveCfg = Debug|Any CPU + {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x86.Build.0 = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.Build.0 = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.ActiveCfg = Release|Any CPU diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 510f34dc7d..7719b12420 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -12,7 +12,7 @@ $(RepositoryUrl) Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET - Debug;Release;Release-InnerLoop;Debug-InnerLoop + Debug;Release;Debug-InnerLoop;Release-InnerLoop diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index a146dc03ee..17f6068d40 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -8,7 +8,7 @@ false false - Debug;Release;Release-InnerLoop;Debug-InnerLoop + Debug;Release;Debug-InnerLoop;Release-InnerLoop diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index fe3b16450c..a60ac604f1 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -12,7 +12,7 @@ false false - Debug;Release;Release-InnerLoop;Debug-InnerLoop + Debug;Release;Debug-InnerLoop;Release-InnerLoop false diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index b6482455e0..b8d44d0d1e 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -6,7 +6,7 @@ SixLabors.ImageSharp.Tests AnyCPU;x64;x86 SixLabors.ImageSharp.Tests - Debug;Release;Release-InnerLoop;Debug-InnerLoop + Debug;Release;Debug-InnerLoop;Release-InnerLoop From 8a80449cabdebf1787ec7cd1e9a68b32e3da9b28 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 15 Jun 2021 14:27:47 +1000 Subject: [PATCH 29/33] Update editorconfig --- .editorconfig | 2 +- shared-infrastructure | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 03036f8a53..33fd0577a8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -75,7 +75,7 @@ indent_style = tab [*.{cs,csx,cake,vb,vbx}] # Default Severity for all .NET Code Style rules below -dotnet_analyzer_diagnostic.severity = warning +dotnet_analyzer_diagnostic.category-style.severity = warning ########################################## # Language Rules diff --git a/shared-infrastructure b/shared-infrastructure index 1f7ee70281..9b94ebc4be 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 1f7ee702812f3a1713ab7f749c0faae0ef139ed7 +Subproject commit 9b94ebc4be9b7a8d7620c257e6ee485455973332 From 1642a675c0f3ab2a40c4586d574684a411f1921e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 15 Jun 2021 14:37:10 +1000 Subject: [PATCH 30/33] Fix build errors --- .../Encoder/RgbToYCbCrConverterVectorized.cs | 4 +- .../Jpeg/Components/FastFloatingPointDCT.cs | 2 +- .../Formats/Jpeg/JpegEncoderCore.cs | 78 +++++++++---------- .../Tiff/Writers/TiffPaletteWriter{TPixel}.cs | 2 +- .../Codecs/EncodeTiff.cs | 2 +- .../Codecs/Jpeg/EncodeJpeg.cs | 4 + 6 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs index 926e7d5a4a..9566ee862a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs @@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // left 8x8 column conversions for (int j = 0; j < 4; j += 2) { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * (i * 4 + j))).AsUInt32(), extractToLanesMask).AsByte(); + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte(); rgb = Avx2.Shuffle(rgb, extractRgbMask); @@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // right 8x8 column conversions for (int j = 1; j < 4; j += 2) { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * (i * 4 + j))).AsUInt32(), extractToLanesMask).AsByte(); + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte(); rgb = Avx2.Shuffle(rgb, extractRgbMask); diff --git a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs index f31d07efca..0f569b5da1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components private static readonly Vector256 C_V_n1_8477 = Vector256.Create(-1.847759065f); private static readonly Vector256 C_V_0_7653 = Vector256.Create(0.765366865f); - private static Vector256 C_V_InvSqrt2 = Vector256.Create(0.707107f); + private static readonly Vector256 C_V_InvSqrt2 = Vector256.Create(0.707107f); #endif #pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f); diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 6020e6196c..135048aa4e 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -28,44 +28,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private const int QuantizationTableCount = 2; - /// - /// Gets the unscaled quantization tables in zig-zag order. Each - /// encoder copies and scales the tables according to its quality parameter. - /// The values are derived from section K.1 after converting from natural to - /// zig-zag order. - /// - // The C# compiler emits this as a compile-time constant embedded in the PE file. - // This is effectively compiled down to: return new ReadOnlySpan(&data, length) - // More details can be found: https://github.com/dotnet/roslyn/pull/24621 - private static ReadOnlySpan UnscaledQuant_Luminance => new byte[] - { - // Luminance. - 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, - 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, - 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, - 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, - 100, 120, 92, 101, 103, 99, - }; - - /// - /// Gets the unscaled quantization tables in zig-zag order. Each - /// encoder copies and scales the tables according to its quality parameter. - /// The values are derived from section K.1 after converting from natural to - /// zig-zag order. - /// - // The C# compiler emits this as a compile-time constant embedded in the PE file. - // This is effectively compiled down to: return new ReadOnlySpan(&data, length) - // More details can be found: https://github.com/dotnet/roslyn/pull/24621 - private static ReadOnlySpan UnscaledQuant_Chrominance => new byte[] - { - // Chrominance. - 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - }; - /// /// A scratch buffer to reduce allocations. /// @@ -102,6 +64,44 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.colorType = options.ColorType; } + /// + /// Gets the unscaled quantization tables in zig-zag order. Each + /// encoder copies and scales the tables according to its quality parameter. + /// The values are derived from section K.1 after converting from natural to + /// zig-zag order. + /// + // The C# compiler emits this as a compile-time constant embedded in the PE file. + // This is effectively compiled down to: return new ReadOnlySpan(&data, length) + // More details can be found: https://github.com/dotnet/roslyn/pull/24621 + private static ReadOnlySpan UnscaledQuant_Luminance => new byte[] + { + // Luminance. + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, + 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, + 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, + 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, + 100, 120, 92, 101, 103, 99, + }; + + /// + /// Gets the unscaled quantization tables in zig-zag order. Each + /// encoder copies and scales the tables according to its quality parameter. + /// The values are derived from section K.1 after converting from natural to + /// zig-zag order. + /// + // The C# compiler emits this as a compile-time constant embedded in the PE file. + // This is effectively compiled down to: return new ReadOnlySpan(&data, length) + // More details can be found: https://github.com/dotnet/roslyn/pull/24621 + private static ReadOnlySpan UnscaledQuant_Chrominance => new byte[] + { + // Chrominance. + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + }; + /// /// Encode writes the image to the jpeg baseline format with the given options. /// @@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } else { - switch (subsample) + switch (this.subsample) { case JpegSubsample.Ratio444: scanEncoder.Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs index 5314490182..61e24d6529 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers /// protected override void Dispose(bool disposing) { - this.quantizedImage?.Dispose(); + this.quantizedImage?.Dispose(); this.indexedPixelsBuffer?.Dispose(); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs index 39055faf50..025412adcd 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs ImageCodecInfo codec = FindCodecForType("image/tiff"); using var parameters = new EncoderParameters(1) { - Param = {[0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression))} + Param = { [0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression)) } }; using var memoryStream = new MemoryStream(); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index 47c6f2c7d4..d472791e43 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -47,8 +47,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg this.bmpDrawing = SDImage.FromStream(this.bmpStream); this.jpegCodec = GetEncoder(ImageFormat.Jpeg); this.encoderParameters = new EncoderParameters(1); + // Quality cast to long is necessary +#pragma warning disable IDE0004 // Remove Unnecessary Cast this.encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (long)this.Quality); +#pragma warning restore IDE0004 // Remove Unnecessary Cast this.destinationStream = new MemoryStream(); } @@ -101,6 +104,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg return codec; } } + return null; } } From 83f0a01d37905fca7c2cfc6f2563bc6b75495b53 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 08:35:21 +0300 Subject: [PATCH 31/33] Fixed typo, fixed GetHuffmanEncodingLength invalid fallback code --- .../Components/Encoder/HuffmanScanEncoder.cs | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 174ae232c9..df1b4e4686 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -360,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder b = value - 1; } - int bt = GetHuffmanEncodingLegth((uint)a); + int bt = GetHuffmanEncodingLength((uint)a); this.EmitHuff(index, (runLength << 4) | bt); if (bt > 0) @@ -391,30 +391,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding. - /// This method does not follow the standard convention - it does not support input value of zero. /// /// - /// Passing zero as input value would result in an undefined behaviour. - /// This is done for performance reasons as Huffman encoding code checks for zero value, second identical check would degrade the performance in the hot path. + /// This method returns 0 for input value 0. This is done specificaly for huffman encoding /// /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetHuffmanEncodingLegth(uint value) + private static int GetHuffmanEncodingLength(uint value) { - DebugGuard.IsFalse(value == 0, nameof(value), "Huffman encoding does not encode zero values"); + DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max"); #if SUPPORTS_BITOPERATIONS // This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation // But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value)) - // BitOperations.Log2 implementation also checks if input value is zero for the convention - // As this is a very specific method for a very specific Huffman encoding code - // We can omit zero check as this should not be invoked with value == 0 and guarded in debug builds + // BitOperations.Log2 implementation also checks if input value is zero for the convention 0->0 + // Lzcnt would return 32 for input value of 0 - no need to check that with branching + // Fallback code if Lzcnt is not supported still use if-check + // But most modern CPUs support this instruction so this should not be a problem return 32 - System.Numerics.BitOperations.LeadingZeroCount(value); #else - // On the contrary to BitOperations implementation this does follow the convention and supports value == 0 case - // Although it's still won't be called with value == 0 - // As these implementations behave differently for the value == 0 case it's documented as undefined behaviour - return Numerics.Log2SoftwareFallback(value) + 1; + // Ideally: + // if 0 - return 0 in this case + // else - return log2(value) + 1 + // + // Hack based on input value constaint: + // We know that input values are guaranteed to be maximum 16 bit large for huffman encoding + // We can safely shift input value for one bit -> log2(value << 1) + // Because of the 16 bit value constraint it won't overflow + // With that input value change we no longer need to add 1 before returning + // And this eliminates need to check if input value is zero - it is a standard convention which Log2SoftwareFallback adheres to + return Numerics.Log2SoftwareFallback(value << 1); #endif } } From 5e5e48c5371b47b2b1667680650a00377d344f0d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 08:40:17 +0300 Subject: [PATCH 32/33] Style fix --- src/ImageSharp/Common/Helpers/Numerics.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 9faf1cda08..9d60ac35cd 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -876,9 +876,7 @@ namespace SixLabors.ImageSharp // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check return Unsafe.AddByteOffset( ref MemoryMarshal.GetReference(Log2DeBruijn), - - // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here - (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); + (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here } #endif } From a5210b21a5114a6622bfb5f092f3bd0f8419c8be Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 15 Jun 2021 09:31:05 +0300 Subject: [PATCH 33/33] Jpeg encoder no uses Numerics.Log2 as fallback --- src/ImageSharp/Common/Helpers/Numerics.cs | 2 +- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 9d60ac35cd..db65b84cca 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -861,7 +861,7 @@ namespace SixLabors.ImageSharp /// https://cstheory.stackexchange.com/questions/19524/using-the-de-bruijn-sequence-to-find-the-lceil-log-2-v-rceil-of-an-integer /// /// The value. - internal static int Log2SoftwareFallback(uint value) + private static int Log2SoftwareFallback(uint value) { // No AggressiveInlining due to large method size // Has conventional contract 0->0 (Log(0) is undefined) by default, no need for if checking diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index df1b4e4686..860a9c3236 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -420,7 +420,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // Because of the 16 bit value constraint it won't overflow // With that input value change we no longer need to add 1 before returning // And this eliminates need to check if input value is zero - it is a standard convention which Log2SoftwareFallback adheres to - return Numerics.Log2SoftwareFallback(value << 1); + return Numerics.Log2(value << 1); #endif } }