diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 0943ab4eb..ffacf51e4 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -28,7 +28,7 @@ #### **Running tests and Debugging** -* Expected test output is pulled in as a submodule from the [ImageSharp.Tests.Images repository](https://github.com/SixLabors/Imagesharp.Tests.Images/tree/master/ReferenceOutput). To succesfully run tests, make sure that you have updated the submodules! +* Expected test output is pulled in as a submodule from the [ImageSharp.Tests.Images repository](https://github.com/SixLabors/Imagesharp.Tests.Images/tree/main/ReferenceOutput). To succesfully run tests, make sure that you have updated the submodules! * Debugging (running tests in Debug mode) is only supported on .NET Core 2.1+, because of JIT Code Generation bugs like [dotnet/coreclr#16443](https://github.com/dotnet/coreclr/issues/16443) or [dotnet/coreclr#20657](https://github.com/dotnet/coreclr/issues/20657) #### **Do you have questions about consuming the library or the source code?** diff --git a/.github/ISSUE_TEMPLATE/commercial-bug-report.md b/.github/ISSUE_TEMPLATE/commercial-bug-report.md deleted file mode 100644 index 024de8e19..000000000 --- a/.github/ISSUE_TEMPLATE/commercial-bug-report.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: "Commercial License : Bug Report" -about: | - Create a report to help us improve the project. For Commercial License holders only. - Please contact help@sixlabors.com for issues requiring private support. -labels: commercial, needs triage - ---- - - -### Prerequisites - -- [ ] I have written a descriptive issue title -- [ ] I have verified that I am running the latest version of ImageSharp -- [ ] I have verified if the problem exist in both `DEBUG` and `RELEASE` mode -- [ ] I have searched [open](https://github.com/SixLabors/ImageSharp/issues) and [closed](https://github.com/SixLabors/ImageSharp/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported - -### Description - - -### Steps to Reproduce - - -### System Configuration - - -- ImageSharp version: -- Other ImageSharp packages and versions: -- Environment (Operating system, version and so on): -- .NET Framework version: -- Additional information: - - diff --git a/.github/ISSUE_TEMPLATE/commercial-bug-report.yml b/.github/ISSUE_TEMPLATE/commercial-bug-report.yml new file mode 100644 index 000000000..e96677b24 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/commercial-bug-report.yml @@ -0,0 +1,42 @@ +name: "Commercial License : Bug Report" +description: | + Create a report to help us improve the project. For Commercial License holders only. + Please contact help@sixlabors.com for issues requiring private support. +labels: ["commercial", "needs triage"] +body: +- type: input + attributes: + label: ImageSharp version + validations: + required: true +- type: input + attributes: + label: Other ImageSharp packages and versions + validations: + required: true +- type: input + attributes: + label: Environment (Operating system, version and so on) + validations: + required: true +- type: input + attributes: + label: .NET Framework version + validations: + required: true +- type: textarea + attributes: + label: Description + description: A description of the bug + validations: + required: true +- type: textarea + attributes: + label: Steps to Reproduce + description: List of steps, sample code, failing test or link to a project that reproduces the behavior. Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues. + validations: + required: true +- type: textarea + attributes: + label: Images + description: Please upload images that can be used to reproduce issues in the area below. If the file type is not supported the file can be zipped and then uploaded instead. diff --git a/.github/ISSUE_TEMPLATE/oss-bug-report.md b/.github/ISSUE_TEMPLATE/oss-bug-report.md deleted file mode 100644 index 9e9567a99..000000000 --- a/.github/ISSUE_TEMPLATE/oss-bug-report.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: "OSS : Bug Report" -about: Create a report to help us improve the project. OSS Issues are not guaranteed to be triaged. -labels: needs triage - ---- - -### Prerequisites - -- [ ] I have written a descriptive issue title -- [ ] I have verified that I am running the latest version of ImageSharp -- [ ] I have verified if the problem exist in both `DEBUG` and `RELEASE` mode -- [ ] I have searched [open](https://github.com/SixLabors/ImageSharp/issues) and [closed](https://github.com/SixLabors/ImageSharp/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported - -### Description - - -### Steps to Reproduce - - -### System Configuration - - -- ImageSharp version: -- Other ImageSharp packages and versions: -- Environment (Operating system, version and so on): -- .NET Framework version: -- Additional information: - - diff --git a/.github/ISSUE_TEMPLATE/oss-bug-report.yml b/.github/ISSUE_TEMPLATE/oss-bug-report.yml new file mode 100644 index 000000000..00138d511 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/oss-bug-report.yml @@ -0,0 +1,40 @@ +name: "OSS : Bug Report" +description: Create a report to help us improve the project. OSS Issues are not guaranteed to be triaged. +labels: ["needs triage"] +body: +- type: input + attributes: + label: ImageSharp version + validations: + required: true +- type: input + attributes: + label: Other ImageSharp packages and versions + validations: + required: true +- type: input + attributes: + label: Environment (Operating system, version and so on) + validations: + required: true +- type: input + attributes: + label: .NET Framework version + validations: + required: true +- type: textarea + attributes: + label: Description + description: A description of the bug + validations: + required: true +- type: textarea + attributes: + label: Steps to Reproduce + description: List of steps, sample code, failing test or link to a project that reproduces the behavior. Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues. + validations: + required: true +- type: textarea + attributes: + label: Images + description: Please upload images that can be used to reproduce issues in the area below. If the file type is not supported the file can be zipped and then uploaded instead. diff --git a/README.md b/README.md index acaba0c6a..2492041db 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-SixLabors.ImageSharp +SixLabors.ImageSharp
SixLabors.ImageSharp

