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); ///