From 8077172088f4c7516e221db1acace288aefec338 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 25 Feb 2021 15:39:07 +0100 Subject: [PATCH] Introduce TiffBitsPerSample enum --- .../Formats/Tiff/TiffBitsPerSample.cs | 36 ++++ .../Tiff/TiffBitsPerSampleExtensions.cs | 99 +++++++++++ .../Formats/Tiff/TiffDecoderCore.cs | 15 +- .../Tiff/TiffDecoderMetadataCreator.cs | 4 +- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 157 ++++++++---------- .../Formats/Tiff/TiffFrameMetadata.cs | 30 ++-- .../Formats/Tiff/TiffMetadataTests.cs | 3 +- 7 files changed, 225 insertions(+), 119 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/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs new file mode 100644 index 000000000..b556e5b95 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs @@ -0,0 +1,36 @@ +// 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, + + /// + /// One bit per sample for bicolor images. + /// + One, + + /// + /// Four bits per sample for grayscale images with 16 different levels of gray or paletted images with a palette of 16 colors. + /// + Four, + + /// + /// Eight bits per sample for grayscale images with 256 different levels of gray or paletted images with a palette of 256 colors. + /// + Eight, + + /// + /// Each channel has 8 Bits. + /// + Rgb888, + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs new file mode 100644 index 000000000..884481b98 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Experimental.Tiff; + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + internal static class TiffBitsPerSampleExtensions + { + private static readonly ushort[] One = { 1 }; + + private static readonly ushort[] Four = { 4 }; + + private static readonly ushort[] Eight = { 8 }; + + private static readonly ushort[] Rgb888 = { 8, 8, 8 }; + + /// + /// 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.One: + return One; + case TiffBitsPerSample.Four: + return Four; + case TiffBitsPerSample.Eight: + return Eight; + case TiffBitsPerSample.Rgb888: + return Rgb888; + + default: + TiffThrowHelper.ThrowNotSupported("The bits per pixels are not supported"); + 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[0] == Rgb888[0] && bitsPerSample[1] == Rgb888[1] && bitsPerSample[2] == Rgb888[2]) + { + return TiffBitsPerSample.Rgb888; + } + + break; + + case 1: + if (bitsPerSample[0] == One[0]) + { + return TiffBitsPerSample.One; + } + + if (bitsPerSample[0] == Four[0]) + { + return TiffBitsPerSample.Four; + } + + if (bitsPerSample[0] == Eight[0]) + { + return TiffBitsPerSample.Eight; + } + + break; + } + + return TiffBitsPerSample.Unknown; + } + + /// + /// Gets the bits per pixel for the given bits per sample. + /// + /// The tiff bits per sample. + /// Bits per pixel. + public static int BitsPerPixel(this TiffBitsPerSample tiffBitsPerSample) + { + var bitsPerSample = tiffBitsPerSample.Bits(); + int bitsPerPixel = 0; + foreach (var bits in bitsPerSample) + { + bitsPerPixel += bits; + } + + return bitsPerPixel; + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index fe81d2edb..876816a36 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Threading; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -48,9 +49,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff } /// - /// Gets or sets the number of bits for each sample of the pixel format used to encode the image. + /// Gets or sets the number of bits per component of the pixel format used to decode the image. /// - public ushort[] BitsPerSample { get; set; } + public TiffBitsPerSample BitsPerSample { get; set; } /// /// Gets or sets the bits per pixel. @@ -154,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, reader.ByteOrder); TiffFrameMetadata root = framesMetadata[0]; - return new ImageInfo(new PixelTypeInfo(root.BitsPerPixel), (int)root.Width, (int)root.Height, metadata); + return new ImageInfo(new PixelTypeInfo(root.BitsPerSample.BitsPerPixel()), (int)root.Width, (int)root.Height, metadata); } /// @@ -213,7 +214,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff } else { - bitsPerPixel = this.BitsPerSample[plane]; + bitsPerPixel = this.BitsPerSample.Bits()[plane]; } int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; @@ -233,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.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.Bits().Length; int stripsPerPlane = stripOffsets.Length / stripsPerPixel; int bitsPerPixel = this.BitsPerPixel; @@ -251,7 +252,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff using TiffBaseDecompresor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); - RgbPlanarTiffColor colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap); + RgbPlanarTiffColor colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); for (int i = 0; i < stripsPerPlane; i++) { @@ -294,7 +295,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff using TiffBaseDecompresor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); - TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap); + TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) { diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index de17ada5d..b1696dc86 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; - +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -129,6 +129,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff } private static TiffBitsPerPixel GetBitsPerPixel(TiffFrameMetadata firstFrameMetaData) - => (TiffBitsPerPixel)firstFrameMetaData.BitsPerPixel; + => (TiffBitsPerPixel)firstFrameMetaData.BitsPerSample.BitsPerPixel(); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 123b8494e..fa90c4522 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff @@ -59,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff options.Predictor = entries.Predictor; options.PhotometricInterpretation = entries.PhotometricInterpretation; options.BitsPerSample = entries.BitsPerSample; - options.BitsPerPixel = entries.BitsPerPixel; + options.BitsPerPixel = entries.BitsPerSample.BitsPerPixel(); ParseColorType(options, entries); ParseCompression(options, entries); @@ -71,38 +72,36 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff { case TiffPhotometricInterpretation.WhiteIsZero: { - if (options.BitsPerSample.Length == 1) + if (options.BitsPerSample.Bits().Length != 1) { - switch (options.BitsPerSample[0]) - { - case 8: - { - options.ColorType = TiffColorType.WhiteIsZero8; - break; - } - - case 4: - { - options.ColorType = TiffColorType.WhiteIsZero4; - break; - } - - case 1: - { - options.ColorType = TiffColorType.WhiteIsZero1; - break; - } - - default: - { - options.ColorType = TiffColorType.WhiteIsZero; - break; - } - } + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } - else + + switch (options.BitsPerSample) { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + case TiffBitsPerSample.Eight: + { + options.ColorType = TiffColorType.WhiteIsZero8; + break; + } + + case TiffBitsPerSample.Four: + { + options.ColorType = TiffColorType.WhiteIsZero4; + break; + } + + case TiffBitsPerSample.One: + { + options.ColorType = TiffColorType.WhiteIsZero1; + break; + } + + default: + { + options.ColorType = TiffColorType.WhiteIsZero; + break; + } } break; @@ -110,38 +109,36 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff case TiffPhotometricInterpretation.BlackIsZero: { - if (options.BitsPerSample.Length == 1) + if (options.BitsPerSample.Bits().Length != 1) { - switch (options.BitsPerSample[0]) - { - case 8: - { - options.ColorType = TiffColorType.BlackIsZero8; - break; - } - - case 4: - { - options.ColorType = TiffColorType.BlackIsZero4; - break; - } - - case 1: - { - options.ColorType = TiffColorType.BlackIsZero1; - break; - } - - default: - { - options.ColorType = TiffColorType.BlackIsZero; - break; - } - } + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } - else + + switch (options.BitsPerSample) { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + case TiffBitsPerSample.Eight: + { + options.ColorType = TiffColorType.BlackIsZero8; + break; + } + + case TiffBitsPerSample.Four: + { + options.ColorType = TiffColorType.BlackIsZero4; + break; + } + + case TiffBitsPerSample.One: + { + options.ColorType = TiffColorType.BlackIsZero1; + break; + } + + default: + { + options.ColorType = TiffColorType.BlackIsZero; + break; + } } break; @@ -149,27 +146,18 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff case TiffPhotometricInterpretation.Rgb: { - if (options.BitsPerSample.Length == 3) + if (options.BitsPerSample.Bits().Length != 3) { - if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) - { - if (options.BitsPerSample[0] == 8 && options.BitsPerSample[1] == 8 && options.BitsPerSample[2] == 8) - { - options.ColorType = TiffColorType.Rgb888; - } - else - { - options.ColorType = TiffColorType.Rgb; - } - } - else - { - options.ColorType = TiffColorType.RgbPlanar; - } + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + { + options.ColorType = options.BitsPerSample == TiffBitsPerSample.Rgb888 ? TiffColorType.Rgb888 : TiffColorType.Rgb; } else { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + options.ColorType = TiffColorType.RgbPlanar; } break; @@ -180,21 +168,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff options.ColorMap = entries.ColorMap; if (options.ColorMap != null) { - if (options.BitsPerSample.Length == 1) - { - switch (options.BitsPerSample[0]) - { - default: - { - options.ColorType = TiffColorType.PaletteColor; - break; - } - } - } - else + if (options.BitsPerSample.Bits().Length != 1) { TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } + + options.ColorType = TiffColorType.PaletteColor; } else { @@ -206,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff default: { - TiffThrowHelper.ThrowNotSupported("The specified TIFF photometric interpretation is not supported: " + options.PhotometricInterpretation); + TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}"); } break; diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index 363e68cee..21a24896d 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// /// Gets the number of bits per component. /// - public ushort[] BitsPerSample + public TiffBitsPerSample BitsPerSample { get { @@ -98,32 +99,21 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff if (this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero || this.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) { - bits = new[] { (ushort)1 }; + return TiffBitsPerSample.One; } - else - { - TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing."); - } - } - return bits; - } - } - - internal int BitsPerPixel - { - get - { - int bitsPerPixel = 0; - foreach (var bits in this.BitsPerSample) - { - bitsPerPixel += bits; + TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image."); } - return bitsPerPixel; + return bits.GetBitsPerSample(); } } + /// + /// Gets the bits per pixel. + /// + public int BitsPerPixel => this.BitsPerSample.BitsPerPixel(); + /// /// Gets the compression scheme used on the image data. /// diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 9acd44608..e4b935e0f 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -8,6 +8,7 @@ using System.Linq; using SixLabors.ImageSharp.Formats.Experimental.Tiff; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -187,7 +188,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(32u, frame.Width); Assert.Equal(32u, frame.Height); - Assert.Equal(new ushort[] { 4 }, frame.BitsPerSample); + Assert.Equal(TiffBitsPerSample.Four, frame.BitsPerSample); Assert.Equal(TiffCompression.Lzw, frame.Compression); Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame.PhotometricInterpretation); Assert.Equal("This is Название", frame.ImageDescription);