diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
index c5283c74b5..d64ac2e7d2 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
@@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
switch (this.Mode)
{
case TiffEncodingMode.ColorPalette:
- imageDataBytes = writer.WritePalettedRgb(image, this.quantizer, this.padding, this.CompressionType, out colorMap);
+ imageDataBytes = writer.WritePalettedRgb(image, this.quantizer, this.padding, this.CompressionType, this.useHorizontalPredictor, out colorMap);
break;
case TiffEncodingMode.Gray:
imageDataBytes = writer.WriteGray(image, this.padding, this.CompressionType, this.useHorizontalPredictor);
@@ -346,7 +346,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
if (this.useHorizontalPredictor)
{
- if (this.Mode == TiffEncodingMode.Rgb || this.Mode == TiffEncodingMode.Gray)
+ if (this.Mode == TiffEncodingMode.Rgb || this.Mode == TiffEncodingMode.Gray || this.Mode == TiffEncodingMode.ColorPalette)
{
var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal };
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
index d427e9c668..53f763a025 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
@@ -217,7 +217,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// The pixel data.
/// The image to write to the stream.
/// A Span for a pixel row.
- /// Indicates if horizontal prediction should be used. Should only be used with deflate compression.
+ /// Indicates if horizontal prediction should be used.
/// The number of bytes written.
private int WriteLzwCompressedRgb(Image image, Span rowSpan, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel
@@ -286,9 +286,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// The quantizer to use.
/// The padding bytes for each row.
/// The compression to use.
+ /// Indicates if horizontal prediction should be used. Should only be used in combination with deflate or LZW compression.
/// The color map.
/// The number of bytes written.
- public int WritePalettedRgb(Image image, IQuantizer quantizer, int padding, TiffEncoderCompression compression, out IExifValue colorMap)
+ public int WritePalettedRgb(Image image, IQuantizer quantizer, int padding, TiffEncoderCompression compression, bool useHorizontalPredictor, out IExifValue colorMap)
where TPixel : unmanaged, IPixel
{
int colorsPerChannel = 256;
@@ -340,7 +341,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
if (compression == TiffEncoderCompression.Deflate)
{
- return this.WriteDeflateCompressedPalettedRgb(image, quantized, padding);
+ return this.WriteDeflateCompressedPalettedRgb(image, quantized, padding, useHorizontalPredictor);
}
if (compression == TiffEncoderCompression.PackBits)
@@ -373,18 +374,31 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// 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 WriteDeflateCompressedPalettedRgb(Image image, IndexedImageFrame quantized, int padding)
+ public int WriteDeflateCompressedPalettedRgb(Image image, IndexedImageFrame quantized, int padding, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel
{
+ using IManagedByteBuffer tmpBuffer = this.memoryAllocator.AllocateManagedByteBuffer(image.Width);
using var memoryStream = new MemoryStream();
using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, PngCompressionLevel.Level6); // TODO: make compression level configurable
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
- ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y);
- deflateStream.Write(pixelSpan);
+ ReadOnlySpan pixelRow = quantized.GetPixelRowSpan(y);
+ if (useHorizontalPredictor)
+ {
+ // We need a writable Span here.
+ Span pixelRowCopy = tmpBuffer.GetSpan();
+ pixelRow.CopyTo(pixelRowCopy);
+ HorizontalPredictor.ApplyHorizontalPrediction8Bit(pixelRowCopy);
+ deflateStream.Write(pixelRowCopy);
+ }
+ else
+ {
+ deflateStream.Write(pixelRow);
+ }
for (int i = 0; i < padding; i++)
{
@@ -423,7 +437,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
{
ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y);
- int size = 0;
+ int size;
if (padding != 0)
{
pixelSpan.CopyTo(pixelRowWithPaddingSpan);
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
index 2f0d753884..aa5a84d83f 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
@@ -143,6 +143,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
TiffTestUtils.CompareWithReferenceDecoder(encodedImagePath, encodedImage);
}
+ [Theory]
+ [WithFile(TestImages.Tiff.Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
+ 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};
+
+ 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);
+ }
+
[Theory]
[WithFile(TestImages.Tiff.Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works(TestImageProvider provider)