From c5fa9fd454ecad005dbb8ee21c4f9568704dc8a2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 4 Dec 2020 16:27:29 +0100 Subject: [PATCH] Add option to use lzw with paletted image --- .../Formats/Tiff/TiffEncoderCore.cs | 5 ++ .../Formats/Tiff/Utils/TiffWriter.cs | 51 +++++++++++++++ .../Formats/Tiff/TiffEncoderTests.cs | 62 ++++++++++--------- 3 files changed, 90 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index d64ac2e7d..93deca380 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -463,6 +463,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff return (ushort)TiffCompression.Deflate; } + if (this.CompressionType == TiffEncoderCompression.Lzw && this.Mode == TiffEncodingMode.ColorPalette) + { + return (ushort)TiffCompression.Lzw; + } + if (this.CompressionType == TiffEncoderCompression.PackBits && this.Mode == TiffEncodingMode.ColorPalette) { return (ushort)TiffCompression.PackBits; diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs index 53f763a02..db8bad133 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs @@ -344,6 +344,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils return this.WriteDeflateCompressedPalettedRgb(image, quantized, padding, useHorizontalPredictor); } + if (compression == TiffEncoderCompression.Lzw) + { + return this.WriteLzwCompressedPalettedRgb(image, quantized, padding, useHorizontalPredictor); + } + if (compression == TiffEncoderCompression.PackBits) { return this.WritePackBitsCompressedPalettedRgb(image, quantized, padding); @@ -414,6 +419,52 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils return bytesWritten; } + /// + /// Writes the image data as indices into a color map compressed with lzw compression to the stream. + /// + /// The pixel data. + /// The image to write to the stream. + /// The quantized frame. + /// The padding bytes for each row. + /// Indicates if horizontal prediction should be used. + /// The number of bytes written. + public int WriteLzwCompressedPalettedRgb(Image image, IndexedImageFrame quantized, int padding, bool useHorizontalPredictor) + where TPixel : unmanaged, IPixel + { + IMemoryOwner pixelData = this.memoryAllocator.Allocate(image.Width * image.Height); + using IManagedByteBuffer tmpBuffer = this.memoryAllocator.AllocateManagedByteBuffer(image.Width); + using var memoryStream = new MemoryStream(); + + int bytesWritten = 0; + Span pixels = pixelData.GetSpan(); + for (int y = 0; y < image.Height; y++) + { + ReadOnlySpan indexedPixelRow = quantized.GetPixelRowSpan(y); + + if (useHorizontalPredictor) + { + // We need a writable Span here. + Span pixelRowCopy = tmpBuffer.GetSpan(); + indexedPixelRow.CopyTo(pixelRowCopy); + HorizontalPredictor.ApplyHorizontalPrediction8Bit(pixelRowCopy); + pixelRowCopy.CopyTo(pixels.Slice(y * image.Width)); + } + else + { + indexedPixelRow.CopyTo(pixels.Slice(y * image.Width)); + } + } + + using var lzwEncoder = new TiffLzwEncoder(this.memoryAllocator, pixelData, 8); + lzwEncoder.Encode(memoryStream); + + byte[] buffer = memoryStream.ToArray(); + this.output.Write(buffer); + bytesWritten += buffer.Length; + + return bytesWritten; + } + /// /// Writes the image data as indices into a color map compressed with deflate compression to the stream. /// diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index aa5a84d83..2e6ca6318 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -111,19 +111,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { - // Because a quantizer is used to create the palette (and therefore changes to the original are expected), - // we do not compare the encoded image against the original: - // Instead we load the encoded image with a reference decoder and compare against that image. - using Image image = provider.GetImage(); - using var memStream = new MemoryStream(); var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.None }; - image.Save(memStream, encoder); - memStream.Position = 0; - - using var encodedImage = (Image)Image.Load(memStream); - var encodedImagePath = provider.Utility.SaveTestOutputFile(encodedImage, "tiff", encoder); - TiffTestUtils.CompareWithReferenceDecoder(encodedImagePath, encodedImage); + TiffEncoderPaletteTest(provider, encoder); } [Theory] @@ -131,16 +121,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(); - using var memStream = new MemoryStream(); var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.Deflate }; - image.Save(memStream, encoder); - memStream.Position = 0; - - using var encodedImage = (Image)Image.Load(memStream); - var encodedImagePath = provider.Utility.SaveTestOutputFile(encodedImage, "tiff", encoder); - TiffTestUtils.CompareWithReferenceDecoder(encodedImagePath, encodedImage); + TiffEncoderPaletteTest(provider, encoder); } [Theory] @@ -148,16 +131,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffEncoder_EncodeColorPalette_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(); - using var memStream = new MemoryStream(); - var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.Deflate, UseHorizontalPredictor = true}; + var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.Deflate, UseHorizontalPredictor = true }; - image.Save(memStream, encoder); - memStream.Position = 0; + TiffEncoderPaletteTest(provider, encoder); + } - using var encodedImage = (Image)Image.Load(memStream); - var encodedImagePath = provider.Utility.SaveTestOutputFile(encodedImage, "tiff", encoder); - TiffTestUtils.CompareWithReferenceDecoder(encodedImagePath, encodedImage); + [Theory] + [WithFile(TestImages.Tiff.Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithLzwCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.Lzw }; + + TiffEncoderPaletteTest(provider, encoder); + } + + [Theory] + [WithFile(TestImages.Tiff.Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.Lzw, UseHorizontalPredictor = true }; + + TiffEncoderPaletteTest(provider, encoder); } [Theory] @@ -165,9 +161,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { + var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.PackBits }; + + TiffEncoderPaletteTest(provider, encoder); + } + + private static void TiffEncoderPaletteTest(TestImageProvider provider, TiffEncoder encoder) + where TPixel : unmanaged, IPixel + { + // Because a quantizer is used to create the palette (and therefore changes to the original are expected), + // we do not compare the encoded image against the original: + // Instead we load the encoded image with a reference decoder and compare against that image. using Image image = provider.GetImage(); using var memStream = new MemoryStream(); - var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.PackBits }; image.Save(memStream, encoder); memStream.Position = 0;