From 4e5be469603ba5d2b5a31f861b4802d9d92d826d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 27 Nov 2020 12:31:09 +0100 Subject: [PATCH] Add support for encoding deflate compressed tiff's --- .../Formats/Tiff/TiffEncoderCompression.cs | 5 ++++ .../Formats/Tiff/TiffEncoderCore.cs | 23 +++++++++++---- .../Formats/Tiff/Utils/TiffWriter.cs | 29 ++++++++++++++++++- .../Formats/Tiff/TiffEncoderTests.cs | 24 +++++++++------ 4 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs index 334262dbfc..536cd2c2d3 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs @@ -12,5 +12,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// No compression is used. /// None, + + /// + /// Use zlib compression. + /// + Deflate } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index c493d34a41..99b299d043 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -150,16 +150,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff // Write the image bytes to the steam. var imageDataStart = (uint)writer.Position; int imageDataBytes; - switch (this.PhotometricInterpretation) + switch (this.Mode) { - case TiffPhotometricInterpretation.PaletteColor: + case TiffEncodingMode.ColorPalette: imageDataBytes = writer.WritePalettedRgbImageData(image, this.quantizer, this.padding, out colorMap); break; - case TiffPhotometricInterpretation.BlackIsZero: + case TiffEncodingMode.Gray: imageDataBytes = writer.WriteGrayImageData(image, this.padding); break; default: - imageDataBytes = writer.WriteRgbImageData(image, this.padding); + imageDataBytes = writer.WriteRgbImageData(image, this.padding, this.CompressionType); break; } @@ -260,10 +260,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff Value = bitsPerSampleValue }; + ushort compressionType = this.GetCompressionType(); var compression = new ExifShort(ExifTagValue.Compression) { - // TODO: for the start, no compression is used. - Value = (ushort)TiffCompression.None + Value = compressionType }; var photometricInterpretation = new ExifShort(ExifTagValue.PhotometricInterpretation) @@ -374,5 +374,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff return new ushort[] { 8, 8, 8 }; } } + + private ushort GetCompressionType() + { + if (this.CompressionType == TiffEncoderCompression.Deflate && + this.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb) + { + return (ushort)TiffCompression.Deflate; + } + + return (ushort)TiffCompression.None; + } } } diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs index e99682bc08..8ef57f4b2a 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs @@ -6,6 +6,9 @@ using System.Buffers; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -133,13 +136,37 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The pixel data. /// The image to write to the stream. /// The padding bytes for each row. + /// The compression to use. /// The number of bytes written. - public int WriteRgbImageData(Image image, int padding) + public int WriteRgbImageData(Image image, int padding, TiffEncoderCompression compression) where TPixel : unmanaged, IPixel { using IManagedByteBuffer row = this.AllocateRow(image.Width, 3, padding); Span rowSpan = row.GetSpan(); int bytesWritten = 0; + if (compression == TiffEncoderCompression.Deflate) + { + using var memoryStream = new MemoryStream(); + + // TODO: move zlib compression from png to a common place? + using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, PngCompressionLevel.Level6); // TODO: make compression level configurable + + for (int y = 0; y < image.Height; y++) + { + Span pixelRow = image.GetPixelRowSpan(y); + PixelOperations.Instance.ToRgb24Bytes(this.configuration, pixelRow, rowSpan, pixelRow.Length); + deflateStream.Write(rowSpan); + } + + deflateStream.Flush(); + + byte[] buffer = memoryStream.ToArray(); + this.output.Write(buffer); + bytesWritten += buffer.Length; + return bytesWritten; + } + + // No compression. for (int y = 0; y < image.Height; y++) { Span pixelRow = image.GetPixelRowSpan(y); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 27ca717e66..cef8cecc7e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -3,10 +3,11 @@ using System.IO; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Tiff @@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Trait("Category", "Tiff")] public class TiffEncoderTests { - private static TiffDecoder referenceDecoder = new TiffDecoder(); + private static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(); public static readonly TheoryData TiffBitsPerPixelFiles = new TheoryData @@ -45,18 +46,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_Works(TestImageProvider provider, TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Pixel24, TiffEncodingMode mode = TiffEncodingMode.Rgb) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, bitsPerPixel, mode); + public void TiffEncoder_EncodeRgb_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.Rgb); [Theory] [WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_Works(TestImageProvider provider, TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Pixel8, TiffEncodingMode mode = TiffEncodingMode.Gray) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, bitsPerPixel, mode); + public void TiffEncoder_EncodeRgb_WithDeflateCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.Rgb, TiffEncoderCompression.Deflate); + + [Theory] + [WithFile(TestImages.Tiff.GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.Gray); [Theory] [WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider, TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Pixel24, TiffEncodingMode mode = TiffEncodingMode.ColorPalette) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, bitsPerPixel, mode); + public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette); private static void TestTiffEncoderCore( TestImageProvider provider, @@ -71,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff var encoder = new TiffEncoder { Mode = mode, Compression = compression }; // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "tiff", bitsPerPixel, encoder, useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), referenceDecoder: referenceDecoder); + image.VerifyEncoder(provider, "tiff", bitsPerPixel, encoder, useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), referenceDecoder: ReferenceDecoder); } } }