@@ -39,12 +39,12 @@ Support the efforts of the development of the Six Labors projects. ## Documentation - [Detailed documentation](https://sixlabors.github.io/docs/) for the ImageSharp API is available. This includes additional conceptual documentation to help you get started. -- Our [Samples Repository](https://github.com/SixLabors/Samples/tree/master/ImageSharp) is also available containing buildable code samples demonstrating common activities. +- Our [Samples Repository](https://github.com/SixLabors/Samples/tree/main/ImageSharp) is also available containing buildable code samples demonstrating common activities. ## Questions - Do you have questions? We are happy to help! Please [join our Discussions Forum](https://github.com/SixLabors/ImageSharp/discussions/category_choices), or ask them on [Stack Overflow](https://stackoverflow.com) using the `ImageSharp` tag. Please do not open issues for questions. -- Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/master/.github/CONTRIBUTING.md) before opening issues or pull requests! +- Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/main/.github/CONTRIBUTING.md) before opening issues or pull requests! ## Code of Conduct This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs index 30da537eb..d038e9c8b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; -using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; @@ -13,222 +11,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors /// /// Bitwriter for writing compressed CCITT T4 1D data. /// - internal sealed class T4BitCompressor : TiffBaseCompressor + internal sealed class T4BitCompressor : TiffCcittCompressor { - private const uint WhiteZeroRunTermCode = 0x35; - - private const uint BlackZeroRunTermCode = 0x37; - - private static readonly uint[] MakeupRunLength = - { - 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560 - }; - - private static readonly Dictionary WhiteLen4TermCodes = new Dictionary() - { - { 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF } - }; - - private static readonly Dictionary WhiteLen5TermCodes = new Dictionary() - { - { 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 } - }; - - private static readonly Dictionary WhiteLen6TermCodes = new Dictionary() - { - { 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B } - }; - - private static readonly Dictionary WhiteLen7TermCodes = new Dictionary() - { - { 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 }, - { 27, 0x24 }, { 28, 0x18 } - }; - - private static readonly Dictionary WhiteLen8TermCodes = new Dictionary() - { - { 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 }, - { 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D }, - { 45, 0x4 }, { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 }, - { 54, 0x25 }, { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 }, - { 63, 0x34 } - }; - - private static readonly Dictionary BlackLen2TermCodes = new Dictionary() - { - { 2, 0x3 }, { 3, 0x2 } - }; - - private static readonly Dictionary BlackLen3TermCodes = new Dictionary() - { - { 1, 0x2 }, { 4, 0x3 } - }; - - private static readonly Dictionary BlackLen4TermCodes = new Dictionary() - { - { 5, 0x3 }, { 6, 0x2 } - }; - - private static readonly Dictionary BlackLen5TermCodes = new Dictionary() - { - { 7, 0x3 } - }; - - private static readonly Dictionary BlackLen6TermCodes = new Dictionary() - { - { 8, 0x5 }, { 9, 0x4 } - }; - - private static readonly Dictionary BlackLen7TermCodes = new Dictionary() - { - { 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 } - }; - - private static readonly Dictionary BlackLen8TermCodes = new Dictionary() - { - { 13, 0x4 }, { 14, 0x7 } - }; - - private static readonly Dictionary BlackLen9TermCodes = new Dictionary() - { - { 15, 0x18 } - }; - - private static readonly Dictionary BlackLen10TermCodes = new Dictionary() - { - { 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 } - }; - - private static readonly Dictionary BlackLen11TermCodes = new Dictionary() - { - { 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 } - }; - - private static readonly Dictionary BlackLen12TermCodes = new Dictionary() - { - { 26, 0xCA }, { 27, 0xCB }, { 28, 0xCC }, { 29, 0xCD }, { 30, 0x68 }, { 31, 0x69 }, { 32, 0x6A }, { 33, 0x6B }, { 34, 0xD2 }, - { 35, 0xD3 }, { 36, 0xD4 }, { 37, 0xD5 }, { 38, 0xD6 }, { 39, 0xD7 }, { 40, 0x6C }, { 41, 0x6D }, { 42, 0xDA }, { 43, 0xDB }, - { 44, 0x54 }, { 45, 0x55 }, { 46, 0x56 }, { 47, 0x57 }, { 48, 0x64 }, { 49, 0x65 }, { 50, 0x52 }, { 51, 0x53 }, { 52, 0x24 }, - { 53, 0x37 }, { 54, 0x38 }, { 55, 0x27 }, { 56, 0x28 }, { 57, 0x58 }, { 58, 0x59 }, { 59, 0x2B }, { 60, 0x2C }, { 61, 0x5A }, - { 62, 0x66 }, { 63, 0x67 } - }; - - private static readonly Dictionary WhiteLen5MakeupCodes = new Dictionary() - { - { 64, 0x1B }, { 128, 0x12 } - }; - - private static readonly Dictionary WhiteLen6MakeupCodes = new Dictionary() - { - { 192, 0x17 }, { 1664, 0x18 } - }; - - private static readonly Dictionary WhiteLen8MakeupCodes = new Dictionary() - { - { 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 } - }; - - private static readonly Dictionary WhiteLen7MakeupCodes = new Dictionary() - { - { 256, 0x37 } - }; - - private static readonly Dictionary WhiteLen9MakeupCodes = new Dictionary() - { - { 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 }, - { 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 }, - { 1600, 0x9A }, { 1728, 0x9B } - }; - - private static readonly Dictionary WhiteLen11MakeupCodes = new Dictionary() - { - { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } - }; - - private static readonly Dictionary WhiteLen12MakeupCodes = new Dictionary() - { - { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, - { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } - }; - - private static readonly Dictionary BlackLen10MakeupCodes = new Dictionary() - { - { 64, 0xF } - }; - - private static readonly Dictionary BlackLen11MakeupCodes = new Dictionary() - { - { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } - }; - - private static readonly Dictionary BlackLen12MakeupCodes = new Dictionary() - { - { 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 }, - { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, - { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } - }; - - private static readonly Dictionary BlackLen13MakeupCodes = new Dictionary() - { - { 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 }, - { 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 }, - { 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 } - }; - /// /// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows. /// private readonly bool useModifiedHuffman; - private IMemoryOwner compressedDataBuffer; - - private int bytePosition; - - private byte bitPosition; - /// /// Initializes a new instance of the class. /// - /// The output. - /// The allocator. - /// The width. + /// The output stream to write the compressed data. + /// The memory allocator. + /// The width of the image. /// The bits per pixel. /// Indicates if the modified huffman RLE should be used. public T4BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, bool useModifiedHuffman = false) - : base(output, allocator, width, bitsPerPixel) - { - this.bytePosition = 0; - this.bitPosition = 0; - this.useModifiedHuffman = useModifiedHuffman; - } + : base(output, allocator, width, bitsPerPixel) => this.useModifiedHuffman = useModifiedHuffman; /// public override TiffCompression Method => this.useModifiedHuffman ? TiffCompression.Ccitt1D : TiffCompression.CcittGroup3Fax; - /// - public override void Initialize(int rowsPerStrip) - { - // This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good. - int maxNeededBytes = this.Width * rowsPerStrip; - this.compressedDataBuffer = this.Allocator.Allocate(maxNeededBytes); - } - /// - /// Writes a image compressed with CCITT T4 to the stream. + /// Writes a image compressed with CCITT T4 to the output buffer. /// /// The pixels as 8-bit gray array. /// The strip height. - public override void CompressStrip(Span pixelsAsGray, int height) + /// The destination for the compressed data. + protected override void CompressStrip(Span pixelsAsGray, int height, Span compressedData) { - DebugGuard.IsTrue(pixelsAsGray.Length / height == this.Width, "Values must be equals"); - DebugGuard.IsTrue(pixelsAsGray.Length % height == 0, "Values must be equals"); - - this.compressedDataBuffer.Clear(); - Span compressedData = this.compressedDataBuffer.GetSpan(); - - this.bytePosition = 0; - this.bitPosition = 0; - if (!this.useModifiedHuffman) { // An EOL code is expected at the start of the data. @@ -315,25 +126,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors this.WriteEndOfLine(compressedData); } - - // Write the compressed data to the stream. - int bytesToWrite = this.bitPosition != 0 ? this.bytePosition + 1 : this.bytePosition; - this.Output.Write(compressedData.Slice(0, bytesToWrite)); } - protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose(); - private void WriteEndOfLine(Span compressedData) { if (this.useModifiedHuffman) { - // Check if padding is necessary. - if (this.bitPosition % 8 != 0) - { - // Skip padding bits, move to next byte. - this.bytePosition++; - this.bitPosition = 0; - } + this.PadByte(); } else { @@ -341,254 +140,5 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors this.WriteCode(12, 1, compressedData); } } - - private void WriteCode(uint codeLength, uint code, Span compressedData) - { - while (codeLength > 0) - { - int bitNumber = (int)codeLength; - bool bit = (code & (1 << (bitNumber - 1))) != 0; - if (bit) - { - BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition); - } - else - { - BitWriterUtils.WriteZeroBit(compressedData, this.bytePosition, this.bitPosition); - } - - this.bitPosition++; - if (this.bitPosition == 8) - { - this.bytePosition++; - this.bitPosition = 0; - } - - codeLength--; - } - } - - private uint GetBestFittingMakeupRunLength(uint runLength) - { - for (int i = 0; i < MakeupRunLength.Length - 1; i++) - { - if (MakeupRunLength[i] <= runLength && MakeupRunLength[i + 1] > runLength) - { - return MakeupRunLength[i]; - } - } - - return MakeupRunLength[MakeupRunLength.Length - 1]; - } - - private uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun) - { - if (isWhiteRun) - { - return this.GetWhiteTermCode(runLength, out codeLength); - } - - return this.GetBlackTermCode(runLength, out codeLength); - } - - private uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun) - { - if (isWhiteRun) - { - return this.GetWhiteMakeupCode(runLength, out codeLength); - } - - return this.GetBlackMakeupCode(runLength, out codeLength); - } - - private uint GetWhiteMakeupCode(uint runLength, out uint codeLength) - { - codeLength = 0; - - if (WhiteLen5MakeupCodes.ContainsKey(runLength)) - { - codeLength = 5; - return WhiteLen5MakeupCodes[runLength]; - } - - if (WhiteLen6MakeupCodes.ContainsKey(runLength)) - { - codeLength = 6; - return WhiteLen6MakeupCodes[runLength]; - } - - if (WhiteLen7MakeupCodes.ContainsKey(runLength)) - { - codeLength = 7; - return WhiteLen7MakeupCodes[runLength]; - } - - if (WhiteLen8MakeupCodes.ContainsKey(runLength)) - { - codeLength = 8; - return WhiteLen8MakeupCodes[runLength]; - } - - if (WhiteLen9MakeupCodes.ContainsKey(runLength)) - { - codeLength = 9; - return WhiteLen9MakeupCodes[runLength]; - } - - if (WhiteLen11MakeupCodes.ContainsKey(runLength)) - { - codeLength = 11; - return WhiteLen11MakeupCodes[runLength]; - } - - if (WhiteLen12MakeupCodes.ContainsKey(runLength)) - { - codeLength = 12; - return WhiteLen12MakeupCodes[runLength]; - } - - return 0; - } - - private uint GetBlackMakeupCode(uint runLength, out uint codeLength) - { - codeLength = 0; - - if (BlackLen10MakeupCodes.ContainsKey(runLength)) - { - codeLength = 10; - return BlackLen10MakeupCodes[runLength]; - } - - if (BlackLen11MakeupCodes.ContainsKey(runLength)) - { - codeLength = 11; - return BlackLen11MakeupCodes[runLength]; - } - - if (BlackLen12MakeupCodes.ContainsKey(runLength)) - { - codeLength = 12; - return BlackLen12MakeupCodes[runLength]; - } - - if (BlackLen13MakeupCodes.ContainsKey(runLength)) - { - codeLength = 13; - return BlackLen13MakeupCodes[runLength]; - } - - return 0; - } - - private uint GetWhiteTermCode(uint runLength, out uint codeLength) - { - codeLength = 0; - - if (WhiteLen4TermCodes.ContainsKey(runLength)) - { - codeLength = 4; - return WhiteLen4TermCodes[runLength]; - } - - if (WhiteLen5TermCodes.ContainsKey(runLength)) - { - codeLength = 5; - return WhiteLen5TermCodes[runLength]; - } - - if (WhiteLen6TermCodes.ContainsKey(runLength)) - { - codeLength = 6; - return WhiteLen6TermCodes[runLength]; - } - - if (WhiteLen7TermCodes.ContainsKey(runLength)) - { - codeLength = 7; - return WhiteLen7TermCodes[runLength]; - } - - if (WhiteLen8TermCodes.ContainsKey(runLength)) - { - codeLength = 8; - return WhiteLen8TermCodes[runLength]; - } - - return 0; - } - - private uint GetBlackTermCode(uint runLength, out uint codeLength) - { - codeLength = 0; - - if (BlackLen2TermCodes.ContainsKey(runLength)) - { - codeLength = 2; - return BlackLen2TermCodes[runLength]; - } - - if (BlackLen3TermCodes.ContainsKey(runLength)) - { - codeLength = 3; - return BlackLen3TermCodes[runLength]; - } - - if (BlackLen4TermCodes.ContainsKey(runLength)) - { - codeLength = 4; - return BlackLen4TermCodes[runLength]; - } - - if (BlackLen5TermCodes.ContainsKey(runLength)) - { - codeLength = 5; - return BlackLen5TermCodes[runLength]; - } - - if (BlackLen6TermCodes.ContainsKey(runLength)) - { - codeLength = 6; - return BlackLen6TermCodes[runLength]; - } - - if (BlackLen7TermCodes.ContainsKey(runLength)) - { - codeLength = 7; - return BlackLen7TermCodes[runLength]; - } - - if (BlackLen8TermCodes.ContainsKey(runLength)) - { - codeLength = 8; - return BlackLen8TermCodes[runLength]; - } - - if (BlackLen9TermCodes.ContainsKey(runLength)) - { - codeLength = 9; - return BlackLen9TermCodes[runLength]; - } - - if (BlackLen10TermCodes.ContainsKey(runLength)) - { - codeLength = 10; - return BlackLen10TermCodes[runLength]; - } - - if (BlackLen11TermCodes.ContainsKey(runLength)) - { - codeLength = 11; - return BlackLen11TermCodes[runLength]; - } - - if (BlackLen12TermCodes.ContainsKey(runLength)) - { - codeLength = 12; - return BlackLen12TermCodes[runLength]; - } - - return 0; - } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs new file mode 100644 index 000000000..9e03d2764 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs @@ -0,0 +1,201 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +{ + /// + /// Bitwriter for writing compressed CCITT T6 2D data. + /// + internal sealed class T6BitCompressor : TiffCcittCompressor + { + /// + /// Vertical codes from -3 to +3. + /// + private static readonly (uint Length, uint Code)[] VerticalCodes = + { + (7u, 3u), + (6u, 3u), + (3u, 3u), + (1u, 1u), + (3u, 2u), + (6u, 2u), + (7u, 2u) + }; + + private IMemoryOwner referenceLineBuffer; + + /// + /// Initializes a new instance of the class. + /// + /// The output stream to write the compressed data. + /// The memory allocator. + /// The width of the image. + /// The bits per pixel. + public T6BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) + : base(output, allocator, width, bitsPerPixel) + { + } + + /// + public override TiffCompression Method => TiffCompression.CcittGroup4Fax; + + /// + /// Writes a image compressed with CCITT T6 to the output buffer. + /// + /// The pixels as 8-bit gray array. + /// The strip height. + /// The destination for the compressed data. + protected override void CompressStrip(Span pixelsAsGray, int height, Span compressedData) + { + // Initial reference line is all white. + Span referenceLine = this.referenceLineBuffer.GetSpan(); + referenceLine.Fill(0xff); + + for (int y = 0; y < height; y++) + { + Span row = pixelsAsGray.Slice(y * this.Width, this.Width); + uint a0 = 0; + uint a1 = row[0] == 0 ? 0 : this.FindRunEnd(row, 0); + uint b1 = referenceLine[0] == 0 ? 0 : this.FindRunEnd(referenceLine, 0); + + while (true) + { + uint b2 = this.FindRunEnd(referenceLine, b1); + if (b2 < a1) + { + // Pass mode. + this.WriteCode(4, 1, compressedData); + a0 = b2; + } + else + { + int d = int.MaxValue; + if ((b1 >= a1) && (b1 - a1 <= 3)) + { + d = (int)(b1 - a1); + } + else if ((b1 < a1) && (a1 - b1 <= 3)) + { + d = -(int)(a1 - b1); + } + + if ((d >= -3) && (d <= 3)) + { + // Vertical mode. + (uint length, uint code) = VerticalCodes[d + 3]; + this.WriteCode(length, code, compressedData); + a0 = a1; + } + else + { + // Horizontal mode. + this.WriteCode(3, 1, compressedData); + + uint a2 = this.FindRunEnd(row, a1); + if ((a0 + a1 == 0) || (row[(int)a0] != 0)) + { + this.WriteRun(a1 - a0, true, compressedData); + this.WriteRun(a2 - a1, false, compressedData); + } + else + { + this.WriteRun(a1 - a0, false, compressedData); + this.WriteRun(a2 - a1, true, compressedData); + } + + a0 = a2; + } + } + + if (a0 >= row.Length) + { + break; + } + + byte thisPixel = row[(int)a0]; + a1 = this.FindRunEnd(row, a0, thisPixel); + b1 = this.FindRunEnd(referenceLine, a0, (byte)~thisPixel); + b1 = this.FindRunEnd(referenceLine, b1, thisPixel); + } + + // This row is now the reference line. + row.CopyTo(referenceLine); + } + + this.WriteCode(12, 1, compressedData); + this.WriteCode(12, 1, compressedData); + } + + /// + protected override void Dispose(bool disposing) + { + this.referenceLineBuffer?.Dispose(); + base.Dispose(disposing); + } + + /// + /// Finds the end of a pixel run. + /// + /// The row of pixels to examine. + /// The index of the first pixel in to examine. + /// Color of pixels in the run. If not specified, the color at + /// will be used. + /// The index of the first pixel at or after + /// that does not match , or the length of , + /// whichever comes first. + private uint FindRunEnd(Span row, uint startIndex, byte? color = null) + { + if (startIndex >= row.Length) + { + return (uint)row.Length; + } + + byte colorValue = color.GetValueOrDefault(row[(int)startIndex]); + for (int i = (int)startIndex; i < row.Length; i++) + { + if (row[i] != colorValue) + { + return (uint)i; + } + } + + return (uint)row.Length; + } + + /// + public override void Initialize(int rowsPerStrip) + { + base.Initialize(rowsPerStrip); + this.referenceLineBuffer = this.Allocator.Allocate(this.Width); + } + + /// + /// Writes a run to the output buffer. + /// + /// The length of the run. + /// If true the run is white pixels, + /// if false the run is black pixels. + /// The destination to write the run to. + private void WriteRun(uint runLength, bool isWhiteRun, Span compressedData) + { + uint code; + uint codeLength; + while (runLength > 63) + { + uint makeupLength = this.GetBestFittingMakeupRunLength(runLength); + code = this.GetMakeupCode(makeupLength, out codeLength, isWhiteRun); + this.WriteCode(codeLength, code, compressedData); + runLength -= makeupLength; + } + + code = this.GetTermCode(runLength, out codeLength, isWhiteRun); + this.WriteCode(codeLength, code, compressedData); + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs new file mode 100644 index 000000000..316610621 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs @@ -0,0 +1,536 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +{ + /// + /// Common functionality for CCITT T4 and T6 Compression + /// + internal abstract class TiffCcittCompressor : TiffBaseCompressor + { + protected const uint WhiteZeroRunTermCode = 0x35; + + protected const uint BlackZeroRunTermCode = 0x37; + + private static readonly uint[] MakeupRunLength = + { + 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560 + }; + + private static readonly Dictionary WhiteLen4TermCodes = new Dictionary() + { + { 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF } + }; + + private static readonly Dictionary WhiteLen5TermCodes = new Dictionary() + { + { 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 } + }; + + private static readonly Dictionary WhiteLen6TermCodes = new Dictionary() + { + { 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B } + }; + + private static readonly Dictionary WhiteLen7TermCodes = new Dictionary() + { + { 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 }, + { 27, 0x24 }, { 28, 0x18 } + }; + + private static readonly Dictionary WhiteLen8TermCodes = new Dictionary() + { + { 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 }, + { 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D }, + { 45, 0x4 }, { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 }, + { 54, 0x25 }, { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 }, + { 63, 0x34 } + }; + + private static readonly Dictionary BlackLen2TermCodes = new Dictionary() + { + { 2, 0x3 }, { 3, 0x2 } + }; + + private static readonly Dictionary BlackLen3TermCodes = new Dictionary() + { + { 1, 0x2 }, { 4, 0x3 } + }; + + private static readonly Dictionary BlackLen4TermCodes = new Dictionary() + { + { 5, 0x3 }, { 6, 0x2 } + }; + + private static readonly Dictionary BlackLen5TermCodes = new Dictionary() + { + { 7, 0x3 } + }; + + private static readonly Dictionary BlackLen6TermCodes = new Dictionary() + { + { 8, 0x5 }, { 9, 0x4 } + }; + + private static readonly Dictionary BlackLen7TermCodes = new Dictionary() + { + { 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 } + }; + + private static readonly Dictionary BlackLen8TermCodes = new Dictionary() + { + { 13, 0x4 }, { 14, 0x7 } + }; + + private static readonly Dictionary BlackLen9TermCodes = new Dictionary() + { + { 15, 0x18 } + }; + + private static readonly Dictionary BlackLen10TermCodes = new Dictionary() + { + { 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 } + }; + + private static readonly Dictionary BlackLen11TermCodes = new Dictionary() + { + { 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 } + }; + + private static readonly Dictionary BlackLen12TermCodes = new Dictionary() + { + { 26, 0xCA }, { 27, 0xCB }, { 28, 0xCC }, { 29, 0xCD }, { 30, 0x68 }, { 31, 0x69 }, { 32, 0x6A }, { 33, 0x6B }, { 34, 0xD2 }, + { 35, 0xD3 }, { 36, 0xD4 }, { 37, 0xD5 }, { 38, 0xD6 }, { 39, 0xD7 }, { 40, 0x6C }, { 41, 0x6D }, { 42, 0xDA }, { 43, 0xDB }, + { 44, 0x54 }, { 45, 0x55 }, { 46, 0x56 }, { 47, 0x57 }, { 48, 0x64 }, { 49, 0x65 }, { 50, 0x52 }, { 51, 0x53 }, { 52, 0x24 }, + { 53, 0x37 }, { 54, 0x38 }, { 55, 0x27 }, { 56, 0x28 }, { 57, 0x58 }, { 58, 0x59 }, { 59, 0x2B }, { 60, 0x2C }, { 61, 0x5A }, + { 62, 0x66 }, { 63, 0x67 } + }; + + private static readonly Dictionary WhiteLen5MakeupCodes = new Dictionary() + { + { 64, 0x1B }, { 128, 0x12 } + }; + + private static readonly Dictionary WhiteLen6MakeupCodes = new Dictionary() + { + { 192, 0x17 }, { 1664, 0x18 } + }; + + private static readonly Dictionary WhiteLen8MakeupCodes = new Dictionary() + { + { 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 } + }; + + private static readonly Dictionary WhiteLen7MakeupCodes = new Dictionary() + { + { 256, 0x37 } + }; + + private static readonly Dictionary WhiteLen9MakeupCodes = new Dictionary() + { + { 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 }, + { 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 }, + { 1600, 0x9A }, { 1728, 0x9B } + }; + + private static readonly Dictionary WhiteLen11MakeupCodes = new Dictionary() + { + { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } + }; + + private static readonly Dictionary WhiteLen12MakeupCodes = new Dictionary() + { + { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, + { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } + }; + + private static readonly Dictionary BlackLen10MakeupCodes = new Dictionary() + { + { 64, 0xF } + }; + + private static readonly Dictionary BlackLen11MakeupCodes = new Dictionary() + { + { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } + }; + + private static readonly Dictionary BlackLen12MakeupCodes = new Dictionary() + { + { 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 }, + { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, + { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } + }; + + private static readonly Dictionary BlackLen13MakeupCodes = new Dictionary() + { + { 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 }, + { 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 }, + { 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 } + }; + + private int bytePosition; + + private byte bitPosition; + + private IMemoryOwner compressedDataBuffer; + + /// + /// Initializes a new instance of the class. + /// + /// The output. + /// The allocator. + /// The width. + /// The bits per pixel. + protected TiffCcittCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) + : base(output, allocator, width, bitsPerPixel) + { + DebugGuard.IsTrue(bitsPerPixel == 1, nameof(bitsPerPixel), "CCITT compression requires one bit per pixel"); + this.bytePosition = 0; + this.bitPosition = 0; + } + + private uint GetWhiteMakeupCode(uint runLength, out uint codeLength) + { + codeLength = 0; + + if (WhiteLen5MakeupCodes.ContainsKey(runLength)) + { + codeLength = 5; + return WhiteLen5MakeupCodes[runLength]; + } + + if (WhiteLen6MakeupCodes.ContainsKey(runLength)) + { + codeLength = 6; + return WhiteLen6MakeupCodes[runLength]; + } + + if (WhiteLen7MakeupCodes.ContainsKey(runLength)) + { + codeLength = 7; + return WhiteLen7MakeupCodes[runLength]; + } + + if (WhiteLen8MakeupCodes.ContainsKey(runLength)) + { + codeLength = 8; + return WhiteLen8MakeupCodes[runLength]; + } + + if (WhiteLen9MakeupCodes.ContainsKey(runLength)) + { + codeLength = 9; + return WhiteLen9MakeupCodes[runLength]; + } + + if (WhiteLen11MakeupCodes.ContainsKey(runLength)) + { + codeLength = 11; + return WhiteLen11MakeupCodes[runLength]; + } + + if (WhiteLen12MakeupCodes.ContainsKey(runLength)) + { + codeLength = 12; + return WhiteLen12MakeupCodes[runLength]; + } + + return 0; + } + + private uint GetBlackMakeupCode(uint runLength, out uint codeLength) + { + codeLength = 0; + + if (BlackLen10MakeupCodes.ContainsKey(runLength)) + { + codeLength = 10; + return BlackLen10MakeupCodes[runLength]; + } + + if (BlackLen11MakeupCodes.ContainsKey(runLength)) + { + codeLength = 11; + return BlackLen11MakeupCodes[runLength]; + } + + if (BlackLen12MakeupCodes.ContainsKey(runLength)) + { + codeLength = 12; + return BlackLen12MakeupCodes[runLength]; + } + + if (BlackLen13MakeupCodes.ContainsKey(runLength)) + { + codeLength = 13; + return BlackLen13MakeupCodes[runLength]; + } + + return 0; + } + + private uint GetWhiteTermCode(uint runLength, out uint codeLength) + { + codeLength = 0; + + if (WhiteLen4TermCodes.ContainsKey(runLength)) + { + codeLength = 4; + return WhiteLen4TermCodes[runLength]; + } + + if (WhiteLen5TermCodes.ContainsKey(runLength)) + { + codeLength = 5; + return WhiteLen5TermCodes[runLength]; + } + + if (WhiteLen6TermCodes.ContainsKey(runLength)) + { + codeLength = 6; + return WhiteLen6TermCodes[runLength]; + } + + if (WhiteLen7TermCodes.ContainsKey(runLength)) + { + codeLength = 7; + return WhiteLen7TermCodes[runLength]; + } + + if (WhiteLen8TermCodes.ContainsKey(runLength)) + { + codeLength = 8; + return WhiteLen8TermCodes[runLength]; + } + + return 0; + } + + private uint GetBlackTermCode(uint runLength, out uint codeLength) + { + codeLength = 0; + + if (BlackLen2TermCodes.ContainsKey(runLength)) + { + codeLength = 2; + return BlackLen2TermCodes[runLength]; + } + + if (BlackLen3TermCodes.ContainsKey(runLength)) + { + codeLength = 3; + return BlackLen3TermCodes[runLength]; + } + + if (BlackLen4TermCodes.ContainsKey(runLength)) + { + codeLength = 4; + return BlackLen4TermCodes[runLength]; + } + + if (BlackLen5TermCodes.ContainsKey(runLength)) + { + codeLength = 5; + return BlackLen5TermCodes[runLength]; + } + + if (BlackLen6TermCodes.ContainsKey(runLength)) + { + codeLength = 6; + return BlackLen6TermCodes[runLength]; + } + + if (BlackLen7TermCodes.ContainsKey(runLength)) + { + codeLength = 7; + return BlackLen7TermCodes[runLength]; + } + + if (BlackLen8TermCodes.ContainsKey(runLength)) + { + codeLength = 8; + return BlackLen8TermCodes[runLength]; + } + + if (BlackLen9TermCodes.ContainsKey(runLength)) + { + codeLength = 9; + return BlackLen9TermCodes[runLength]; + } + + if (BlackLen10TermCodes.ContainsKey(runLength)) + { + codeLength = 10; + return BlackLen10TermCodes[runLength]; + } + + if (BlackLen11TermCodes.ContainsKey(runLength)) + { + codeLength = 11; + return BlackLen11TermCodes[runLength]; + } + + if (BlackLen12TermCodes.ContainsKey(runLength)) + { + codeLength = 12; + return BlackLen12TermCodes[runLength]; + } + + return 0; + } + + /// + /// Gets the best makeup run length for a given run length + /// + /// A run length needing a makeup code + /// The makeup length for . + protected uint GetBestFittingMakeupRunLength(uint runLength) + { + DebugGuard.MustBeGreaterThanOrEqualTo(runLength, MakeupRunLength[0], nameof(runLength)); + + for (int i = 0; i < MakeupRunLength.Length - 1; i++) + { + if (MakeupRunLength[i] <= runLength && MakeupRunLength[i + 1] > runLength) + { + return MakeupRunLength[i]; + } + } + + return MakeupRunLength[MakeupRunLength.Length - 1]; + } + + /// + /// Gets the terminating code for a run length. + /// + /// The run length to get the terminating code for. + /// The length of the terminating code. + /// If true, the run is of white pixels. + /// If false the run is of black pixels + /// The terminating code for a run of length + protected uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun) + { + if (isWhiteRun) + { + return this.GetWhiteTermCode(runLength, out codeLength); + } + + return this.GetBlackTermCode(runLength, out codeLength); + } + + /// + /// Gets the makeup code for a run length. + /// + /// The run length to get the makeup code for. + /// The length of the makeup code. + /// If true, the run is of white pixels. + /// If false the run is of black pixels + /// The makeup code for a run of length + protected uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun) + { + if (isWhiteRun) + { + return this.GetWhiteMakeupCode(runLength, out codeLength); + } + + return this.GetBlackMakeupCode(runLength, out codeLength); + } + + /// + /// Pads output to the next byte + /// + /// + /// If the output is not currently on a byte boundary, + /// zero-pad it to the next byte + /// + protected void PadByte() + { + // Check if padding is necessary. + if (this.bitPosition % 8 != 0) + { + // Skip padding bits, move to next byte. + this.bytePosition++; + this.bitPosition = 0; + } + } + + /// + /// Writes a code to the output. + /// + /// The length of the code to write. + /// The code to be written. + /// The destination buffer to write the code to. + protected void WriteCode(uint codeLength, uint code, Span compressedData) + { + while (codeLength > 0) + { + int bitNumber = (int)codeLength; + bool bit = (code & (1 << (bitNumber - 1))) != 0; + if (bit) + { + BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition); + } + else + { + BitWriterUtils.WriteZeroBit(compressedData, this.bytePosition, this.bitPosition); + } + + this.bitPosition++; + if (this.bitPosition == 8) + { + this.bytePosition++; + this.bitPosition = 0; + } + + codeLength--; + } + } + + /// + /// Writes a image compressed with CCITT T6 to the stream. + /// + /// The pixels as 8-bit gray array. + /// The strip height. + public override void CompressStrip(Span pixelsAsGray, int height) + { + DebugGuard.IsTrue(pixelsAsGray.Length / height == this.Width, "Values must be equals"); + DebugGuard.IsTrue(pixelsAsGray.Length % height == 0, "Values must be equals"); + + this.compressedDataBuffer.Clear(); + Span compressedData = this.compressedDataBuffer.GetSpan(); + + this.bytePosition = 0; + this.bitPosition = 0; + + this.CompressStrip(pixelsAsGray, height, compressedData); + + // Write the compressed data to the stream. + int bytesToWrite = this.bitPosition != 0 ? this.bytePosition + 1 : this.bytePosition; + this.Output.Write(compressedData.Slice(0, bytesToWrite)); + } + + /// + /// Compress a data strip + /// + /// The pixels as 8-bit gray array. + /// The strip height. + /// The destination for the compressed data. + protected abstract void CompressStrip(Span pixelsAsGray, int height, Span compressedData); + + /// + protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose(); + + /// + public override void Initialize(int rowsPerStrip) + { + // This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good. + int maxNeededBytes = this.Width * rowsPerStrip; + this.compressedDataBuffer = this.Allocator.Allocate(maxNeededBytes); + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs index db2b935b7..904470e81 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs @@ -55,6 +55,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); return new T4BitCompressor(output, allocator, width, bitsPerPixel, false); + case TiffCompression.CcittGroup4Fax: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T6BitCompressor(output, allocator, width, bitsPerPixel); + case TiffCompression.Ccitt1D: DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs index 031494fc5..0baf4c89c 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs @@ -35,9 +35,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants /// /// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification). - /// - /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, - /// if this is choosen. /// CcittGroup4Fax = 4, diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index ff4aa52b0..488701e31 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -45,7 +45,7 @@ |Ccitt1D | Y | Y | | |PackBits | Y | Y | | |CcittGroup3Fax | Y | Y | | -|CcittGroup4Fax | | Y | | +|CcittGroup4Fax | Y | Y | | |Lzw | Y | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case | |Old Jpeg | | | We should not even try to support this | |Jpeg (Technote 2) | Y | Y | | diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index e54d029ab..04f3682b0 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -395,6 +395,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff case TiffCompression.CcittGroup3Fax: return (ushort)TiffCompression.CcittGroup3Fax; + case TiffCompression.CcittGroup4Fax: + return (ushort)TiffCompression.CcittGroup4Fax; + case TiffCompression.Ccitt1D: return (ushort)TiffCompression.Ccitt1D; diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs index 5fec09ef1..f21a465cd 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers { int width = this.Image.Width; - if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D) + if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D || compressor.Method == TiffCompression.CcittGroup4Fax) { // Special case for T4BitCompressor. int stripPixels = width * height; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs index b74e4445e..1d54245d3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// Executes 'dump-jpeg-coeffs.exe' for the given jpeg image file, saving the libjpeg spectral data into 'destFile'. Windows only! /// See: /// - /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/master/tools/jpeg/README.md + /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/main/tools/jpeg/README.md /// /// public static void RunDumpJpegCoeffsTool(string sourceFile, string destFile) @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Extract libjpeg from the given jpg file with 'dump-jpeg-coeffs.exe'. Windows only! /// See: - /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/master/tools/jpeg/README.md + /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/main/tools/jpeg/README.md /// public static SpectralData ExtractSpectralData(string inputFile) { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index aded52cd9..93ca611c9 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -109,6 +109,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup3Fax)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup4Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup4Fax)] [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)] @@ -228,8 +229,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup4Fax, TiffCompression.CcittGroup4Fax)] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] + [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffCompression.CcittGroup4Fax, TiffCompression.CcittGroup4Fax)] [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] public void TiffEncoder_EncodesWithCorrectBiColorModeCompression(TestImageProvider provider, TiffCompression compression, TiffCompression expectedCompression) where TPixel : unmanaged, IPixel @@ -405,6 +408,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_BlackIsZero_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax); + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.CcittGroup4Fax); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup4Fax); + [Theory] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_WhiteIsZero_Works(TestImageProvider provider) diff --git a/tests/Images/External/README.md b/tests/Images/External/README.md index 936b15e6a..9e9fcf5e1 100644 --- a/tests/Images/External/README.md +++ b/tests/Images/External/README.md @@ -5,4 +5,4 @@ Contains images to validate against in ImageSharp tests. In most cases the file Various utilities to help dealing with images. - `optipng.exe`: [lossless PNG compressor](http://optipng.sourceforge.net/), to keep the `ReferenceImages` folder as small as possible - `optimize-all.cmd`: Runs lossless optimizer for reference PNG-s. Currently it has to be manually edited to add new test class directories. -- [`dump-jpeg-coeffs.exe`](https://github.com/SixLabors/Imagesharp/blob/master/tests/Images/External/tools/jpeg/README.md) +- [`dump-jpeg-coeffs.exe`](https://github.com/SixLabors/Imagesharp/blob/main/tests/Images/External/tools/jpeg/README.md)