Browse Source

Merge branch 'main' into bp/webpanimation

pull/1985/head
Brian Popow 4 years ago
committed by GitHub
parent
commit
8b177f8757
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/CONTRIBUTING.md
  2. 33
      .github/ISSUE_TEMPLATE/commercial-bug-report.md
  3. 42
      .github/ISSUE_TEMPLATE/commercial-bug-report.yml
  4. 30
      .github/ISSUE_TEMPLATE/oss-bug-report.md
  5. 40
      .github/ISSUE_TEMPLATE/oss-bug-report.yml
  6. 6
      README.md
  7. 468
      src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
  8. 201
      src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs
  9. 536
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs
  10. 5
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs
  11. 3
      src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
  12. 2
      src/ImageSharp/Formats/Tiff/README.md
  13. 3
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  14. 2
      src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs
  15. 4
      tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs
  16. 13
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  17. 2
      tests/Images/External/README.md

2
.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?**

33
.github/ISSUE_TEMPLATE/commercial-bug-report.md

@ -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
<!-- A description of the bug or feature -->
### Steps to Reproduce
<!-- List of steps, sample code, failing test or link to a project that reproduces the behavior -->
### System Configuration
<!-- Tell us about the environment where you are experiencing the bug -->
- ImageSharp version:
- Other ImageSharp packages and versions:
- Environment (Operating system, version and so on):
- .NET Framework version:
- Additional information:
<!-- Thanks for reporting the issue to ImageSharp! -->

42
.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.

30
.github/ISSUE_TEMPLATE/oss-bug-report.md

@ -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
<!-- A description of the bug or feature -->
### Steps to Reproduce
<!-- List of steps, sample code, failing test or link to a project that reproduces the behavior -->
### System Configuration
<!-- Tell us about the environment where you are experiencing the bug -->
- ImageSharp version:
- Other ImageSharp packages and versions:
- Environment (Operating system, version and so on):
- .NET Framework version:
- Additional information:
<!-- Thanks for reporting the issue to ImageSharp! -->

40
.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.

6
README.md

@ -1,6 +1,6 @@
<h1 align="center">
<img src="https://github.com/SixLabors/Branding/raw/master/icons/imagesharp/sixlabors.imagesharp.svg?sanitize=true" alt="SixLabors.ImageSharp" width="256"/>
<img src="https://github.com/SixLabors/Branding/raw/main/icons/imagesharp/sixlabors.imagesharp.svg?sanitize=true" alt="SixLabors.ImageSharp" width="256"/>
<br/>
SixLabors.ImageSharp
</h1>
@ -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.

