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