From 1dbe5838245b7855209d3ba3b75446740d6fa08c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 1 Mar 2021 11:22:17 +0100 Subject: [PATCH] Allow encoding 4bit color palette images --- .../Compression/Compressors/NoCompressor.cs | 5 +- .../Compressors/T4BitCompressor.cs | 2 + .../Decompressors/DeflateTiffCompression.cs | 2 +- .../Decompressors/LzwTiffCompression.cs | 2 +- .../ModifiedHuffmanTiffCompression.cs | 7 +- .../Decompressors/NoneTiffCompression.cs | 10 +- .../Decompressors/PackBitsTiffCompression.cs | 8 +- .../Decompressors/T4TiffCompression.cs | 9 +- ...Decompresor.cs => TiffBaseDecompressor.cs} | 4 +- .../Tiff/Compression/TiffCompressorFactory.cs | 2 +- .../Compression/TiffDecompressorsFactory.cs | 10 +- .../Formats/Tiff/Constants/TiffConstants.cs | 20 ++++ .../Formats/Tiff/ITiffEncoderOptions.cs | 5 + .../Tiff/TiffBitsPerSampleExtensions.cs | 27 ++--- .../Formats/Tiff/TiffDecoderCore.cs | 4 +- src/ImageSharp/Formats/Tiff/TiffEncoder.cs | 3 + .../Formats/Tiff/TiffEncoderCore.cs | 107 ++++++++++++------ .../Tiff/TiffEncoderEntriesCollector.cs | 22 ++-- .../Tiff/Writers/TiffBaseColorWriter.cs | 8 +- .../Tiff/Writers/TiffColorWriterFactory.cs | 5 +- .../Formats/Tiff/Writers/TiffPaletteWriter.cs | 76 +++++++++---- .../Compression/NoneTiffCompressionTests.cs | 2 +- .../PackBitsTiffCompressionTests.cs | 2 +- .../Formats/Tiff/TiffEncoderTests.cs | 38 ++----- tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Tiff/bike_colorpalette_4bit.tiff | 3 + 26 files changed, 242 insertions(+), 142 deletions(-) rename src/ImageSharp/Formats/Tiff/Compression/{TiffBaseDecompresor.cs => TiffBaseDecompressor.cs} (90%) create mode 100644 tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs index f4a902b46a..79fbd29b4d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs @@ -3,13 +3,14 @@ using System; using System.IO; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors { internal class NoCompressor : TiffBaseCompressor { - public NoCompressor(Stream output) - : base(output, default, default, default) + public NoCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel) + : base(output, memoryAllocator, width, bitsPerPixel) { } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs index 13d19f7ff8..bb631c5a9a 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs @@ -201,8 +201,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors this.useModifiedHuffman = useModifiedHuffman; } + /// public override TiffEncoderCompression Method => this.useModifiedHuffman ? TiffEncoderCompression.ModifiedHuffman : TiffEncoderCompression.CcittGroup3Fax; + /// public override void Initialize(int rowsPerStrip) { // This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good. diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs index be63bdad86..a6ca8c78db 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// /// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type. /// - internal class DeflateTiffCompression : TiffBaseDecompresor + internal class DeflateTiffCompression : TiffBaseDecompressor { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs index a14738e44a..f3b37b09cf 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// /// Class to handle cases where TIFF image data is compressed using LZW compression. /// - internal class LzwTiffCompression : TiffBaseDecompresor + internal class LzwTiffCompression : TiffBaseDecompressor { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs index 7a7cd20f78..9c2495efc2 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs @@ -22,10 +22,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// Initializes a new instance of the class. /// /// The memory allocator. - /// The photometric interpretation. /// The image width. - public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width) - : base(allocator, FaxCompressionOptions.None, photometricInterpretation, width) + /// The number of bits per pixel. + /// The photometric interpretation. + public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) + : base(allocator, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation) { bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; this.whiteValue = (byte)(isWhiteZero ? 0 : 1); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs index f041a00e9e..3475b74cee 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs @@ -4,19 +4,23 @@ using System; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors { /// /// Class to handle cases where TIFF image data is not compressed. /// - internal class NoneTiffCompression : TiffBaseDecompresor + internal class NoneTiffCompression : TiffBaseDecompressor { /// /// Initializes a new instance of the class. /// - public NoneTiffCompression() - : base(default, default, default) + /// The memory allocator. + /// The width of the image. + /// The bits per pixel. + public NoneTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) + : base(memoryAllocator, width, bitsPerPixel) { } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs index c7461a8073..727e0eeb77 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// /// Class to handle cases where TIFF image data is compressed using PackBits compression. /// - internal class PackBitsTiffCompression : TiffBaseDecompresor + internal class PackBitsTiffCompression : TiffBaseDecompressor { private IMemoryOwner compressedDataMemory; @@ -20,8 +20,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// Initializes a new instance of the class. /// /// The memoryAllocator to use for buffer allocations. - public PackBitsTiffCompression(MemoryAllocator memoryAllocator) - : base(memoryAllocator, default, default) + /// The width of the image. + /// The number of bits per pixel. + public PackBitsTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) + : base(memoryAllocator, width, bitsPerPixel) { } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index 275e3d598d..549f75846b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// /// Class to handle cases where TIFF image data is compressed using CCITT T4 compression. /// - internal class T4TiffCompression : TiffBaseDecompresor + internal class T4TiffCompression : TiffBaseDecompressor { private readonly FaxCompressionOptions faxCompressionOptions; @@ -24,11 +24,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// Initializes a new instance of the class. /// /// The memory allocator. + /// The image width. + /// The number of bits per pixel. /// Fax compression options. /// The photometric interpretation. - /// The image width. - public T4TiffCompression(MemoryAllocator allocator, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation, int width) - : base(allocator, width, default) + public T4TiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation) + : base(allocator, width, bitsPerPixel) { this.faxCompressionOptions = faxOptions; diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompresor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs similarity index 90% rename from src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompresor.cs rename to src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs index 5f981911df..2f40214ebe 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompresor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs @@ -13,9 +13,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression /// /// The base tiff decompressor class. /// - internal abstract class TiffBaseDecompresor : TiffBaseCompression + internal abstract class TiffBaseDecompressor : TiffBaseCompression { - protected TiffBaseDecompresor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + protected TiffBaseDecompressor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) : base(allocator, width, bitsPerPixel, predictor) { } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs index eeb14fb749..7cd39d8cbe 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new NoCompressor(output); + return new NoCompressor(output, allocator, width, bitsPerPixel); case TiffEncoderCompression.PackBits: DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index 130205b5b2..641142d4c7 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression { internal static class TiffDecompressorsFactory { - public static TiffBaseDecompresor Create( + public static TiffBaseDecompressor Create( TiffDecoderCompressionType method, MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, @@ -23,12 +23,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression case TiffDecoderCompressionType.None: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new NoneTiffCompression(); + return new NoneTiffCompression(allocator, width, bitsPerPixel); case TiffDecoderCompressionType.PackBits: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new PackBitsTiffCompression(allocator); + return new PackBitsTiffCompression(allocator, width, bitsPerPixel); case TiffDecoderCompressionType.Deflate: DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); @@ -40,11 +40,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression case TiffDecoderCompressionType.T4: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T4TiffCompression(allocator, faxOptions, photometricInterpretation, width); + return new T4TiffCompression(allocator, width, bitsPerPixel, faxOptions, photometricInterpretation); case TiffDecoderCompressionType.HuffmanRle: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new ModifiedHuffmanTiffCompression(allocator, photometricInterpretation, width); + return new ModifiedHuffmanTiffCompression(allocator, width, bitsPerPixel, photometricInterpretation); default: throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs index 8a591fc838..894a6d3486 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -75,6 +75,26 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants /// public const int SizeOfDouble = 8; + /// + /// The bits per sample for 1 bit bicolor images. + /// + public static readonly ushort[] BitsPerSample1Bit = { 1 }; + + /// + /// The bits per sample for images with a 4 color palette. + /// + public static readonly ushort[] BitsPerSample4Bit = { 4 }; + + /// + /// The bits per sample for 8 bit images. + /// + public static readonly ushort[] BitsPerSample8Bit = { 8 }; + + /// + /// The bits per sample for images with 8 bits for each color channel. + /// + public static readonly ushort[] BitsPerSampleRgb8Bit = { 8, 8, 8 }; + /// /// The list of mimetypes that equate to a tiff. /// diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs index de1095eee7..efa5dcf85b 100644 --- a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs +++ b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs @@ -11,6 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// internal interface ITiffEncoderOptions { + /// + /// Gets or sets the number of bits per pixel. + /// + TiffBitsPerPixel? BitsPerPixel { get; set; } + /// /// Gets the compression type to use. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs index 884481b982..307ae4ee9d 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs @@ -3,19 +3,12 @@ using System; using SixLabors.ImageSharp.Formats.Experimental.Tiff; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; 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] /// @@ -26,13 +19,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff switch (tiffBitsPerSample) { case TiffBitsPerSample.One: - return One; + return TiffConstants.BitsPerSample1Bit; case TiffBitsPerSample.Four: - return Four; + return TiffConstants.BitsPerSample4Bit; case TiffBitsPerSample.Eight: - return Eight; + return TiffConstants.BitsPerSample8Bit; case TiffBitsPerSample.Rgb888: - return Rgb888; + return TiffConstants.BitsPerSampleRgb8Bit; default: TiffThrowHelper.ThrowNotSupported("The bits per pixels are not supported"); @@ -50,7 +43,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff switch (bitsPerSample.Length) { case 3: - if (bitsPerSample[0] == Rgb888[0] && bitsPerSample[1] == Rgb888[1] && bitsPerSample[2] == Rgb888[2]) + if (bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit[0] && + bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit[1] && + bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit[2]) { return TiffBitsPerSample.Rgb888; } @@ -58,17 +53,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff break; case 1: - if (bitsPerSample[0] == One[0]) + if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit[0]) { return TiffBitsPerSample.One; } - if (bitsPerSample[0] == Four[0]) + if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit[0]) { return TiffBitsPerSample.Four; } - if (bitsPerSample[0] == Eight[0]) + if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit[0]) { return TiffBitsPerSample.Eight; } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 876816a36f..bedd132fa5 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -250,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize); } - using TiffBaseDecompresor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); + 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); @@ -293,7 +293,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff Buffer2D pixels = frame.PixelBuffer; - using TiffBaseDecompresor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); + 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); diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs index 88204eb556..7e075c2e66 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs @@ -17,6 +17,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// public class TiffEncoder : IImageEncoder, ITiffEncoderOptions { + /// + public TiffBitsPerPixel? BitsPerPixel { get; set; } + /// public TiffEncoderCompression Compression { get; set; } = TiffEncoderCompression.None; diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 6e80899e47..a04b8a809f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -40,11 +41,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// private Configuration configuration; - /// - /// The color depth, in number of bits per pixel. - /// - private TiffBitsPerPixel bitsPerPixel; - /// /// The quantizer for creating color palette image. /// @@ -70,6 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff this.memoryAllocator = memoryAllocator; this.Mode = options.Mode; this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; + this.BitsPerPixel = options.BitsPerPixel; this.UseHorizontalPredictor = options.UseHorizontalPredictor; this.CompressionType = options.Compression; this.compressionLevel = options.CompressionLevel; @@ -82,9 +79,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff internal TiffPhotometricInterpretation PhotometricInterpretation { get; private set; } /// - /// Gets the compression implementation to use when encoding the image. + /// Gets or sets the compression implementation to use when encoding the image. /// - internal TiffEncoderCompression CompressionType { get; } + internal TiffEncoderCompression CompressionType { get; set; } /// /// Gets the encoding mode to use. RGB, RGB with color palette or gray. @@ -97,6 +94,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// internal bool UseHorizontalPredictor { get; } + /// + /// Gets the bits per pixel. + /// + internal TiffBitsPerPixel? BitsPerPixel { get; private set; } + /// /// Encodes the image to the specified stream from the . /// @@ -111,8 +113,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff Guard.NotNull(stream, nameof(stream)); this.configuration = image.GetConfiguration(); + ImageMetadata metadata = image.Metadata; + TiffMetadata tiffMetadata = metadata.GetTiffMetadata(); + this.BitsPerPixel ??= tiffMetadata.BitsPerPixel; - this.SetMode(image); + this.SetMode(tiffMetadata); + this.SetBitsPerPixel(); this.SetPhotometricInterpretation(); using (var writer = new TiffStreamWriter(stream)) @@ -155,20 +161,31 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff // Write the image bytes to the steam. var imageDataStart = (uint)writer.Position; - using TiffBaseCompressor compressor = TiffCompressorFactory.Create( - this.CompressionType, - writer.BaseStream, - this.memoryAllocator, - image.Width, - (int)this.bitsPerPixel, - this.compressionLevel, - this.UseHorizontalPredictor ? TiffPredictor.Horizontal : TiffPredictor.None); - - using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create(this.Mode, image.Frames.RootFrame, this.quantizer, this.memoryAllocator, this.configuration, entriesCollector); - - int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame, colorWriter.BytesPerRow); - - colorWriter.Write(compressor, rowsPerStrip); + TiffBitsPerPixel? tiffBitsPerPixel = this.BitsPerPixel; + if (tiffBitsPerPixel != null) + { + using TiffBaseCompressor compressor = TiffCompressorFactory.Create( + this.CompressionType, + writer.BaseStream, + this.memoryAllocator, + image.Width, + (int)tiffBitsPerPixel, + this.compressionLevel, + this.UseHorizontalPredictor ? TiffPredictor.Horizontal : TiffPredictor.None); + + using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create( + this.Mode, + image.Frames.RootFrame, + this.quantizer, + this.memoryAllocator, + this.configuration, + entriesCollector, + (int)tiffBitsPerPixel); + + int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame.Height, colorWriter.BytesPerRow); + + colorWriter.Write(compressor, rowsPerStrip); + } entriesCollector.ProcessImageFormat(this); entriesCollector.ProcessGeneral(image); @@ -177,12 +194,21 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries); } - private int CalcRowsPerStrip(ImageFrame image, int bytesPerRow) + /// + /// Calculates the number of rows written per strip. + /// + /// The height of the image. + /// The number of bytes per row. + /// Number of rows per strip. + private int CalcRowsPerStrip(int height, int bytesPerRow) { - int sz = this.maxStripBytes > 0 ? this.maxStripBytes : DefaultStripSize; - int height = sz / bytesPerRow; + DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); + DebugGuard.MustBeGreaterThan(bytesPerRow, 0, nameof(bytesPerRow)); + + int stripBytes = this.maxStripBytes > 0 ? this.maxStripBytes : DefaultStripSize; + int rowsPerStrip = stripBytes / bytesPerRow; - return height > 0 ? (height < image.Height ? height : image.Height) : 1; + return rowsPerStrip > 0 ? (rowsPerStrip < height ? rowsPerStrip : height) : 1; } /// @@ -242,14 +268,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff return nextIfdMarker; } - private void SetMode(Image image) + private void SetMode(TiffMetadata tiffMetadata) { + // Make sure, that the fax compressions are only used together with the BiColor mode. if (this.CompressionType == TiffEncoderCompression.CcittGroup3Fax || this.CompressionType == TiffEncoderCompression.ModifiedHuffman) { + // Default means the user has not specified a preferred encoding mode. if (this.Mode == TiffEncodingMode.Default) { this.Mode = TiffEncodingMode.BiColor; - this.bitsPerPixel = TiffBitsPerPixel.Pixel1; return; } @@ -262,36 +289,48 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff if (this.Mode == TiffEncodingMode.Default) { // Preserve input bits per pixel, if no mode was specified. - TiffMetadata tiffMetadata = image.Metadata.GetTiffMetadata(); switch (tiffMetadata.BitsPerPixel) { case TiffBitsPerPixel.Pixel1: this.Mode = TiffEncodingMode.BiColor; break; + case TiffBitsPerPixel.Pixel4: + this.Mode = TiffEncodingMode.ColorPalette; + break; case TiffBitsPerPixel.Pixel8: - this.Mode = tiffMetadata.PhotometricInterpretation != TiffPhotometricInterpretation.PaletteColor ? TiffEncodingMode.Gray : TiffEncodingMode.Rgb; + this.Mode = tiffMetadata.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor ? TiffEncodingMode.ColorPalette : TiffEncodingMode.Gray; + break; default: this.Mode = TiffEncodingMode.Rgb; break; } } + } + private void SetBitsPerPixel() + { switch (this.Mode) { case TiffEncodingMode.BiColor: - this.bitsPerPixel = TiffBitsPerPixel.Pixel1; + this.BitsPerPixel = TiffBitsPerPixel.Pixel1; break; case TiffEncodingMode.ColorPalette: + if (this.BitsPerPixel != TiffBitsPerPixel.Pixel8 && this.BitsPerPixel != TiffBitsPerPixel.Pixel4) + { + this.BitsPerPixel = TiffBitsPerPixel.Pixel8; + } + + break; case TiffEncodingMode.Gray: - this.bitsPerPixel = TiffBitsPerPixel.Pixel8; + this.BitsPerPixel = TiffBitsPerPixel.Pixel8; break; case TiffEncodingMode.Rgb: - this.bitsPerPixel = TiffBitsPerPixel.Pixel24; + this.BitsPerPixel = TiffBitsPerPixel.Pixel24; break; default: this.Mode = TiffEncodingMode.Rgb; - this.bitsPerPixel = TiffBitsPerPixel.Pixel24; + this.BitsPerPixel = TiffBitsPerPixel.Pixel24; break; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index f2e3d9faf7..c8ad38db36 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -298,25 +298,33 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff switch (encoder.PhotometricInterpretation) { case TiffPhotometricInterpretation.PaletteColor: - return new ushort[] { 8 }; + if (encoder.BitsPerPixel == TiffBitsPerPixel.Pixel4) + { + return TiffConstants.BitsPerSample4Bit; + } + else + { + return TiffConstants.BitsPerSample8Bit; + } + case TiffPhotometricInterpretation.Rgb: - return new ushort[] { 8, 8, 8 }; + return TiffConstants.BitsPerSampleRgb8Bit; case TiffPhotometricInterpretation.WhiteIsZero: if (encoder.Mode == TiffEncodingMode.BiColor) { - return new ushort[] { 1 }; + return TiffConstants.BitsPerSample1Bit; } - return new ushort[] { 8 }; + return TiffConstants.BitsPerSample8Bit; case TiffPhotometricInterpretation.BlackIsZero: if (encoder.Mode == TiffEncodingMode.BiColor) { - return new ushort[] { 1 }; + return TiffConstants.BitsPerSample1Bit; } - return new ushort[] { 8 }; + return TiffConstants.BitsPerSample8Bit; default: - return new ushort[] { 8, 8, 8 }; + return TiffConstants.BitsPerSampleRgb8Bit; } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs index f61b294364..70c91fa892 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs @@ -20,13 +20,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers this.MemoryAllocator = memoryAllocator; this.Configuration = configuration; this.EntriesCollector = entriesCollector; - - this.BytesPerRow = ((image.Width * this.BitsPerPixel) + 7) / 8; } public abstract int BitsPerPixel { get; } - public int BytesPerRow { get; } + public int BytesPerRow => ((this.Image.Width * this.BitsPerPixel) + 7) / 8; protected ImageFrame Image { get; } @@ -38,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip) { - DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow || compressor.BytesPerRow == 0, "Values must be equals"); + DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow, "bytes per row of the compressor does not match tiff color writer"); int stripsCount = (this.Image.Height + rowsPerStrip - 1) / rowsPerStrip; uint[] stripOffsets = new uint[stripsCount]; @@ -59,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers stripIndex++; } - DebugGuard.IsTrue(stripIndex == stripsCount, "Values must be equals"); + DebugGuard.IsTrue(stripIndex == stripsCount, "stripIndex and stripsCount should match"); this.AddStripTags(rowsPerStrip, stripOffsets, stripByteCounts); } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs index 10bb9b96ed..4dcb47b47d 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs @@ -15,13 +15,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers IQuantizer quantizer, MemoryAllocator memoryAllocator, Configuration configuration, - TiffEncoderEntriesCollector entriesCollector) + TiffEncoderEntriesCollector entriesCollector, + int bitsPerPixel) where TPixel : unmanaged, IPixel { switch (mode) { case TiffEncodingMode.ColorPalette: - return new TiffPaletteWriter(image, quantizer, memoryAllocator, configuration, entriesCollector); + return new TiffPaletteWriter(image, quantizer, memoryAllocator, configuration, entriesCollector, bitsPerPixel); case TiffEncodingMode.Gray: return new TiffGrayWriter(image, memoryAllocator, configuration, entriesCollector); case TiffEncodingMode.BiColor: diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs index d15bc5e17a..b094c22fc0 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Runtime.InteropServices; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Memory; @@ -16,52 +17,85 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers internal sealed class TiffPaletteWriter : TiffBaseColorWriter where TPixel : unmanaged, IPixel { - private const int ColorsPerChannel = 256; - private const int ColorPaletteSize = ColorsPerChannel * 3; - private const int ColorPaletteBytes = ColorPaletteSize * 2; - - private readonly IndexedImageFrame quantized; - - public TiffPaletteWriter(ImageFrame image, IQuantizer quantizer, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + private readonly int maxColors; + private readonly int colorPaletteSize; + private readonly int colorPaletteBytes; + private readonly IndexedImageFrame quantizedImage; + + public TiffPaletteWriter( + ImageFrame image, + IQuantizer quantizer, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector, + int bitsPerPixel) : base(image, memoryAllocator, configuration, entriesCollector) { - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration); - this.quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + DebugGuard.NotNull(quantizer, nameof(quantizer)); + DebugGuard.NotNull(configuration, nameof(configuration)); + DebugGuard.NotNull(entriesCollector, nameof(entriesCollector)); + DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel)); + + this.BitsPerPixel = bitsPerPixel; + this.maxColors = this.BitsPerPixel == 4 ? 16 : 256; + this.colorPaletteSize = this.maxColors * 3; + this.colorPaletteBytes = this.colorPaletteSize * 2; + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration, new QuantizerOptions() + { + MaxColors = this.maxColors + }); + this.quantizedImage = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - this.AddTag(this.quantized); + this.AddColorMapTag(); } /// - public override int BitsPerPixel => 8; + public override int BitsPerPixel { get; } /// protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) { - Span pixels = GetStripPixels(((IPixelSource)this.quantized).PixelBuffer, y, height); - compressor.CompressStrip(pixels, height); + Span pixels = GetStripPixels(((IPixelSource)this.quantizedImage).PixelBuffer, y, height); + if (this.BitsPerPixel == 4) + { + using IMemoryOwner rows4bitBuffer = this.MemoryAllocator.Allocate(pixels.Length / 2); + Span rows4bit = rows4bitBuffer.GetSpan(); + int idx = 0; + for (int i = 0; i < rows4bit.Length; i++) + { + rows4bit[i] = (byte)((pixels[idx] << 4) | (pixels[idx + 1] & 0xF)); + idx += 2; + } + + compressor.CompressStrip(rows4bit, height); + } + else + { + compressor.CompressStrip(pixels, height); + } } /// - protected override void Dispose(bool disposing) => this.quantized?.Dispose(); + protected override void Dispose(bool disposing) => this.quantizedImage?.Dispose(); - private void AddTag(IndexedImageFrame quantized) + private void AddColorMapTag() { - using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(ColorPaletteBytes); + using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(this.colorPaletteBytes); Span colorPalette = colorPaletteBuffer.GetSpan(); - ReadOnlySpan quantizedColors = quantized.Palette.Span; + ReadOnlySpan quantizedColors = this.quantizedImage.Palette.Span; int quantizedColorBytes = quantizedColors.Length * 3 * 2; - // In the ColorMap, black is represented by 0,0,0 and white is represented by 65535, 65535, 65535. + // In the ColorMap, black is represented by 0, 0, 0 and white is represented by 65535, 65535, 65535. Span quantizedColorRgb48 = MemoryMarshal.Cast(colorPalette.Slice(0, quantizedColorBytes)); PixelOperations.Instance.ToRgb48(this.Configuration, quantizedColors, quantizedColorRgb48); - // It can happen that the quantized colors are less than the expected 256 per channel. - var diffToMaxColors = ColorsPerChannel - quantizedColors.Length; + // It can happen that the quantized colors are less than the expected maximum per channel. + var diffToMaxColors = this.maxColors - quantizedColors.Length; // In a TIFF ColorMap, all the Red values come first, followed by the Green values, // then the Blue values. Convert the quantized palette to this format. - var palette = new ushort[ColorPaletteSize]; + var palette = new ushort[this.colorPaletteSize]; int paletteIdx = 0; for (int i = 0; i < quantizedColors.Length; i++) { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs index 466027bee4..d5435a8a4d 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); var buffer = new byte[expectedResult.Length]; - new NoneTiffCompression().Decompress(stream, 0, byteCount, buffer); + new NoneTiffCompression(default, default, default).Decompress(stream, 0, byteCount, buffer); Assert.Equal(expectedResult, buffer); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs index a211bde535..d0649934d5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); var buffer = new byte[expectedResult.Length]; - using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator()); + using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator(), default, default); decompressor.Decompress(stream, 0, (uint)inputData.Length, buffer); Assert.Equal(expectedResult, buffer); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index d4f68ff660..57a4356298 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.IO; using SixLabors.ImageSharp.Formats; @@ -38,7 +37,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.None, TiffBitsPerPixel.Pixel1, TiffCompression.None)] [InlineData(TiffEncodingMode.Default, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel24, TiffCompression.Deflate)] [InlineData(TiffEncodingMode.Rgb, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel24, TiffCompression.Deflate)] - [InlineData(TiffEncodingMode.ColorPalette, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel8, TiffCompression.Deflate)] [InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel8, TiffCompression.Deflate)] [InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.Deflate, TiffBitsPerPixel.Pixel1, TiffCompression.Deflate)] [InlineData(TiffEncodingMode.Default, TiffEncoderCompression.PackBits, TiffBitsPerPixel.Pixel24, TiffCompression.PackBits)] @@ -47,7 +45,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.PackBits, TiffBitsPerPixel.Pixel8, TiffCompression.PackBits)] [InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.PackBits, TiffBitsPerPixel.Pixel1, TiffCompression.PackBits)] [InlineData(TiffEncodingMode.Rgb, TiffEncoderCompression.Lzw, TiffBitsPerPixel.Pixel24, TiffCompression.Lzw)] - [InlineData(TiffEncodingMode.ColorPalette, TiffEncoderCompression.Lzw, TiffBitsPerPixel.Pixel8, TiffCompression.Lzw)] [InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.Lzw, TiffBitsPerPixel.Pixel8, TiffCompression.Lzw)] [InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.CcittGroup3Fax, TiffBitsPerPixel.Pixel1, TiffCompression.CcittGroup3Fax)] [InlineData(TiffEncodingMode.BiColor, TiffEncoderCompression.ModifiedHuffman, TiffBitsPerPixel.Pixel1, TiffCompression.Ccitt1D)] @@ -73,7 +70,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel1)] [WithFile(GrayscaleUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel8)] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel24)] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel24)] + [WithFile(Rgb4BitPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel4)] + [WithFile(RgbPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel8)] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Pixel8)] public void TiffEncoder_PreserveBitsPerPixel(TestImageProvider provider, TiffBitsPerPixel expectedBitsPerPixel) where TPixel : unmanaged, IPixel { @@ -97,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncoderCompression.ModifiedHuffman, TiffCompression.Ccitt1D)] [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncoderCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncoderCompression.ModifiedHuffman, TiffCompression.Ccitt1D)] - public void TiffEncoder_CorrectBiMode(TestImageProvider provider, TiffEncoderCompression compression, TiffCompression expectedCompression) + public void TiffEncoder_EncodesWithCorrectBiColorModeCompression(TestImageProvider provider, TiffEncoderCompression compression, TiffCompression expectedCompression) where TPixel : unmanaged, IPixel { // arrange @@ -197,37 +196,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f); + TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.ColorPalette, 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.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.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.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Deflate, usePredictor: true, 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.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Lzw, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) + [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_With4Bit_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Lzw, usePredictor: true, useExactComparer: false, compareTolerance: 0.001f); + TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f); + TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.ColorPalette, TiffEncoderCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f); [Theory] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] @@ -335,6 +316,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff var encoder = new TiffEncoder { Mode = mode, + BitsPerPixel = bitsPerPixel, Compression = compression, UseHorizontalPredictor = usePredictor, MaxStripBytes = maxStripSize diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index aa9883e96e..49d0e759cd 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -556,6 +556,7 @@ namespace SixLabors.ImageSharp.Tests public const string RgbPackbitsMultistrip = "Tiff/rgb_packbits_multistrip.tiff"; public const string RgbUncompressed = "Tiff/rgb_uncompressed.tiff"; public const string RgbPalette = "Tiff/rgb_palette.tiff"; + public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; diff --git a/tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff b/tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff new file mode 100644 index 0000000000..d76966336d --- /dev/null +++ b/tests/Images/Input/Tiff/bike_colorpalette_4bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc9047bc28ff5256530a7203bf73fa0a7ed1545736cd57fabdaff2d600cfa3f1 +size 132265