diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md
index 7202cac46..feefdd55a 100644
--- a/src/ImageSharp/Formats/Tiff/README.md
+++ b/src/ImageSharp/Formats/Tiff/README.md
@@ -57,8 +57,8 @@
|---------------------------|:-----:|:-----:|--------------------------|
|WhiteIsZero | | Y | General + 1/4/8-bit optimised implementations |
|BlackIsZero | | Y | General + 1/4/8-bit optimised implementations |
-|Rgb (Chunky) | | Y | General + Rgb888 optimised implementation |
-|Rgb (Planar) | Y | Y | General implementation only |
+|Rgb (Chunky) | Y | Y | General + Rgb888 optimised implementation |
+|Rgb (Planar) | | Y | General implementation only |
|PaletteColor | Y | Y | General implementation only |
|TransparencyMask | | | |
|Separated (TIFF Extension) | | | |
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
index 7cf12afb5..a0e204bd2 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
@@ -158,6 +158,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffEncodingMode.Gray:
imageDataBytes = writer.WriteGray(image, this.padding, this.CompressionType);
break;
+ case TiffEncodingMode.BiColor:
+ imageDataBytes = writer.WriteBiColor(image);
+ break;
default:
imageDataBytes = writer.WriteRgbImageData(image, this.padding, this.CompressionType);
break;
@@ -337,6 +340,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffEncodingMode.ColorPalette:
this.PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor;
break;
+ case TiffEncodingMode.BiColor:
case TiffEncodingMode.Gray:
this.PhotometricInterpretation = TiffPhotometricInterpretation.BlackIsZero;
break;
@@ -369,6 +373,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffPhotometricInterpretation.Rgb:
return new ushort[] { 8, 8, 8 };
case TiffPhotometricInterpretation.BlackIsZero:
+ if (this.Mode == TiffEncodingMode.BiColor)
+ {
+ return new ushort[] { 1 };
+ }
+
return new ushort[] { 8 };
default:
return new ushort[] { 8, 8, 8 };
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs b/src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs
index 195353ec4..374505195 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncodingMode.cs
@@ -27,5 +27,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// The image will be encoded as 8 bit gray.
///
Gray = 3,
+
+ ///
+ /// The image will be written as a white and black image.
+ ///
+ BiColor = 4,
}
}
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
index 2c6e03d9c..d018248ed 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
@@ -12,6 +12,8 @@ using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Tiff
@@ -199,6 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// The image to write to the stream.
/// The quantizer to use.
/// The padding bytes for each row.
+ /// The compression to use.
/// The color map.
/// The number of bytes written.
public int WritePalettedRgb(Image image, IQuantizer quantizer, int padding, TiffEncoderCompression compression, out IExifValue colorMap)
@@ -328,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// Writes the image data as 8 bit gray with deflate compression to the stream.
///
/// The image to write to the stream.
- /// A span of a pixel row.
+ /// A span of a row of pixels.
/// The number of bytes written.
private int WriteGrayDeflateCompressed(Image image, Span rowSpan)
where TPixel : unmanaged, IPixel
@@ -337,8 +340,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
using var memoryStream = new MemoryStream();
// TODO: move zlib compression from png to a common place?
- using var deflateStream =
- new ZlibDeflateStream(this.memoryAllocator, memoryStream, PngCompressionLevel.Level6); // TODO: make compression level configurable
+ using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, PngCompressionLevel.Level6); // TODO: make compression level configurable
for (int y = 0; y < image.Height; y++)
{
@@ -355,6 +357,52 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return bytesWritten;
}
+ public int WriteBiColor(Image image)
+ where TPixel : unmanaged, IPixel
+ {
+ int padding = image.Width % 8 == 0 ? 0 : 1;
+ int bytesPerRow = (image.Width / 8) + padding;
+ using IMemoryOwner rowRgb = this.memoryAllocator.Allocate(image.Width);
+ using IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerRow, AllocationOptions.Clean);
+ Span rowSpan = row.GetSpan();
+ Span rowRgbSpan = rowRgb.GetSpan();
+
+ // Convert image to black and white.
+ using Image imageClone = image.Clone();
+ imageClone.Mutate(img => img.BinaryDither(default(ErrorDither)));
+
+ int bytesWritten = 0;
+ for (int y = 0; y < image.Height; y++)
+ {
+ int bitIndex = 0;
+ int byteIndex = 0;
+ Span pixelRow = imageClone.GetPixelRowSpan(y);
+ PixelOperations.Instance.ToRgb24(this.configuration, pixelRow, rowRgbSpan);
+ for (int x = 0; x < pixelRow.Length; x++)
+ {
+ int shift = 7 - bitIndex;
+ if (rowRgbSpan[x].R == 255)
+ {
+ rowSpan[byteIndex] |= (byte)(1 << shift);
+ }
+
+ bitIndex++;
+ if (bitIndex == 8)
+ {
+ byteIndex++;
+ bitIndex = 0;
+ }
+ }
+
+ this.output.Write(row);
+ bytesWritten += row.Length();
+
+ row.Clear();
+ }
+
+ return bytesWritten;
+ }
+
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel, int padding) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, padding);
///