diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 2bacf7c51..4d7bbbeb7 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -48,7 +48,7 @@ |Lzw | | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case | |Old Jpeg | | | We should not even try to support this | |Jpeg (Technote 2) | | | | -|Deflate (Technote 2) | (Y) | Y | Based on PNG Deflate. Deflate encoding only for RGB now, should we allow this for the gray and palette too? | +|Deflate (Technote 2) | (Y) | Y | Based on PNG Deflate. Deflate encoding only for RGB now gray, should we allow this for palette too? | |Old Deflate (Technote 2) | | Y | | ### Photometric Interpretation Formats diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 6dee09932..f070eab31 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff imageDataBytes = writer.WritePalettedRgb(image, this.quantizer, this.padding, out colorMap); break; case TiffEncodingMode.Gray: - imageDataBytes = writer.WriteGray(image, this.padding); + imageDataBytes = writer.WriteGray(image, this.padding, this.CompressionType); break; default: imageDataBytes = writer.WriteRgbImageData(image, this.padding, this.CompressionType); @@ -378,7 +378,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff private ushort GetCompressionType() { if (this.CompressionType == TiffEncoderCompression.Deflate && - this.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb) + this.Mode == TiffEncodingMode.Rgb) + { + return (ushort)TiffCompression.Deflate; + } + + if (this.CompressionType == TiffEncoderCompression.Deflate && + this.Mode == TiffEncodingMode.Gray) { return (ushort)TiffCompression.Deflate; } diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs index c95378356..028d53ab8 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs @@ -272,12 +272,19 @@ 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 WriteGray(Image image, int padding) + public int WriteGray(Image image, int padding, TiffEncoderCompression compression) where TPixel : unmanaged, IPixel { using IManagedByteBuffer row = this.AllocateRow(image.Width, 1, padding); Span rowSpan = row.GetSpan(); + + if (compression == TiffEncoderCompression.Deflate) + { + return this.WriteGrayDeflateCompressed(image, rowSpan); + } + int bytesWritten = 0; for (int y = 0; y < image.Height; y++) { @@ -290,6 +297,37 @@ namespace SixLabors.ImageSharp.Formats.Tiff return bytesWritten; } + /// + /// Writes the image data as 8 bit gray with deflate compression to the stream. + /// + /// The image to write to the stream. + /// A span of a pixel row. + /// The number of bytes written. + private int WriteGrayDeflateCompressed(Image image, Span rowSpan) + where TPixel : unmanaged, IPixel + { + int bytesWritten = 0; + 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.ToL8Bytes(this.configuration, pixelRow, rowSpan, pixelRow.Length); + deflateStream.Write(rowSpan); + } + + deflateStream.Flush(); + + byte[] buffer = memoryStream.ToArray(); + this.output.Write(buffer); + bytesWritten += buffer.Length; + return bytesWritten; + } + private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel, int padding) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, padding); /// diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 9d24132a4..458acd34c 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -8,6 +8,7 @@ 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 @@ -59,6 +60,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffEncoder_EncodeGray_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.Gray); + [Theory] + [WithFile(TestImages.Tiff.GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithDeflateCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.Gray, TiffEncoderCompression.Deflate); + + // TODO: this test fails, but the output looks correct. I thinks its due to the fact that a quantizer is used to create the palette. [Theory] [WithFile(TestImages.Tiff.Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider)