From d22692ee8fd787a5081fa7bfd1764c950899c060 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 20 May 2021 17:30:08 +0200 Subject: [PATCH] Change TiffEncoder to use TiffPhotometricInterpretation instead of EncodingMode --- .../Compressors/T4BitCompressor.cs | 4 +- .../TiffPhotometricInterpretation.cs | 20 +- .../Formats/Tiff/ITiffEncoderOptions.cs | 12 +- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 2 +- src/ImageSharp/Formats/Tiff/TiffEncoder.cs | 8 +- .../Formats/Tiff/TiffEncoderCore.cs | 170 +++++++------- .../Tiff/TiffEncoderEntriesCollector.cs | 29 ++- .../Formats/Tiff/TiffEncodingMode.cs | 36 --- .../Formats/Tiff/TiffThrowHelper.cs | 3 + .../Tiff/Writers/TiffBiColorWriter{TPixel}.cs | 15 +- .../Tiff/Writers/TiffColorWriterFactory.cs | 17 +- .../Codecs/EncodeTiff.cs | 14 +- .../Formats/Tiff/TiffEncoderTests.cs | 218 +++++++++++------- .../Formats/Tiff/TiffMetadataTests.cs | 4 +- 14 files changed, 302 insertions(+), 250 deletions(-) delete mode 100644 src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs index a016fb712..3e9b7f4e6 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs @@ -344,8 +344,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors { while (codeLength > 0) { - var bitNumber = (int)codeLength; - var bit = (code & (1 << (bitNumber - 1))) != 0; + int bitNumber = (int)codeLength; + bool bit = (code & (1 << (bitNumber - 1))) != 0; if (bit) { BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition); diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs index b39e1003a..6dab7de6e 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs @@ -10,6 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants { /// /// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black. + /// + /// Not supported by the TiffEncoder. /// WhiteIsZero = 0, @@ -29,42 +31,58 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants PaletteColor = 3, /// - /// A transparency mask + /// A transparency mask. + /// + /// Not supported by the TiffEncoder. /// TransparencyMask = 4, /// /// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification). + /// + /// Not supported by the TiffEncoder. /// Separated = 5, /// /// YCbCr (see Section 21 of the TIFF 6.0 specification). + /// + /// Not supported by the TiffEncoder. /// YCbCr = 6, /// /// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification). + /// + /// Not supported by the TiffEncoder. /// CieLab = 8, /// /// ICC L*a*b* (see TIFF Specification, supplement 1). + /// + /// Not supported by the TiffEncoder. /// IccLab = 9, /// /// ITU L*a*b* (see RFC2301). + /// + /// Not supported by the TiffEncoder. /// ItuLab = 10, /// /// Color Filter Array (see the DNG specification). + /// + /// Not supported by the TiffEncoder. /// ColorFilterArray = 32803, /// /// Linear Raw (see the DNG specification). + /// + /// Not supported by the TiffEncoder. /// LinearRaw = 34892 } diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs index 03c25ea13..d56a587df 100644 --- a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs +++ b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs @@ -20,24 +20,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Gets the compression type to use. /// - TiffCompression Compression { get; } + TiffCompression? Compression { get; } /// /// Gets the compression level 1-9 for the deflate compression mode. /// Defaults to . /// - DeflateCompressionLevel CompressionLevel { get; } + DeflateCompressionLevel? CompressionLevel { get; } /// - /// Gets the encoding mode to use. Possible options are RGB, RGB with a color palette, gray or BiColor. - /// If no mode is specified in the options, RGB will be used. + /// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor. + /// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used. /// - TiffEncodingMode Mode { get; } + TiffPhotometricInterpretation? PhotometricInterpretation { get; } /// /// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression. /// - TiffPredictor HorizontalPredictor { get; } + TiffPredictor? HorizontalPredictor { get; } /// /// Gets the quantizer for creating a color palette image. diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 7111f5d36..88a2d194d 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff options.PlanarConfiguration = (TiffPlanarConfiguration?)exifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration; options.Predictor = predictor; options.PhotometricInterpretation = exifProfile.GetValue(ExifTag.PhotometricInterpretation) != null ? - (TiffPhotometricInterpretation)exifProfile.GetValue(ExifTag.PhotometricInterpretation).Value : TiffPhotometricInterpretation.WhiteIsZero; + (TiffPhotometricInterpretation)exifProfile.GetValue(ExifTag.PhotometricInterpretation).Value : TiffPhotometricInterpretation.BlackIsZero; options.BitsPerPixel = entries.BitsPerPixel != null ? (int)entries.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; options.BitsPerSample = GetBitsPerSample(entries.BitsPerPixel); diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs index b66ba339c..7d5ccdb94 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs @@ -22,16 +22,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff public TiffBitsPerPixel? BitsPerPixel { get; set; } /// - public TiffCompression Compression { get; set; } = TiffCompression.None; + public TiffCompression? Compression { get; set; } /// - public DeflateCompressionLevel CompressionLevel { get; set; } = DeflateCompressionLevel.DefaultCompression; + public DeflateCompressionLevel? CompressionLevel { get; set; } /// - public TiffEncodingMode Mode { get; set; } + public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; } /// - public TiffPredictor HorizontalPredictor { get; set; } + public TiffPredictor? HorizontalPredictor { get; set; } /// public IQuantizer Quantizer { get; set; } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 878bd99a9..f9402e4d4 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Writers; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -61,34 +62,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff public TiffEncoderCore(ITiffEncoderOptions options, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; - this.Mode = options.Mode; + this.PhotometricInterpretation = options.PhotometricInterpretation; this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; this.BitsPerPixel = options.BitsPerPixel; this.HorizontalPredictor = options.HorizontalPredictor; - this.CompressionType = options.Compression != TiffCompression.Invalid ? options.Compression : TiffCompression.None; - this.compressionLevel = options.CompressionLevel; + this.CompressionType = options.Compression; + this.compressionLevel = options.CompressionLevel ?? DeflateCompressionLevel.DefaultCompression; } /// /// Gets the photometric interpretation implementation to use when encoding the image. /// - internal TiffPhotometricInterpretation PhotometricInterpretation { get; private set; } + internal TiffPhotometricInterpretation? PhotometricInterpretation { get; private set; } /// /// Gets or sets the compression implementation to use when encoding the image. /// - internal TiffCompression CompressionType { get; set; } + internal TiffCompression? CompressionType { get; set; } /// - /// Gets the encoding mode to use. RGB, RGB with color palette or gray. - /// If no mode is specified in the options, RGB will be used. + /// Gets or sets a value indicating which horizontal predictor to use. This can improve the compression ratio with deflate compression. /// - internal TiffEncodingMode Mode { get; private set; } - - /// - /// Gets a value indicating which horizontal predictor to use. This can improve the compression ratio with deflate compression. - /// - internal TiffPredictor HorizontalPredictor { get; } + internal TiffPredictor? HorizontalPredictor { get; set; } /// /// Gets the bits per pixel. @@ -111,18 +106,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.configuration = image.GetConfiguration(); TiffPhotometricInterpretation rootFramePhotometricInterpretation = GetRootFramePhotometricInterpretation(image); - TiffPhotometricInterpretation photometricInterpretation = this.Mode == TiffEncodingMode.ColorPalette + TiffPhotometricInterpretation photometricInterpretation = this.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor ? TiffPhotometricInterpretation.PaletteColor : rootFramePhotometricInterpretation; - TiffBitsPerPixel? rootFrameBitsPerPixel = image.Frames.RootFrame.Metadata.GetTiffMetadata().BitsPerPixel; + ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; + ExifProfile rootFrameExifProfile = image.Frames.RootFrame.Metadata.ExifProfile; + TiffBitsPerPixel? rootFrameBitsPerPixel = rootFrameMetaData.GetTiffMetadata().BitsPerPixel; + + // If the user has not chosen a predictor or compression, set the values from the decoded image, if present. + if (!this.HorizontalPredictor.HasValue && rootFrameExifProfile?.GetValue(ExifTag.Predictor) != null) + { + this.HorizontalPredictor = (TiffPredictor)rootFrameExifProfile?.GetValue(ExifTag.Predictor).Value; + } + + if (!this.CompressionType.HasValue && rootFrameExifProfile?.GetValue(ExifTag.Compression) != null) + { + this.CompressionType = (TiffCompression)rootFrameExifProfile?.GetValue(ExifTag.Compression).Value; + } - // TODO: This isn't correct. - // We're overwriting explicit BPP based upon the Mode. It should be the other way around. - // BPP should also be nullable and based upon the current TPixel if not set. - this.SetBitsPerPixel(rootFrameBitsPerPixel, photometricInterpretation); - this.SetMode(photometricInterpretation); - this.SetPhotometricInterpretation(); + this.SetBitsPerPixel(rootFrameBitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation); + this.SetPhotometricInterpretation(photometricInterpretation); using (var writer = new TiffStreamWriter(stream)) { @@ -159,20 +163,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff { var entriesCollector = new TiffEncoderEntriesCollector(); - // Write the image bytes to the steam. - uint imageDataStart = (uint)writer.Position; - using TiffBaseCompressor compressor = TiffCompressorFactory.Create( - this.CompressionType, + this.CompressionType ?? TiffCompression.None, writer.BaseStream, this.memoryAllocator, image.Width, (int)this.BitsPerPixel, this.compressionLevel, - this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor : TiffPredictor.None); + this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None); using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create( - this.Mode, + this.PhotometricInterpretation, image.Frames.RootFrame, this.quantizer, this.memoryAllocator, @@ -227,8 +228,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { if (entries.Count == 0) { - // TODO: Perf. Throwhelper - throw new ArgumentException("There must be at least one entry per IFD.", nameof(entries)); + TiffThrowHelper.ThrowArgumentException("There must be at least one entry per IFD."); } uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12)); @@ -277,52 +277,73 @@ namespace SixLabors.ImageSharp.Formats.Tiff return nextIfdMarker; } - private void SetMode(TiffPhotometricInterpretation photometricInterpretation) + private void SetPhotometricInterpretation(TiffPhotometricInterpretation? photometricInterpretation) { - // Make sure, that the fax compressions are only used together with the BiColor mode. + // Make sure, that the fax compressions are only used together with the WhiteIsZero. if (this.CompressionType == TiffCompression.CcittGroup3Fax || this.CompressionType == TiffCompression.Ccitt1D) { - // Default means the user has not specified a preferred encoding mode. - if (this.Mode == TiffEncodingMode.Default) + // The user has not specified a preferred photometric interpretation. + if (this.PhotometricInterpretation == null) { - this.Mode = TiffEncodingMode.BiColor; + this.PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero; this.BitsPerPixel = TiffBitsPerPixel.Bit1; return; } - if (this.Mode != TiffEncodingMode.BiColor) + if (this.PhotometricInterpretation != TiffPhotometricInterpretation.WhiteIsZero && this.PhotometricInterpretation != TiffPhotometricInterpretation.BlackIsZero) { - TiffThrowHelper.ThrowImageFormatException($"The {this.CompressionType} compression and {this.Mode} aren't compatible. Please use {this.CompressionType} only with {TiffEncodingMode.BiColor} or {TiffEncodingMode.Default} mode."); + TiffThrowHelper.ThrowImageFormatException( + $"The {this.CompressionType} compression and {this.PhotometricInterpretation} aren't compatible. Please use {this.CompressionType} only with {TiffPhotometricInterpretation.BlackIsZero} or {TiffPhotometricInterpretation.WhiteIsZero}."); } + else + { + // The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero. + this.PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero; + } + + return; + } + + switch (this.PhotometricInterpretation) + { + // The currently supported values by the encoder for photometric interpretation: + case TiffPhotometricInterpretation.PaletteColor: + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: + break; + + default: + this.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb; + break; } - // Use the bits per pixel to determine the encoding mode. - this.SetModeWithBitsPerPixel(this.BitsPerPixel, photometricInterpretation); + // Use the bits per pixel to determine the photometric interpretation. + this.SetPhotometricInterpretationWithBitsPerPixel(this.BitsPerPixel, photometricInterpretation); } - private void SetModeWithBitsPerPixel(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) + private void SetPhotometricInterpretationWithBitsPerPixel(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation) { switch (bitsPerPixel) { case TiffBitsPerPixel.Bit1: - this.Mode = TiffEncodingMode.BiColor; + this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero; break; case TiffBitsPerPixel.Bit4: - this.Mode = TiffEncodingMode.ColorPalette; + this.PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor; break; case TiffBitsPerPixel.Bit8: - this.Mode = photometricInterpretation == TiffPhotometricInterpretation.PaletteColor - ? TiffEncodingMode.ColorPalette - : TiffEncodingMode.Gray; + this.PhotometricInterpretation = photometricInterpretation == TiffPhotometricInterpretation.PaletteColor + ? TiffPhotometricInterpretation.PaletteColor + : TiffPhotometricInterpretation.BlackIsZero; break; default: - this.Mode = TiffEncodingMode.Rgb; + this.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb; break; } } - private void SetBitsPerPixel(TiffBitsPerPixel? rootFrameBitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) + private void SetBitsPerPixel(TiffBitsPerPixel? rootFrameBitsPerPixel, int inputBitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) { this.BitsPerPixel ??= rootFrameBitsPerPixel; @@ -341,56 +362,41 @@ namespace SixLabors.ImageSharp.Formats.Tiff return; } - switch (this.Mode) + if (this.PhotometricInterpretation == null && inputBitsPerPixel == 8) { - case TiffEncodingMode.BiColor: - this.BitsPerPixel = TiffBitsPerPixel.Bit1; - break; - case TiffEncodingMode.ColorPalette: - if (this.BitsPerPixel != TiffBitsPerPixel.Bit8 && this.BitsPerPixel != TiffBitsPerPixel.Bit4) - { - this.BitsPerPixel = TiffBitsPerPixel.Bit8; - } - - break; - case TiffEncodingMode.Gray: - this.BitsPerPixel = TiffBitsPerPixel.Bit8; - break; - case TiffEncodingMode.Rgb: - this.BitsPerPixel = TiffBitsPerPixel.Bit24; - break; - default: - this.Mode = TiffEncodingMode.Rgb; - this.BitsPerPixel = TiffBitsPerPixel.Bit24; - break; + this.BitsPerPixel = TiffBitsPerPixel.Bit8; + return; } - } - private void SetPhotometricInterpretation() - { - switch (this.Mode) + switch (this.PhotometricInterpretation) { - case TiffEncodingMode.ColorPalette: - this.PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor; - break; - case TiffEncodingMode.BiColor: - if (this.CompressionType == TiffCompression.CcittGroup3Fax || this.CompressionType == TiffCompression.Ccitt1D) + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: + if (this.CompressionType == TiffCompression.Ccitt1D || + this.CompressionType == TiffCompression.CcittGroup3Fax || + this.CompressionType == TiffCompression.CcittGroup4Fax) { - // The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero. - this.PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero; + this.BitsPerPixel = TiffBitsPerPixel.Bit1; } else { - this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero; + this.BitsPerPixel = TiffBitsPerPixel.Bit8; } break; + case TiffPhotometricInterpretation.PaletteColor: + if (this.BitsPerPixel != TiffBitsPerPixel.Bit8 && this.BitsPerPixel != TiffBitsPerPixel.Bit4) + { + this.BitsPerPixel = TiffBitsPerPixel.Bit8; + } - case TiffEncodingMode.Gray: - this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero; + break; + case TiffPhotometricInterpretation.Rgb: + this.BitsPerPixel = TiffBitsPerPixel.Bit24; break; default: this.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb; + this.BitsPerPixel = TiffBitsPerPixel.Bit24; break; } } @@ -400,7 +406,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff ExifProfile exifProfile = image.Frames.RootFrame.Metadata.ExifProfile; return exifProfile?.GetValue(ExifTag.PhotometricInterpretation) != null ? (TiffPhotometricInterpretation)exifProfile?.GetValue(ExifTag.PhotometricInterpretation).Value - : TiffPhotometricInterpretation.WhiteIsZero; + : TiffPhotometricInterpretation.BlackIsZero; } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index 391f7d541..09605bc69 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -281,7 +281,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff if (encoder.HorizontalPredictor == TiffPredictor.Horizontal) { - if (encoder.Mode == TiffEncodingMode.Rgb || encoder.Mode == TiffEncodingMode.Gray || encoder.Mode == TiffEncodingMode.ColorPalette) + if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) { var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal }; @@ -320,20 +322,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffPhotometricInterpretation.Rgb: return TiffConstants.BitsPerSampleRgb8Bit; + case TiffPhotometricInterpretation.WhiteIsZero: - if (encoder.Mode == TiffEncodingMode.BiColor) + if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) { return TiffConstants.BitsPerSample1Bit; } return TiffConstants.BitsPerSample8Bit; + case TiffPhotometricInterpretation.BlackIsZero: - if (encoder.Mode == TiffEncodingMode.BiColor) + if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) { return TiffConstants.BitsPerSample1Bit; } return TiffConstants.BitsPerSample8Bit; + default: return TiffConstants.BitsPerSampleRgb8Bit; } @@ -350,7 +355,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff // PackBits is allowed for all modes. return (ushort)TiffCompression.PackBits; case TiffCompression.Lzw: - if (encoder.Mode == TiffEncodingMode.Rgb || encoder.Mode == TiffEncodingMode.Gray || encoder.Mode == TiffEncodingMode.ColorPalette) + if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) { return (ushort)TiffCompression.Lzw; } @@ -358,20 +365,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff break; case TiffCompression.CcittGroup3Fax: - if (encoder.Mode == TiffEncodingMode.BiColor) - { - return (ushort)TiffCompression.CcittGroup3Fax; - } - - break; + return (ushort)TiffCompression.CcittGroup3Fax; case TiffCompression.Ccitt1D: - if (encoder.Mode == TiffEncodingMode.BiColor) - { - return (ushort)TiffCompression.Ccitt1D; - } - - break; + return (ushort)TiffCompression.Ccitt1D; } return (ushort)TiffCompression.None; diff --git a/src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs b/src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs deleted file mode 100644 index 374505195..000000000 --- a/src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Tiff -{ - /// - /// Enum for the different tiff encoding options. - /// - public enum TiffEncodingMode - { - /// - /// No mode specified. Will preserve the bits per pixels of the input image. - /// - Default = 0, - - /// - /// The image will be encoded as RGB, 8 bit per channel. - /// - Rgb = 1, - - /// - /// The image will be encoded as RGB with a color palette. - /// - ColorPalette = 2, - - /// - /// The image will be encoded as 8 bit gray. - /// - Gray = 3, - - /// - /// The image will be written as a white and black image. - /// - BiColor = 4, - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs index c5ebf481a..3c541a786 100644 --- a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs +++ b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs @@ -32,5 +32,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff [MethodImpl(InliningOptions.ColdPath)] public static void ThrowNotSupported(string message) => throw new NotSupportedException(message); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowArgumentException(string message) => throw new ArgumentException(message); } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs index e37cba7cf..be5c837ea 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -36,16 +36,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers /// protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) { - if (this.pixelsAsGray == null) - { - this.pixelsAsGray = this.MemoryAllocator.Allocate(height * this.Image.Width); - } + this.pixelsAsGray ??= this.MemoryAllocator.Allocate(height * this.Image.Width); Span pixelAsGraySpan = this.pixelsAsGray.Slice(0, height * this.Image.Width); - Span pixels = GetStripPixels(this.imageBlackWhite.GetRootFramePixelBuffer(), y, height); + Span pixelsBlackWhite = GetStripPixels(this.imageBlackWhite.GetRootFramePixelBuffer(), y, height); - PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, pixelAsGraySpan, pixels.Length); + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhite, pixelAsGraySpan, pixelsBlackWhite.Length); if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D) { @@ -54,11 +51,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers } else { + // Write uncompressed image. int bytesPerStrip = this.BytesPerRow * height; - if (this.bitStrip == null) - { - this.bitStrip = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip); - } + this.bitStrip ??= this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip); Span rows = this.bitStrip.Slice(0, bytesPerStrip); rows.Clear(); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs index 01c1833f1..e53f4b420 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -10,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers internal static class TiffColorWriterFactory { public static TiffBaseColorWriter Create( - TiffEncodingMode mode, + TiffPhotometricInterpretation? photometricInterpretation, ImageFrame image, IQuantizer quantizer, MemoryAllocator memoryAllocator, @@ -19,14 +20,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int bitsPerPixel) where TPixel : unmanaged, IPixel { - switch (mode) + switch (photometricInterpretation) { - case TiffEncodingMode.ColorPalette: + case TiffPhotometricInterpretation.PaletteColor: return new TiffPaletteWriter(image, quantizer, memoryAllocator, configuration, entriesCollector, bitsPerPixel); - case TiffEncodingMode.Gray: + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: + if (bitsPerPixel == 1) + { + return new TiffBiColorWriter(image, memoryAllocator, configuration, entriesCollector); + } + return new TiffGrayWriter(image, memoryAllocator, configuration, entriesCollector); - case TiffEncodingMode.BiColor: - return new TiffBiColorWriter(image, memoryAllocator, configuration, entriesCollector); default: return new TiffRgbWriter(image, memoryAllocator, configuration, entriesCollector); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs index 3c318e229..7154b2310 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs @@ -61,8 +61,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void SystemDrawing() { ImageCodecInfo codec = FindCodecForType("image/tiff"); - using var parameters = new EncoderParameters(1); - parameters.Param[0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression)); + using var parameters = new EncoderParameters(1) + { + Param = {[0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression))} + }; using var memoryStream = new MemoryStream(); this.drawing.Save(memoryStream, codec, parameters); @@ -71,15 +73,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "ImageSharp Tiff")] public void TiffCore() { - TiffEncodingMode mode = TiffEncodingMode.Default; + TiffPhotometricInterpretation photometricInterpretation = TiffPhotometricInterpretation.Rgb; - // workaround for 1-bit bug + // Workaround for 1-bit bug if (this.Compression == TiffCompression.CcittGroup3Fax || this.Compression == TiffCompression.Ccitt1D) { - mode = TiffEncodingMode.BiColor; + photometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero; } - var encoder = new TiffEncoder() { Compression = this.Compression, Mode = mode }; + var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation }; using var memoryStream = new MemoryStream(); this.core.SaveAsTiff(memoryStream, encoder); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index fb8354b18..99a74182d 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -31,15 +31,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } [Theory] - [InlineData(TiffEncodingMode.Default, TiffBitsPerPixel.Bit24)] - [InlineData(TiffEncodingMode.Rgb, TiffBitsPerPixel.Bit24)] - [InlineData(TiffEncodingMode.ColorPalette, TiffBitsPerPixel.Bit8)] - [InlineData(TiffEncodingMode.Gray, TiffBitsPerPixel.Bit8)] - [InlineData(TiffEncodingMode.BiColor, TiffBitsPerPixel.Bit1)] - public void EncoderOptions_SetEncodingMode_Works(TiffEncodingMode mode, TiffBitsPerPixel expectedBitsPerPixel) + [InlineData(null, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffBitsPerPixel.Bit8)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffBitsPerPixel.Bit8)] + public void EncoderOptions_SetPhotometricInterpretation_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffBitsPerPixel expectedBitsPerPixel) { // arrange - var tiffEncoder = new TiffEncoder { Mode = mode }; + var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation }; using Image input = new Image(10, 10); using var memStream = new MemoryStream(); @@ -81,30 +80,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } [Theory] - [InlineData(TiffEncodingMode.Default, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] - [InlineData(TiffEncodingMode.Rgb, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] - [InlineData(TiffEncodingMode.Gray, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] - [InlineData(TiffEncodingMode.BiColor, TiffCompression.Deflate, TiffBitsPerPixel.Bit1, TiffCompression.Deflate)] - [InlineData(TiffEncodingMode.ColorPalette, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] - [InlineData(TiffEncodingMode.Default, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] - [InlineData(TiffEncodingMode.Rgb, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] - [InlineData(TiffEncodingMode.ColorPalette, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] - [InlineData(TiffEncodingMode.Gray, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] - [InlineData(TiffEncodingMode.BiColor, TiffCompression.PackBits, TiffBitsPerPixel.Bit1, TiffCompression.PackBits)] - [InlineData(TiffEncodingMode.Rgb, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] - [InlineData(TiffEncodingMode.Gray, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] - [InlineData(TiffEncodingMode.ColorPalette, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] - [InlineData(TiffEncodingMode.BiColor, TiffCompression.CcittGroup3Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup3Fax)] - [InlineData(TiffEncodingMode.BiColor, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)] - [InlineData(TiffEncodingMode.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)] - [InlineData(TiffEncodingMode.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)] - [InlineData(TiffEncodingMode.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] - [InlineData(TiffEncodingMode.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)] - [InlineData(TiffEncodingMode.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] - public void EncoderOptions_SetEncodingModeAndCompression_Works(TiffEncodingMode mode, TiffCompression compression, TiffBitsPerPixel expectedBitsPerPixel, TiffCompression expectedCompression) + [InlineData(null, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] + [InlineData(null, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] + [InlineData(null, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup3Fax)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] + public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffBitsPerPixel expectedBitsPerPixel, TiffCompression expectedCompression) { // arrange - var tiffEncoder = new TiffEncoder { Mode = mode, Compression = compression }; + var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression }; using Image input = new Image(10, 10); using var memStream = new MemoryStream(); @@ -115,8 +113,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff memStream.Position = 0; using var output = Image.Load(Configuration, memStream); ExifProfile exifProfile = output.Frames.RootFrame.Metadata.ExifProfile; - var frameMetaData = TiffFrameMetadata.Parse(exifProfile); - Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); + TiffFrameMetadata rootFrameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedBitsPerPixel, rootFrameMetaData.BitsPerPixel); Assert.Equal(expectedCompression, (TiffCompression)exifProfile.GetValue(ExifTag.Compression).Value); } @@ -146,6 +144,72 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); } + [Fact] + public void TiffEncoder_PreservesBitsPerPixel_WhenInputIsL8() + { + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + var expectedBitsPerPixel = TiffBitsPerPixel.Bit8; + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(Configuration, memStream); + ExifProfile exifProfile = output.Frames.RootFrame.Metadata.ExifProfile; + var frameMetaData = TiffFrameMetadata.Parse(exifProfile); + Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); + } + + [Theory] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.None)] + [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffCompression.Lzw)] + [WithFile(RgbDeflate, PixelTypes.Rgba32, TiffCompression.Deflate)] + [WithFile(RgbPackbits, PixelTypes.Rgba32, TiffCompression.PackBits)] + public void TiffEncoder_PreservesCompression(TestImageProvider provider, TiffCompression expectedCompression) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(Configuration, memStream); + ExifProfile exifProfile = output.Frames.RootFrame.Metadata.ExifProfile; + Assert.Equal(expectedCompression, (TiffCompression)exifProfile.GetValue(ExifTag.Compression).Value); + } + + [Theory] + [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffPredictor.None)] + [WithFile(RgbLzwPredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)] + [WithFile(RgbDeflate, PixelTypes.Rgba32, TiffPredictor.None)] + [WithFile(RgbDeflatePredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)] + public void TiffEncoder_PreservesPredictor(TestImageProvider provider, TiffPredictor expectedPredictor) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(Configuration, memStream); + ExifProfile exifProfile = output.Frames.RootFrame.Metadata.ExifProfile; + Assert.Equal(expectedPredictor, (TiffPredictor)exifProfile.GetValue(ExifTag.Predictor).Value); + } + [Theory] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] @@ -172,15 +236,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } [Theory] - [InlineData(TiffEncodingMode.ColorPalette, TiffCompression.CcittGroup3Fax)] - [InlineData(TiffEncodingMode.ColorPalette, TiffCompression.Ccitt1D)] - [InlineData(TiffEncodingMode.Gray, TiffCompression.Ccitt1D)] - [InlineData(TiffEncodingMode.Rgb, TiffCompression.Ccitt1D)] - public void TiffEncoder_IncompatibilityOptions(TiffEncodingMode mode, TiffCompression compression) + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.CcittGroup3Fax)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Ccitt1D)] + public void TiffEncoder_IncompatibilityOptions_ThrowsImageFormatException(TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) { // arrange using var input = new Image(10, 10); - var encoder = new TiffEncoder() { Mode = mode, Compression = compression }; + var encoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression }; using var memStream = new MemoryStream(); // act @@ -190,154 +252,154 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeRgb_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); [Theory] [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeRgb_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb, TiffCompression.Deflate); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate); [Theory] [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeRgb_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb, TiffCompression.Deflate, TiffPredictor.Horizontal); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffPredictor.Horizontal); [Theory] [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeRgb_WithLzwCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb, TiffCompression.Lzw); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw); [Theory] [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeRgb_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb, TiffCompression.Lzw, TiffPredictor.Horizontal); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffPredictor.Horizontal); [Theory] [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.Rgb, TiffCompression.PackBits); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits); [Theory] [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeGray_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero); [Theory] [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeGray_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray, TiffCompression.Deflate); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); [Theory] [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeGray_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray, TiffCompression.Deflate, TiffPredictor.Horizontal); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffPredictor.Horizontal); [Theory] [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeGray_WithLzwCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray, TiffCompression.Lzw); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw); [Theory] [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeGray_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray, TiffCompression.Lzw, TiffPredictor.Horizontal); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffPredictor.Horizontal); [Theory] [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeGray_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.Gray, TiffCompression.PackBits); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f); + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_With4Bit_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => //// Note: The magick reference decoder does not support 4 bit tiff's, so we use our TIFF decoder instead. - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f, imageDecoder: new TiffDecoder()); + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.001f, imageDecoder: new TiffDecoder()); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, TiffCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f); + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, TiffCompression.Deflate, useExactComparer: false, compareTolerance: 0.001f); + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, TiffCompression.Deflate, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithLzwCompression_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, TiffCompression.Lzw, useExactComparer: false, compareTolerance: 0.001f); + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, TiffCompression.Lzw, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeBiColor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffEncodingMode.BiColor); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.BlackIsZero); [Theory] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeBiColor_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffEncodingMode.BiColor, TiffCompression.Deflate); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); [Theory] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffEncodingMode.BiColor, TiffCompression.PackBits); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); [Theory] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffEncodingMode.BiColor, TiffCompression.CcittGroup3Fax); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax); [Theory] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffEncodingMode.BiColor, TiffCompression.Ccitt1D); + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D); [Theory] - [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncodingMode.Gray, TiffCompression.PackBits)] - [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncodingMode.ColorPalette, TiffCompression.Lzw)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncodingMode.Rgb, TiffCompression.Deflate)] - [WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffEncodingMode.Rgb, TiffCompression.None)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncodingMode.Rgb, TiffCompression.None)] - [WithFile(RgbUncompressed, PixelTypes.Rgb48, TiffEncodingMode.Rgb, TiffCompression.None)] - public void TiffEncoder_StripLength(TestImageProvider provider, TiffEncodingMode mode, TiffCompression compression) + [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits)] + [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate)] + [WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] + [WithFile(RgbUncompressed, PixelTypes.Rgb48, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] + public void TiffEncoder_StripLength(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) where TPixel : unmanaged, IPixel => - TestStripLength(provider, mode, compression); + TestStripLength(provider, photometricInterpretation, compression); [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffEncodingMode.BiColor, TiffCompression.CcittGroup3Fax)] - public void TiffEncoder_StripLength_OutOfBounds(TestImageProvider provider, TiffEncodingMode mode, TiffCompression compression) + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax)] + public void TiffEncoder_StripLength_OutOfBounds(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) where TPixel : unmanaged, IPixel => - //// CcittGroup3Fax compressed data length can be larger than the original length - Assert.Throws(() => TestStripLength(provider, mode, compression)); + //// CcittGroup3Fax compressed data length can be larger than the original length. + Assert.Throws(() => TestStripLength(provider, photometricInterpretation, compression)); - private static void TestStripLength(TestImageProvider provider, TiffEncodingMode mode, TiffCompression compression) + private static void TestStripLength(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) where TPixel : unmanaged, IPixel { // arrange - var tiffEncoder = new TiffEncoder() { Mode = mode, Compression = compression }; + var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression }; using Image input = provider.GetImage(); using var memStream = new MemoryStream(); ExifProfile exifProfileInput = input.Frames.RootFrame.Metadata.ExifProfile; @@ -384,14 +446,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff TestTiffEncoderCore( provider, inputMeta.BitsPerPixel, - mode, + photometricInterpretation, inputCompression); } private static void TestTiffEncoderCore( TestImageProvider provider, TiffBitsPerPixel? bitsPerPixel, - TiffEncodingMode mode, + TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression = TiffCompression.None, TiffPredictor predictor = TiffPredictor.None, bool useExactComparer = true, @@ -402,7 +464,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff using Image image = provider.GetImage(); var encoder = new TiffEncoder { - Mode = mode, + PhotometricInterpretation = photometricInterpretation, BitsPerPixel = bitsPerPixel, Compression = compression, HorizontalPredictor = predictor diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 228eec078..25f0521f9 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaInput.BitsPerPixel); // Save to Tiff - var tiffEncoder = new TiffEncoder() { Mode = TiffEncodingMode.Rgb }; + var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = TiffPhotometricInterpretation.Rgb }; using var ms = new MemoryStream(); image.Save(ms, tiffEncoder); @@ -237,7 +237,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff byte[] encodedImageXmpProfile = rootFrameEncodedImage.Metadata.XmpProfile; Assert.Equal(TiffBitsPerPixel.Bit4, tiffMetaDataEncodedRootFrame.BitsPerPixel); - Assert.Equal(TiffCompression.None, (TiffCompression)encodedImageExifProfile.GetValue(ExifTag.Compression).Value); + Assert.Equal(TiffCompression.Lzw, (TiffCompression)encodedImageExifProfile.GetValue(ExifTag.Compression).Value); Assert.Equal(inputMetaData.HorizontalResolution, encodedImageMetaData.HorizontalResolution); Assert.Equal(inputMetaData.VerticalResolution, encodedImageMetaData.VerticalResolution);