diff --git a/src/ImageSharp/Formats/Tiff/Compression/PackBitsWriter.cs b/src/ImageSharp/Formats/Tiff/Compression/PackBitsWriter.cs index 389ad628e..1677976e4 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/PackBitsWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/PackBitsWriter.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// internal static class PackBitsWriter { - public static int PackBits(Span rowSpan, Span compressedRowSpan) + public static int PackBits(ReadOnlySpan rowSpan, Span compressedRowSpan) { int maxRunLength = 127; int posInRowSpan = 0; @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression return bytesWritten; } - private static void WriteLiteralRun(Span rowSpan, int end, int literalRunLength, Span compressedRowSpan, int compressedRowPos) + private static void WriteLiteralRun(ReadOnlySpan rowSpan, int end, int literalRunLength, Span compressedRowSpan, int compressedRowPos) { DebugGuard.MustBeLessThanOrEqualTo(literalRunLength, 127, nameof(literalRunLength)); @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression rowSpan.Slice(literalRunStart, literalRunLength).CopyTo(compressedRowSpan.Slice(compressedRowPos + 1)); } - private static void WriteRun(Span rowSpan, int start, int runLength, Span compressedRowSpan, int compressedRowPos) + private static void WriteRun(ReadOnlySpan rowSpan, int start, int runLength, Span compressedRowSpan, int compressedRowPos) { DebugGuard.MustBeLessThanOrEqualTo(runLength, 127, nameof(runLength)); @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression compressedRowSpan[compressedRowPos + 1] = rowSpan[start]; } - private static bool IsReplicateRun(Span rowSpan, int startPos) + private static bool IsReplicateRun(ReadOnlySpan rowSpan, int startPos) { // We consider run which has at least 3 same consecutive bytes a candidate for a run. var startByte = rowSpan[startPos]; @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression return false; } - private static int FindRunLength(Span rowSpan, int startPos, int maxRunLength) + private static int FindRunLength(ReadOnlySpan rowSpan, int startPos, int maxRunLength) { var startByte = rowSpan[startPos]; int count = 1; diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index f3b138fbf..5e636c097 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -436,6 +436,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff return (ushort)TiffCompression.Deflate; } + if (this.CompressionType == TiffEncoderCompression.PackBits && this.Mode == TiffEncodingMode.ColorPalette) + { + return (ushort)TiffCompression.PackBits; + } + if (this.CompressionType == TiffEncoderCompression.Deflate && this.Mode == TiffEncodingMode.BiColor) { return (ushort)TiffCompression.Deflate; diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs index 3060430ec..1cea6eaab 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs @@ -289,6 +289,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils return this.WriteDeflateCompressedPalettedRgb(image, quantized, padding); } + if (compression == TiffEncoderCompression.PackBits) + { + return this.WritePackBitsCompressedPalettedRgb(image, quantized, padding); + } + // No compression. int bytesWritten = 0; for (int y = 0; y < image.Height; y++) @@ -341,6 +346,47 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils return bytesWritten; } + /// + /// Writes the image data as indices into a color map compressed with deflate compression to the stream. + /// + /// The pixel data. + /// The image to write to the stream. + /// The quantized frame. + /// The padding bytes for each row. + /// The number of bytes written. + public int WritePackBitsCompressedPalettedRgb(Image image, IndexedImageFrame quantized, int padding) + where TPixel : unmanaged, IPixel + { + // Worst case is that the actual compressed data is larger then the input data. In this case we need 1 additional byte per 127 bytes. + int additionalBytes = (image.Width * 3 / 127) + 1; + using IManagedByteBuffer compressedRow = this.memoryAllocator.AllocateManagedByteBuffer((image.Width * 3) + additionalBytes, AllocationOptions.Clean); + using IManagedByteBuffer pixelRowWithPadding = this.memoryAllocator.AllocateManagedByteBuffer((image.Width * 3) + padding, AllocationOptions.Clean); + Span compressedRowSpan = compressedRow.GetSpan(); + Span pixelRowWithPaddingSpan = pixelRowWithPadding.GetSpan(); + + int bytesWritten = 0; + for (int y = 0; y < image.Height; y++) + { + ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y); + + int size = 0; + if (padding != 0) + { + pixelSpan.CopyTo(pixelRowWithPaddingSpan); + size = PackBitsWriter.PackBits(pixelRowWithPaddingSpan, compressedRowSpan); + } + else + { + size = PackBitsWriter.PackBits(pixelSpan, compressedRowSpan); + } + + this.output.Write(compressedRowSpan.Slice(0, size)); + bytesWritten += size; + } + + return bytesWritten; + } + /// /// Writes the image data as 8 bit gray to the stream. /// diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 294a9c0cf..34b713d6e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -88,6 +88,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, 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_WithPackBitsCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.PackBits); + [Theory] [WithFile(TestImages.Tiff.Calliphora_BiColor, PixelTypes.Rgba32)] public void TiffEncoder_EncodeBiColor_Works(TestImageProvider provider)