diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
index 0d3aa4bac2..78cf553d34 100644
--- a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
@@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
TiffEncodingMode Mode { get; }
///
- /// Gets a value indicating whether to use horizontal prediction. This can improve the compression ratio with deflate compression.
+ /// Gets a value indicating whether to use horizontal prediction. This can improve the compression ratio with deflate or lzw compression.
///
bool UseHorizontalPredictor { get; }
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
index 2deb063b0c..0d1821704d 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
@@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public TiffEncodingMode Mode { get; set; }
///
- /// Gets or sets a value indicating whether to use horizontal prediction. This can improve the compression ratio with deflate compression.
+ /// Gets or sets a value indicating whether to use horizontal prediction. This can improve the compression ratio with deflate or lzw compression.
///
public bool UseHorizontalPredictor { get; set; }
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs
index 145849d4f5..f2e94d316e 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs
@@ -18,6 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
Deflate,
+ ///
+ /// Use lzw compression.
+ ///
+ Lzw,
+
///
/// Use PackBits to compression the image data.
///
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
index 6bc3b7338b..c5283c74b5 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
@@ -433,6 +433,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
return (ushort)TiffCompression.Deflate;
}
+ if (this.CompressionType == TiffEncoderCompression.Lzw && this.Mode == TiffEncodingMode.Rgb)
+ {
+ return (ushort)TiffCompression.Lzw;
+ }
+
if (this.CompressionType == TiffEncoderCompression.PackBits && this.Mode == TiffEncodingMode.Rgb)
{
return (ushort)TiffCompression.PackBits;
@@ -443,6 +448,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
return (ushort)TiffCompression.Deflate;
}
+ if (this.CompressionType == TiffEncoderCompression.Lzw && this.Mode == TiffEncodingMode.Gray)
+ {
+ return (ushort)TiffCompression.Lzw;
+ }
+
if (this.CompressionType == TiffEncoderCompression.PackBits && this.Mode == TiffEncodingMode.Gray)
{
return (ushort)TiffCompression.PackBits;
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
index d22f14ce5c..b0c20a0db7 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
@@ -5,6 +5,7 @@ using System;
using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Formats.Gif;
+using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
{
@@ -66,9 +67,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
};
///
- /// The working pixel array
+ /// The working pixel array.
///
- private readonly byte[] pixelArray;
+ private readonly IMemoryOwner pixelArray;
///
/// The initial code size.
@@ -200,11 +201,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
///
/// The array of indexed pixels.
/// The color depth in bits.
- public TiffLzwEncoder(byte[] indexedPixels, int colorDepth)
+ public TiffLzwEncoder(IMemoryOwner indexedPixels, int colorDepth)
{
this.pixelArray = indexedPixels;
this.initialCodeSize = Math.Max(2, colorDepth);
+ // TODO: use memory allocator
this.hashTable = ArrayPool.Shared.Rent(HashSize);
this.codeTable = ArrayPool.Shared.Rent(HashSize);
Array.Clear(this.hashTable, 0, HashSize);
@@ -404,13 +406,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
///
private int NextPixel()
{
- if (this.currentPixel == this.pixelArray.Length)
+ if (this.currentPixel == this.pixelArray.Length())
{
return Eof;
}
this.currentPixel++;
- return this.pixelArray[this.currentPixel - 1] & 0xff;
+ return this.pixelArray.GetSpan()[this.currentPixel - 1] & 0xff;
}
///
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
index 52edbdc41d..fce6ec4624 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
@@ -7,6 +7,7 @@ using System.IO;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
+using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
@@ -149,6 +150,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
return this.WriteDeflateCompressedRgb(image, rowSpan, useHorizontalPredictor);
}
+ if (compression == TiffEncoderCompression.Lzw)
+ {
+ return this.WriteLzwCompressedRgb(image, rowSpan, useHorizontalPredictor);
+ }
+
if (compression == TiffEncoderCompression.PackBits)
{
return this.WriteRgbPackBitsCompressed(image, rowSpan);
@@ -205,6 +211,44 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
return bytesWritten;
}
+ ///
+ /// Writes the image data as RGB compressed with lzw to the stream.
+ ///
+ /// 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.
+ /// The number of bytes written.
+ private int WriteLzwCompressedRgb(Image image, Span rowSpan, bool useHorizontalPredictor)
+ where TPixel : unmanaged, IPixel
+ {
+ int bytesWritten = 0;
+ using var memoryStream = new MemoryStream();
+
+ IMemoryOwner pixelData = this.memoryAllocator.Allocate(image.Width * image.Height * 3);
+ Span pixels = pixelData.GetSpan();
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span pixelRow = image.GetPixelRowSpan(y);
+ PixelOperations.Instance.ToRgb24Bytes(this.configuration, pixelRow, rowSpan, pixelRow.Length);
+
+ if (useHorizontalPredictor)
+ {
+ this.ApplyHorizontalPredictionRgb(rowSpan);
+ }
+
+ rowSpan.CopyTo(pixels.Slice(y * image.Width * 3));
+ }
+
+ using var lzwEncoder = new TiffLzwEncoder(pixelData, 8);
+ lzwEncoder.Encode(memoryStream);
+
+ byte[] buffer = memoryStream.ToArray();
+ this.output.Write(buffer);
+ bytesWritten += buffer.Length;
+ return bytesWritten;
+ }
+
///
/// Applies a horizontal predictor to the rgb row.
/// Make use of the fact that many continuous-tone images rarely vary much in pixel value from one pixel to the next.
@@ -425,7 +469,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// The image to write to the stream.
/// The padding bytes for each row.
/// The compression to use.
- /// Indicates if horizontal prediction should be used. Should only be used with deflate compression.
+ /// Indicates if horizontal prediction should be used. Should only be used with deflate or lzw compression.
/// The number of bytes written.
public int WriteGray(Image image, int padding, TiffEncoderCompression compression, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel
@@ -438,6 +482,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
return this.WriteGrayDeflateCompressed(image, rowSpan, useHorizontalPredictor);
}
+ if (compression == TiffEncoderCompression.Lzw)
+ {
+ return this.WriteGrayLzwCompressed(image, rowSpan, useHorizontalPredictor);
+ }
+
if (compression == TiffEncoderCompression.PackBits)
{
return this.WriteGrayPackBitsCompressed(image, rowSpan);
@@ -460,7 +509,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
///
/// The image to write to the stream.
/// A span of a row of pixels.
- /// 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 WriteGrayDeflateCompressed(Image image, Span rowSpan, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel
@@ -492,6 +541,42 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
return bytesWritten;
}
+ ///
+ /// Writes the image data as 8 bit gray with lzw compression to the stream.
+ ///
+ /// The image to write to the stream.
+ /// A span of a row of pixels.
+ /// Indicates if horizontal prediction should be used.
+ /// The number of bytes written.
+ private int WriteGrayLzwCompressed(Image image, Span rowSpan, bool useHorizontalPredictor)
+ where TPixel : unmanaged, IPixel
+ {
+ int bytesWritten = 0;
+ using var memoryStream = new MemoryStream();
+
+ IMemoryOwner pixelData = this.memoryAllocator.Allocate(image.Width * image.Height);
+ Span pixels = pixelData.GetSpan();
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span pixelRow = image.GetPixelRowSpan(y);
+ PixelOperations.Instance.ToL8Bytes(this.configuration, pixelRow, rowSpan, pixelRow.Length);
+ if (useHorizontalPredictor)
+ {
+ this.ApplyHorizontalPredictionGray(rowSpan);
+ }
+
+ rowSpan.CopyTo(pixels.Slice(y * image.Width));
+ }
+
+ using var lzwEncoder = new TiffLzwEncoder(pixelData, 8);
+ lzwEncoder.Encode(memoryStream);
+
+ byte[] buffer = memoryStream.ToArray();
+ this.output.Write(buffer);
+ bytesWritten += buffer.Length;
+ return bytesWritten;
+ }
+
///
/// Applies a horizontal predictor to a gray pixel row.
///
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
index f6a31f43a3..775d55af69 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
@@ -1,11 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils;
-
+using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
@@ -29,9 +30,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
Assert.Equal(data, buffer);
}
- private static Stream CreateCompressedStream(byte[] data)
+ private static Stream CreateCompressedStream(byte[] inputData)
{
- Stream compressedStream = new MemoryStream();
+ using Stream compressedStream = new MemoryStream();
+ using System.Buffers.IMemoryOwner data = Configuration.Default.MemoryAllocator.Allocate(inputData.Length);
+ inputData.AsSpan().CopyTo(data.GetSpan());
using (var encoder = new TiffLzwEncoder(data, 8))
{
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
index ff00edb672..2f0d753884 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
@@ -61,6 +61,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffEncoder_EncodeRgb_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.Rgb, TiffEncoderCompression.Deflate, usePredictor: true);
+ [Theory]
+ [WithFile(TestImages.Tiff.Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
+ public void TiffEncoder_EncodeRgb_WithLzwCompression_Works(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.Rgb, TiffEncoderCompression.Lzw);
+
+ [Theory]
+ [WithFile(TestImages.Tiff.Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
+ public void TiffEncoder_EncodeRgb_WithLzwCompressionAndPredictor_Works(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.Rgb, TiffEncoderCompression.Lzw, usePredictor: true);
+
[Theory]
[WithFile(TestImages.Tiff.Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works(TestImageProvider provider)
@@ -81,6 +91,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffEncoder_EncodeGray_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.Gray, TiffEncoderCompression.Deflate, usePredictor: true);
+ [Theory]
+ [WithFile(TestImages.Tiff.Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
+ public void TiffEncoder_EncodeGray_WithLzwCompression_Works(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.Gray, TiffEncoderCompression.Lzw);
+
+ [Theory]
+ [WithFile(TestImages.Tiff.Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
+ public void TiffEncoder_EncodeGray_WithLzwCompressionAndPredictor_Works(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.Gray, TiffEncoderCompression.Lzw, usePredictor: true);
+
[Theory]
[WithFile(TestImages.Tiff.Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_WithPackBitsCompression_Works(TestImageProvider provider)