468
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
/// <summary>
/// Bitwriter for writing compressed CCITT T4 1D data.
/// </summary>
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<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>()
{
{ 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF }
};
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>()
{
{ 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 }
};
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>()
{
{ 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B }
};
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new Dictionary<uint, uint>()
{
{ 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<uint, uint> WhiteLen8TermCodes = new Dictionary<uint, uint>()
{
{ 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<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>()
{
{ 2, 0x3 }, { 3, 0x2 }
};
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>()
{
{ 1, 0x2 }, { 4, 0x3 }
};
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>()
{
{ 5, 0x3 }, { 6, 0x2 }
};
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>()
{
{ 7, 0x3 }
};
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>()
{
{ 8, 0x5 }, { 9, 0x4 }
};
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>()
{
{ 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 }
};
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>()
{
{ 13, 0x4 }, { 14, 0x7 }
};
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>()
{
{ 15, 0x18 }
};
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>()
{
{ 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 }
};
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>()
{
{ 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 }
};
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new Dictionary<uint, uint>()
{
{ 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<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>()
{
{ 64, 0x1B }, { 128, 0x12 }
};
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>()
{
{ 192, 0x17 }, { 1664, 0x18 }
};
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>()
{
{ 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 }
};
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>()
{
{ 256, 0x37 }
};
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>()
{
{ 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<uint, uint> WhiteLen11MakeupCodes = new Dictionary<uint, uint>()
{
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD }
};
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>()
{
{ 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C },
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F }
};
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new Dictionary<uint, uint>()
{
{ 64, 0xF }
};
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>()
{
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD }
};
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>()
{
{ 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<uint, uint> BlackLen13MakeupCodes = new Dictionary<uint, uint>()
{
{ 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 }
};
/// <summary>
/// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows.
/// </summary>
private readonly bool useModifiedHuffman;
private IMemoryOwner<byte> compressedDataBuffer;
private int bytePosition;
private byte bitPosition;
/// <summary>
/// Initializes a new instance of the <see cref="T4BitCompressor" /> class.
/// </summary>
/// <param name="output">The output.</param>
/// <param name="allocator">The allocator.</param>
/// <param name="width">The width.</param>
/// <param name="output">The output stream to write the compressed data.</param>
/// <param name="allocator">The memory allocator.</param>
/// <param name="width">The width of the image.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="useModifiedHuffman">Indicates if the modified huffman RLE should be used.</param>
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;
/// <inheritdoc/>
public override TiffCompression Method => this.useModifiedHuffman ? TiffCompression.Ccitt1D : TiffCompression.CcittGroup3Fax;
/// <inheritdoc/>
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<byte>(maxNeededBytes);
}
/// <summary>
/// Writes a image compressed with CCITT T4 to the stream.
/// Writes a image compressed with CCITT T4 to the output buffer.
/// </summary>
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
/// <param name="height">The strip height.</param>
public override void CompressStrip(Span<byte> pixelsAsGray, int height)
/// <param name="compressedData">The destination for the compressed data.</param>
protected override void CompressStrip(Span<byte> pixelsAsGray, int height, Span<byte> 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<byte> 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<byte> 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<byte> 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;
}
}
}

201
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
{
/// <summary>
/// Bitwriter for writing compressed CCITT T6 2D data.
/// </summary>
internal sealed class T6BitCompressor : TiffCcittCompressor
{
/// <summary>
/// Vertical codes from -3 to +3.
/// </summary>
private static readonly (uint Length, uint Code)[] VerticalCodes =
{
(7u, 3u),
(6u, 3u),
(3u, 3u),
(1u, 1u),
(3u, 2u),
(6u, 2u),
(7u, 2u)
};
private IMemoryOwner<byte> referenceLineBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="T6BitCompressor"/> class.
/// </summary>
/// <param name="output">The output stream to write the compressed data.</param>
/// <param name="allocator">The memory allocator.</param>
/// <param name="width">The width of the image.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
public T6BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel)
: base(output, allocator, width, bitsPerPixel)
{
}
/// <inheritdoc />
public override TiffCompression Method => TiffCompression.CcittGroup4Fax;
/// <summary>
/// Writes a image compressed with CCITT T6 to the output buffer.
/// </summary>
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
/// <param name="height">The strip height.</param>
/// <param name="compressedData">The destination for the compressed data.</param>
protected override void CompressStrip(Span<byte> pixelsAsGray, int height, Span<byte> compressedData)
{
// Initial reference line is all white.
Span<byte> referenceLine = this.referenceLineBuffer.GetSpan();
referenceLine.Fill(0xff);
for (int y = 0; y < height; y++)
{
Span<byte> 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);
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
this.referenceLineBuffer?.Dispose();
base.Dispose(disposing);
}
/// <summary>
/// Finds the end of a pixel run.
/// </summary>
/// <param name="row">The row of pixels to examine.</param>
/// <param name="startIndex">The index of the first pixel in <paramref name="row"/> to examine.</param>
/// <param name="color">Color of pixels in the run. If not specified, the color at
/// <paramref name="startIndex"/> will be used.</param>
/// <returns>The index of the first pixel at or after <paramref name="startIndex"/>
/// that does not match <paramref name="color"/>, or the length of <paramref name="row"/>,
/// whichever comes first.</returns>
private uint FindRunEnd(Span<byte> 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;
}
/// <inheritdoc />
public override void Initialize(int rowsPerStrip)
{
base.Initialize(rowsPerStrip);
this.referenceLineBuffer = this.Allocator.Allocate<byte>(this.Width);
}
/// <summary>
/// Writes a run to the output buffer.
/// </summary>
/// <param name="runLength">The length of the run.</param>
/// <param name="isWhiteRun">If <c>true</c> the run is white pixels,
/// if <c>false</c> the run is black pixels.</param>
/// <param name="compressedData">The destination to write the run to.</param>
private void WriteRun(uint runLength, bool isWhiteRun, Span<byte> 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);
}
}
}

536
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
{
/// <summary>
/// Common functionality for CCITT T4 and T6 Compression
/// </summary>
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<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>()
{
{ 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF }
};
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>()
{
{ 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 }
};
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>()
{
{ 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B }
};
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new Dictionary<uint, uint>()
{
{ 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<uint, uint> WhiteLen8TermCodes = new Dictionary<uint, uint>()
{
{ 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<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>()
{
{ 2, 0x3 }, { 3, 0x2 }
};
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>()
{
{ 1, 0x2 }, { 4, 0x3 }
};
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>()
{
{ 5, 0x3 }, { 6, 0x2 }
};
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>()
{
{ 7, 0x3 }
};
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>()
{
{ 8, 0x5 }, { 9, 0x4 }
};
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>()
{
{ 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 }
};
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>()
{
{ 13, 0x4 }, { 14, 0x7 }
};
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>()
{
{ 15, 0x18 }
};
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>()
{
{ 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 }
};
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>()
{
{ 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 }
};
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new Dictionary<uint, uint>()
{
{ 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<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>()
{
{ 64, 0x1B }, { 128, 0x12 }
};
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>()
{
{ 192, 0x17 }, { 1664, 0x18 }
};
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>()
{
{ 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 }
};
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>()
{
{ 256, 0x37 }
};
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>()
{
{ 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<uint, uint> WhiteLen11MakeupCodes = new Dictionary<uint, uint>()
{
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD }
};
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>()
{
{ 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C },
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F }
};
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new Dictionary<uint, uint>()
{
{ 64, 0xF }
};
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>()
{
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD }
};
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>()
{
{ 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<uint, uint> BlackLen13MakeupCodes = new Dictionary<uint, uint>()
{
{ 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<byte> compressedDataBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="TiffCcittCompressor" /> class.
/// </summary>
/// <param name="output">The output.</param>
/// <param name="allocator">The allocator.</param>
/// <param name="width">The width.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
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;
}
/// <summary>
/// Gets the best makeup run length for a given run length
/// </summary>
/// <param name="runLength">A run length needing a makeup code</param>
/// <returns>The makeup length for <paramref name="runLength"/>.</returns>
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];
}
/// <summary>
/// Gets the terminating code for a run length.
/// </summary>
/// <param name="runLength">The run length to get the terminating code for.</param>
/// <param name="codeLength">The length of the terminating code.</param>
/// <param name="isWhiteRun">If <c>true</c>, the run is of white pixels.
/// If <c>false</c> the run is of black pixels</param>
/// <returns>The terminating code for a run of length <paramref name="runLength"/></returns>
protected uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun)
{
if (isWhiteRun)
{
return this.GetWhiteTermCode(runLength, out codeLength);
}
return this.GetBlackTermCode(runLength, out codeLength);
}
/// <summary>
/// Gets the makeup code for a run length.
/// </summary>
/// <param name="runLength">The run length to get the makeup code for.</param>
/// <param name="codeLength">The length of the makeup code.</param>
/// <param name="isWhiteRun">If <c>true</c>, the run is of white pixels.
/// If <c>false</c> the run is of black pixels</param>
/// <returns>The makeup code for a run of length <paramref name="runLength"/></returns>
protected uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun)
{
if (isWhiteRun)
{
return this.GetWhiteMakeupCode(runLength, out codeLength);
}
return this.GetBlackMakeupCode(runLength, out codeLength);
}
/// <summary>
/// Pads output to the next byte
/// </summary>
/// <remarks>
/// If the output is not currently on a byte boundary,
/// zero-pad it to the next byte
/// </remarks>
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;
}
}
/// <summary>
/// Writes a code to the output.
/// </summary>
/// <param name="codeLength">The length of the code to write.</param>
/// <param name="code">The code to be written.</param>
/// <param name="compressedData">The destination buffer to write the code to.</param>
protected void WriteCode(uint codeLength, uint code, Span<byte> 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--;
}
}
/// <summary>
/// Writes a image compressed with CCITT T6 to the stream.
/// </summary>
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
/// <param name="height">The strip height.</param>
public override void CompressStrip(Span<byte> 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<byte> 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));
}
/// <summary>
/// Compress a data strip
/// </summary>
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
/// <param name="height">The strip height.</param>
/// <param name="compressedData">The destination for the compressed data.</param>
protected abstract void CompressStrip(Span<byte> pixelsAsGray, int height, Span<byte> compressedData);
/// <inheritdoc/>
protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose();
/// <inheritdoc/>
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<byte>(maxNeededBytes);
}
}
}

5
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");

3
src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs

@ -35,9 +35,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
/// <summary>
/// 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.
/// </summary>
CcittGroup4Fax = 4,

2
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 | |

3
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;

2
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;

4
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:
/// <see>
/// <cref>https://github.com/SixLabors/Imagesharp.Tests.Images/blob/master/tools/jpeg/README.md</cref>
/// <cref>https://github.com/SixLabors/Imagesharp.Tests.Images/blob/main/tools/jpeg/README.md</cref>
/// </see>
/// </summary>
public static void RunDumpJpegCoeffsTool(string sourceFile, string destFile)
@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
/// <summary>
/// Extract libjpeg <see cref="SpectralData"/> 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
/// </summary>
public static SpectralData ExtractSpectralData(string inputFile)
{

13
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<TPixel>(TestImageProvider<TPixel> provider, TiffCompression compression, TiffCompression expectedCompression)
where TPixel : unmanaged, IPixel<TPixel>
@ -405,6 +408,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_BlackIsZero_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_WhiteIsZero_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.CcittGroup4Fax);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_BlackIsZero_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup4Fax);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_WhiteIsZero_Works<TPixel>(TestImageProvider<TPixel> provider)

2
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)

Loading…
Cancel
Save