diff --git a/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
index 8251f9aab..772d782ef 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
@@ -4,6 +4,7 @@
using System;
using System.IO;
using System.IO.Compression;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
diff --git a/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
index 0e98d5303..ffce22145 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
@@ -3,6 +3,7 @@
using System;
using System.IO;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
diff --git a/src/ImageSharp/Formats/Tiff/Compression/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/ModifiedHuffmanTiffCompression.cs
index 63908ff2f..95a41ed54 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/ModifiedHuffmanTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/ModifiedHuffmanTiffCompression.cs
@@ -3,6 +3,7 @@
using System;
using System.IO;
+
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
diff --git a/src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs
index 39b7ca23d..a8bfe624d 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs
@@ -3,6 +3,8 @@
using System;
using System.IO;
+
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
diff --git a/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
index dc89b650e..a473fcf26 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
@@ -4,6 +4,8 @@
using System;
using System.Buffers;
using System.IO;
+
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
diff --git a/src/ImageSharp/Formats/Tiff/Compression/PackBitsWriter.cs b/src/ImageSharp/Formats/Tiff/Compression/PackBitsWriter.cs
new file mode 100644
index 000000000..389ad628e
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/PackBitsWriter.cs
@@ -0,0 +1,128 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.Compression
+{
+ ///
+ /// Pack Bits compression for tiff images. See Tiff Spec v6, section 9.
+ ///
+ internal static class PackBitsWriter
+ {
+ public static int PackBits(Span rowSpan, Span compressedRowSpan)
+ {
+ int maxRunLength = 127;
+ int posInRowSpan = 0;
+ int bytesWritten = 0;
+ int literalRunLength = 0;
+
+ while (posInRowSpan < rowSpan.Length)
+ {
+ var useReplicateRun = IsReplicateRun(rowSpan, posInRowSpan);
+ if (useReplicateRun)
+ {
+ if (literalRunLength > 0)
+ {
+ WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten);
+ bytesWritten += literalRunLength + 1;
+ }
+
+ // Write a run with the same bytes.
+ var runLength = FindRunLength(rowSpan, posInRowSpan, maxRunLength);
+ WriteRun(rowSpan, posInRowSpan, runLength, compressedRowSpan, bytesWritten);
+
+ bytesWritten += 2;
+ literalRunLength = 0;
+ posInRowSpan += runLength;
+ continue;
+ }
+
+ literalRunLength++;
+ posInRowSpan++;
+
+ if (literalRunLength >= maxRunLength)
+ {
+ WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten);
+ bytesWritten += literalRunLength + 1;
+ literalRunLength = 0;
+ }
+ }
+
+ if (literalRunLength > 0)
+ {
+ WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten);
+ bytesWritten += literalRunLength + 1;
+ }
+
+ return bytesWritten;
+ }
+
+ private static void WriteLiteralRun(Span rowSpan, int end, int literalRunLength, Span compressedRowSpan, int compressedRowPos)
+ {
+ DebugGuard.MustBeLessThanOrEqualTo(literalRunLength, 127, nameof(literalRunLength));
+
+ int literalRunStart = end - literalRunLength;
+ sbyte runLength = (sbyte)(literalRunLength - 1);
+ compressedRowSpan[compressedRowPos] = (byte)runLength;
+ rowSpan.Slice(literalRunStart, literalRunLength).CopyTo(compressedRowSpan.Slice(compressedRowPos + 1));
+ }
+
+ private static void WriteRun(Span rowSpan, int start, int runLength, Span compressedRowSpan, int compressedRowPos)
+ {
+ DebugGuard.MustBeLessThanOrEqualTo(runLength, 127, nameof(runLength));
+
+ sbyte headerByte = (sbyte)(-runLength + 1);
+ compressedRowSpan[compressedRowPos] = (byte)headerByte;
+ compressedRowSpan[compressedRowPos + 1] = rowSpan[start];
+ }
+
+ private static bool IsReplicateRun(Span rowSpan, int startPos)
+ {
+ // We consider run which has at least 3 same consecutive bytes a candidate for a run.
+ var startByte = rowSpan[startPos];
+ int count = 0;
+ for (int i = startPos + 1; i < rowSpan.Length; i++)
+ {
+ if (rowSpan[i] == startByte)
+ {
+ count++;
+ if (count >= 2)
+ {
+ return true;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ private static int FindRunLength(Span rowSpan, int startPos, int maxRunLength)
+ {
+ var startByte = rowSpan[startPos];
+ int count = 1;
+ for (int i = startPos + 1; i < rowSpan.Length; i++)
+ {
+ if (rowSpan[i] == startByte)
+ {
+ count++;
+ }
+ else
+ {
+ break;
+ }
+
+ if (count == maxRunLength)
+ {
+ break;
+ }
+ }
+
+ return count;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs
index 91518c662..d2bc2f47e 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs
@@ -3,6 +3,7 @@
using System;
using System.Numerics;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs
index 0af9d8698..eb3381b70 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs
@@ -3,6 +3,7 @@
using System;
using System.Numerics;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs
index 3bd263ef3..3f96bc220 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs
index bcc303f17..74a4b9496 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs
@@ -3,6 +3,7 @@
using System;
using System.Numerics;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs
index dda338d3b..8257dcec2 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs
@@ -3,6 +3,7 @@
using System;
using System.Numerics;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs
index c76935b3a..1bd84a60a 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs
@@ -18,6 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
///
Deflate,
+ ///
+ /// Use PackBits to compression the image data.
+ ///
+ PackBits,
+
///
/// Use CCITT T4 1D compression. Note: This is only valid for bi-level images.
///
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
index f4e516168..f3b138fbf 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
@@ -7,6 +7,7 @@ using System.IO;
using System.Threading;
using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@@ -166,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
imageDataBytes = writer.WriteBiColor(image, this.CompressionType);
break;
default:
- imageDataBytes = writer.WriteRgbImageData(image, this.padding, this.CompressionType);
+ imageDataBytes = writer.WriteRgb(image, this.padding, this.CompressionType);
break;
}
@@ -415,11 +416,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return (ushort)TiffCompression.Deflate;
}
+ if (this.CompressionType == TiffEncoderCompression.PackBits && this.Mode == TiffEncodingMode.Rgb)
+ {
+ return (ushort)TiffCompression.PackBits;
+ }
+
if (this.CompressionType == TiffEncoderCompression.Deflate && this.Mode == TiffEncodingMode.Gray)
{
return (ushort)TiffCompression.Deflate;
}
+ if (this.CompressionType == TiffEncoderCompression.PackBits && this.Mode == TiffEncodingMode.Gray)
+ {
+ return (ushort)TiffCompression.PackBits;
+ }
+
if (this.CompressionType == TiffEncoderCompression.Deflate && this.Mode == TiffEncodingMode.ColorPalette)
{
return (ushort)TiffCompression.Deflate;
@@ -430,6 +441,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return (ushort)TiffCompression.Deflate;
}
+ if (this.CompressionType == TiffEncoderCompression.PackBits && this.Mode == TiffEncodingMode.BiColor)
+ {
+ return (ushort)TiffCompression.PackBits;
+ }
+
if (this.CompressionType == TiffEncoderCompression.CcittGroup3Fax && this.Mode == TiffEncodingMode.BiColor)
{
return (ushort)TiffCompression.CcittGroup3Fax;
diff --git a/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs
index 0093f342a..40e67c1b0 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs
@@ -3,7 +3,7 @@
using System;
-namespace SixLabors.ImageSharp.Formats.Tiff
+namespace SixLabors.ImageSharp.Formats.Tiff.Utils
{
///
/// Utility class to read a sequence of bits from an array
diff --git a/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs b/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs
index 1a4da9a31..e83c1f062 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs
@@ -4,7 +4,7 @@
using System;
using System.IO;
-namespace SixLabors.ImageSharp.Formats.Tiff
+namespace SixLabors.ImageSharp.Formats.Tiff.Utils
{
///
/// Utility class to encapsulate a sub-portion of another .
@@ -173,4 +173,4 @@ namespace SixLabors.ImageSharp.Formats.Tiff
throw new NotSupportedException();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
index 2e95d7e5a..4322b04b1 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
@@ -7,7 +7,7 @@ using System.IO;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Memory;
-namespace SixLabors.ImageSharp.Formats.Tiff
+namespace SixLabors.ImageSharp.Formats.Tiff.Utils
{
///
/// Decompresses and decodes data using the dynamic LZW algorithms.
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
index a18a820a3..f05f596bf 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -6,7 +6,7 @@ using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Formats.Gif;
-namespace SixLabors.ImageSharp.Formats.Tiff
+namespace SixLabors.ImageSharp.Formats.Tiff.Utils
{
///
/// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
@@ -492,4 +492,4 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.isDisposed = true;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
index 5c68ca14d..4112ba4ba 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
@@ -4,7 +4,7 @@
using System;
using System.IO;
-namespace SixLabors.ImageSharp.Formats.Tiff
+namespace SixLabors.ImageSharp.Formats.Tiff.Utils
{
///
/// TIFF specific utilities and extension methods.
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
index 245bdb74e..f57f05645 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
@@ -3,7 +3,6 @@
using System;
using System.Buffers;
-using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
@@ -17,7 +16,7 @@ using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
-namespace SixLabors.ImageSharp.Formats.Tiff
+namespace SixLabors.ImageSharp.Formats.Tiff.Utils
{
///
/// Utility class for writing TIFF data to a .
@@ -32,8 +31,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private readonly byte[] paddingBytes = new byte[4];
- private readonly List references = new List();
-
///
/// Initializes a new instance of the class.
///
@@ -141,7 +138,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// The padding bytes for each row.
/// The compression to use.
/// The number of bytes written.
- public int WriteRgbImageData(Image image, int padding, TiffEncoderCompression compression)
+ public int WriteRgb(Image image, int padding, TiffEncoderCompression compression)
where TPixel : unmanaged, IPixel
{
using IManagedByteBuffer row = this.AllocateRow(image.Width, 3, padding);
@@ -151,6 +148,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return this.WriteDeflateCompressedRgb(image, rowSpan);
}
+ if (compression == TiffEncoderCompression.PackBits)
+ {
+ return this.WriteRgbPackBitsCompressed(image, rowSpan);
+ }
+
// No compression.
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
@@ -195,6 +197,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return bytesWritten;
}
+ ///
+ /// Writes the image data as RGB with packed bits compression to the stream.
+ ///
+ /// The pixel data.
+ /// The image to write to the stream.
+ /// A Span for a pixel row.
+ /// The number of bytes written.
+ private int WriteRgbPackBitsCompressed(Image image, Span rowSpan)
+ 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);
+ Span compressedRowSpan = compressedRow.GetSpan();
+ int bytesWritten = 0;
+
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span pixelRow = image.GetPixelRowSpan(y);
+ PixelOperations.Instance.ToRgb24Bytes(this.configuration, pixelRow, rowSpan, pixelRow.Length);
+ int size = PackBitsWriter.PackBits(rowSpan, compressedRowSpan);
+ this.output.Write(compressedRow.Slice(0, size));
+ bytesWritten += size;
+ compressedRowSpan.Clear();
+ }
+
+ return bytesWritten;
+ }
+
///
/// Writes the image data as indices into a color map to the stream.
///
@@ -316,6 +347,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return this.WriteGrayDeflateCompressed(image, rowSpan);
}
+ if (compression == TiffEncoderCompression.PackBits)
+ {
+ return this.WriteGrayPackBitsCompressed(image, rowSpan);
+ }
+
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
@@ -358,6 +394,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return bytesWritten;
}
+ ///
+ /// Writes the image data as 8 bit gray to the stream.
+ ///
+ /// The pixel data.
+ /// The image to write to the stream.
+ /// A span of a row of pixels.
+ /// The number of bytes written.
+ private int WriteGrayPackBitsCompressed(Image image, Span rowSpan)
+ 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 / 127) + 1;
+ using IManagedByteBuffer compressedRow = this.memoryAllocator.AllocateManagedByteBuffer(image.Width + additionalBytes, AllocationOptions.Clean);
+ Span compressedRowSpan = compressedRow.GetSpan();
+
+ int bytesWritten = 0;
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span pixelRow = image.GetPixelRowSpan(y);
+ PixelOperations.Instance.ToL8Bytes(this.configuration, pixelRow, rowSpan, pixelRow.Length);
+ int size = PackBitsWriter.PackBits(rowSpan, compressedRowSpan);
+ this.output.Write(compressedRow.Slice(0, size));
+ bytesWritten += size;
+ compressedRowSpan.Clear();
+ }
+
+ return bytesWritten;
+ }
+
///
/// Writes the image data as 1 bit black and white to the stream.
///
@@ -385,6 +450,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return this.WriteBiColorDeflate(imageBlackWhite, pixelRowAsGraySpan, outputRow);
}
+ if (compression == TiffEncoderCompression.PackBits)
+ {
+ return this.WriteBiColorPackBits(imageBlackWhite, pixelRowAsGraySpan, outputRow);
+ }
+
if (compression == TiffEncoderCompression.CcittGroup3Fax)
{
var bitWriter = new T4BitWriter(this.memoryAllocator, this.configuration);
@@ -397,6 +467,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return bitWriter.CompressImage(imageBlackWhite, pixelRowAsGraySpan, this.output);
}
+ // Write image uncompressed.
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
@@ -479,6 +550,56 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return bytesWritten;
}
+ ///
+ /// Writes the image data as 1 bit black and white with pack bits compression to the stream.
+ ///
+ /// The pixel data.
+ /// The image to write to the stream.
+ /// A span for converting a pixel row to gray.
+ /// A span which will be used to store the output pixels.
+ /// The number of bytes written.
+ public int WriteBiColorPackBits(Image image, Span pixelRowAsGraySpan, Span outputRow)
+ 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 bits.
+ int additionalBytes = (image.Width / 127) + 1;
+ int compressedRowBytes = (image.Width / 8) + additionalBytes;
+ using IManagedByteBuffer compressedRow = this.memoryAllocator.AllocateManagedByteBuffer(compressedRowBytes, AllocationOptions.Clean);
+ Span compressedRowSpan = compressedRow.GetSpan();
+
+ int bytesWritten = 0;
+ for (int y = 0; y < image.Height; y++)
+ {
+ int bitIndex = 0;
+ int byteIndex = 0;
+ Span pixelRow = image.GetPixelRowSpan(y);
+ PixelOperations.Instance.ToL8(this.configuration, pixelRow, pixelRowAsGraySpan);
+ for (int x = 0; x < pixelRow.Length; x++)
+ {
+ int shift = 7 - bitIndex;
+ if (pixelRowAsGraySpan[x].PackedValue == 255)
+ {
+ outputRow[byteIndex] |= (byte)(1 << shift);
+ }
+
+ bitIndex++;
+ if (bitIndex == 8)
+ {
+ byteIndex++;
+ bitIndex = 0;
+ }
+ }
+
+ var size = PackBitsWriter.PackBits(outputRow, compressedRowSpan);
+ this.output.Write(compressedRowSpan);
+ bytesWritten += size;
+
+ outputRow.Clear();
+ }
+
+ 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/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
index db802d7d7..2f71b0bd9 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
@@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using Xunit;
-namespace SixLabors.ImageSharp.Tests.Formats.Tiff
+namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
{
[Trait("Category", "Tiff")]
public class DeflateTiffCompressionTests
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
index 5c9ef2d31..4dc643e52 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
@@ -2,11 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
-using SixLabors.ImageSharp.Formats.Tiff;
+
using SixLabors.ImageSharp.Formats.Tiff.Compression;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
+
using Xunit;
-namespace SixLabors.ImageSharp.Tests.Formats.Tiff
+namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
{
[Trait("Category", "Tiff")]
public class LzwTiffCompressionTests
@@ -19,14 +21,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence
public void Decompress_ReadsData(byte[] data)
{
- using (Stream stream = CreateCompressedStream(data))
- {
- var buffer = new byte[data.Length];
+ using Stream stream = CreateCompressedStream(data);
+ var buffer = new byte[data.Length];
- new LzwTiffCompression(Configuration.Default.MemoryAllocator).Decompress(stream, (int)stream.Length, buffer);
+ new LzwTiffCompression(Configuration.Default.MemoryAllocator).Decompress(stream, (int)stream.Length, buffer);
- Assert.Equal(data, buffer);
- }
+ Assert.Equal(data, buffer);
}
private static Stream CreateCompressedStream(byte[] data)
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
index 24820b906..8366c4de3 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
@@ -5,7 +5,7 @@ using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using Xunit;
-namespace SixLabors.ImageSharp.Tests.Formats.Tiff
+namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
{
[Trait("Category", "Tiff")]
public class NoneTiffCompressionTests
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
index de8e11f77..923b95e37 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
@@ -1,13 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.IO;
-using SixLabors.ImageSharp.Formats.Tiff;
+
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
+
using Xunit;
-namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Formats.Tiff
+namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
{
[Trait("Category", "Tiff")]
public class PackBitsTiffCompressionTests
@@ -20,16 +22,32 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Formats.Tiff
[InlineData(new byte[] { 0xFE, 0x2A }, new byte[] { 0x2A, 0x2A, 0x2A })] // Repeat three bytes
[InlineData(new byte[] { 0x80 }, new byte[] { })] // Read a 'No operation' byte
[InlineData(new byte[] { 0x01, 0x15, 0x32, 0x80, 0xFF, 0xA2 }, new byte[] { 0x15, 0x32, 0xA2, 0xA2 })] // Read two bytes, nop, repeat two bytes
- [InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA },
- new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample
+ [InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA }, new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample
public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult)
{
Stream stream = new MemoryStream(inputData);
- byte[] buffer = new byte[expectedResult.Length];
+ var buffer = new byte[expectedResult.Length];
new PackBitsTiffCompression(new ArrayPoolMemoryAllocator()).Decompress(stream, inputData.Length, buffer);
Assert.Equal(expectedResult, buffer);
}
+
+ [Theory]
+ [InlineData(new byte[] { 0xAA, 0xAA, 0xAA, 0xCD, 0xBB, 0xBB, 0xBB, 0xBB }, new byte[] { 0xFE, 0xAA, 0x02, 0xCD, 0xFD, 0xBB })] // A run of 3, then one byte, followed by a run of 4.
+ [InlineData(new byte[] { 0xAB, 0xCD, 0xEF }, new byte[] { 0x04, 0xAB, 0xCD, 0xEF })] // all bytes are different.
+ [InlineData(new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA }, new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA })] // Apple PackBits sample
+ public void Compress_Works(byte[] inputData, byte[] expectedResult)
+ {
+ // arrange
+ Span input = inputData.AsSpan();
+ var compressed = new byte[expectedResult.Length];
+
+ // act
+ PackBitsWriter.PackBits(input, compressed);
+
+ // assert
+ Assert.Equal(expectedResult, compressed);
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
index e279ea562..37db9a6c7 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
@@ -2,8 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
+
using SixLabors.ImageSharp.Formats.Tiff;
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
+
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
index 5b09324df..294a9c0cf 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
@@ -47,25 +47,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
}
[Theory]
- [WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Tiff.Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.Rgb);
[Theory]
- [WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Tiff.Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_WithDeflateCompression_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.Rgb, TiffEncoderCompression.Deflate);
[Theory]
- [WithFile(TestImages.Tiff.GrayscaleUncompressed, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Tiff.Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
+ public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.Rgb, TiffEncoderCompression.PackBits);
+
+ [Theory]
+ [WithFile(TestImages.Tiff.Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.Gray);
[Theory]
- [WithFile(TestImages.Tiff.GrayscaleUncompressed, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Tiff.Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_WithDeflateCompression_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.Gray, TiffEncoderCompression.Deflate);
+ [Theory]
+ [WithFile(TestImages.Tiff.Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
+ public void TiffEncoder_EncodeGray_WithPackBitsCompression_Works(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel8, TiffEncodingMode.Gray, TiffEncoderCompression.PackBits);
+
// 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)]
@@ -88,6 +98,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffEncoder_EncodeBiColor_WithDeflateCompression_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel1, TiffEncodingMode.BiColor, TiffEncoderCompression.Deflate);
+ [Theory]
+ [WithFile(TestImages.Tiff.Calliphora_BiColor, PixelTypes.Rgba32)]
+ public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_Works(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel1, TiffEncodingMode.BiColor, TiffEncoderCompression.PackBits);
+
[Theory]
[WithFile(TestImages.Tiff.Calliphora_BiColor, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_Works(TestImageProvider provider)
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/SubStreamTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/SubStreamTests.cs
index dbb053b90..14f46d2a7 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Utils/SubStreamTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/SubStreamTests.cs
@@ -3,7 +3,9 @@
using System;
using System.IO;
-using SixLabors.ImageSharp.Formats.Tiff;
+
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
+
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs
index 9023fe3e0..15b495556 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs
@@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
-using SixLabors.ImageSharp.Formats.Tiff;
+
+using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
+
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff