Browse Source

Add support for encoding modified huffman RLE

pull/1570/head
Brian Popow 6 years ago
parent
commit
0d5f255e4e
  1. 1
      src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
  2. 42
      src/ImageSharp/Formats/Tiff/Compression/T4BitWriter.cs
  3. 8
      src/ImageSharp/Formats/Tiff/README.md
  4. 5
      src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs
  5. 4
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  6. 5
      src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs
  7. 11
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  8. 17
      src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
  9. 10
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

1
src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs

@ -6,6 +6,7 @@ using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression

42
src/ImageSharp/Formats/Tiff/Compression/T4BitWriter.cs

@ -5,6 +5,7 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -183,17 +184,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
private byte bitPosition;
/// <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 bool useModifiedHuffman;
/// <summary>
/// Initializes a new instance of the <see cref="T4BitWriter" /> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="configuration">The configuration.</param>
public T4BitWriter(MemoryAllocator memoryAllocator, Configuration configuration)
/// <param name="useModifiedHuffman">Indicates if the modified huffman RLE should be used.</param>
public T4BitWriter(MemoryAllocator memoryAllocator, Configuration configuration, bool useModifiedHuffman = false)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.bytePosition = 0;
this.bitPosition = 0;
this.useModifiedHuffman = useModifiedHuffman;
}
/// <summary>
@ -215,9 +223,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
this.bytePosition = 0;
this.bitPosition = 0;
// An EOL code is expected at the start of the data.
this.WriteCode(12, 1, compressedData);
if (!this.useModifiedHuffman)
{
// An EOL code is expected at the start of the data.
this.WriteCode(12, 1, compressedData);
}
uint pixelsWritten = 0;
for (int y = 0; y < image.Height; y++)
{
bool isWhiteRun = true;
@ -268,6 +280,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
code = this.GetTermCode(runLength, out codeLength, isWhiteRun);
this.WriteCode(codeLength, code, compressedData);
x += (int)runLength;
pixelsWritten += runLength;
}
else
{
@ -275,6 +288,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
code = this.GetMakeupCode(runLength, out codeLength, isWhiteRun);
this.WriteCode(codeLength, code, compressedData);
x += (int)runLength;
pixelsWritten += runLength;
// If we are at the end of the line with a makeup code, we need to write a final term code with a length of zero.
if (x == image.Width)
@ -296,8 +310,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
isWhiteRun = !isWhiteRun;
}
// Write EOL.
this.WriteCode(12, 1, compressedData);
this.WriteEndOfLine(compressedData);
}
// Write the compressed data to the stream.
@ -306,6 +319,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
return this.bytePosition;
}
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;
}
}
else
{
// Write EOL.
this.WriteCode(12, 1, compressedData);
}
}
private void WriteCode(uint codeLength, uint code, Span<byte> compressedData)
{
while (codeLength > 0)

8
src/ImageSharp/Formats/Tiff/README.md

@ -41,9 +41,9 @@
| |Encoder|Decoder|Comments |
|---------------------------|:-----:|:-----:|--------------------------|
|None | Y | Y | |
|Ccitt1D | | Y | |
|Ccitt1D | Y | Y | |
|PackBits | | Y | |
|CcittGroup3Fax | | Y | |
|CcittGroup3Fax | Y | Y | |
|CcittGroup4Fax | | | |
|Lzw | | 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 |
@ -55,8 +55,8 @@
| |Encoder|Decoder|Comments |
|---------------------------|:-----:|:-----:|--------------------------|
|WhiteIsZero | | Y | General + 1/4/8-bit optimised implementations |
|BlackIsZero | | Y | General + 1/4/8-bit optimised implementations |
|WhiteIsZero | Y | Y | General + 1/4/8-bit optimised implementations |
|BlackIsZero | Y | Y | General + 1/4/8-bit optimised implementations |
|Rgb (Chunky) | Y | Y | General + Rgb888 optimised implementation |
|Rgb (Planar) | | Y | General implementation only |
|PaletteColor | Y | Y | General implementation only |

5
src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs

@ -8,6 +8,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
public enum TiffBitsPerPixel
{
/// <summary>
/// 1 bits per pixel, bi-color image. Each pixel consists of 1 bit.
/// </summary>
Pixel1 = 1,
/// <summary>
/// 8 bits per pixel, grayscale image. Each pixel consists of 1 byte.
/// </summary>

4
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -159,6 +159,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
this.tiffMetaData.BitsPerPixel = TiffBitsPerPixel.Pixel8;
}
else if (bitsPerPixel == 1)
{
this.tiffMetaData.BitsPerPixel = TiffBitsPerPixel.Pixel1;
}
}
/// <inheritdoc/>

5
src/ImageSharp/Formats/Tiff/TiffEncoderCompression.cs

@ -22,5 +22,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// Use CCITT T4 1D compression. Note: This is only valid for bi-level images.
/// </summary>
CcittGroup3Fax,
/// <summary>
/// Use the modified Huffman RLE. Note: This is only valid for bi-level images.
/// </summary>
ModifiedHuffman,
}
}

11
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -98,6 +98,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
this.Mode = TiffEncodingMode.Gray;
}
else if (this.bitsPerPixel == TiffBitsPerPixel.Pixel1)
{
this.Mode = TiffEncodingMode.BiColor;
}
}
this.SetPhotometricInterpretation();
@ -341,7 +345,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor;
break;
case TiffEncodingMode.BiColor:
if (this.CompressionType == TiffEncoderCompression.CcittGroup3Fax)
if (this.CompressionType == TiffEncoderCompression.CcittGroup3Fax || this.CompressionType == TiffEncoderCompression.ModifiedHuffman)
{
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.PhotometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero;
@ -431,6 +435,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return (ushort)TiffCompression.CcittGroup3Fax;
}
if (this.CompressionType == TiffEncoderCompression.ModifiedHuffman && this.Mode == TiffEncodingMode.BiColor)
{
return (ushort)TiffCompression.Ccitt1D;
}
return (ushort)TiffCompression.None;
}
}

17
src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs

@ -376,18 +376,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Span<L8> pixelRowAsGraySpan = pixelRowAsGray.GetSpan();
// Convert image to black and white.
using Image<TPixel> imageClone = image.Clone();
imageClone.Mutate(img => img.BinaryDither(default(ErrorDither)));
// TODO: Should we allow to skip this by the user, if its known to be black and white already?
using Image<TPixel> imageBlackWhite = image.Clone();
imageBlackWhite.Mutate(img => img.BinaryDither(default(ErrorDither)));
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteBiColorDeflate(image, pixelRowAsGraySpan, outputRow);
return this.WriteBiColorDeflate(imageBlackWhite, pixelRowAsGraySpan, outputRow);
}
if (compression == TiffEncoderCompression.CcittGroup3Fax)
{
var bitWriter = new T4BitWriter(this.memoryAllocator, this.configuration);
return bitWriter.CompressImage(image, pixelRowAsGraySpan, this.output);
return bitWriter.CompressImage(imageBlackWhite, pixelRowAsGraySpan, this.output);
}
if (compression == TiffEncoderCompression.ModifiedHuffman)
{
var bitWriter = new T4BitWriter(this.memoryAllocator, this.configuration, useModifiedHuffman: true);
return bitWriter.CompressImage(imageBlackWhite, pixelRowAsGraySpan, this.output);
}
int bytesWritten = 0;
@ -395,7 +402,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
int bitIndex = 0;
int byteIndex = 0;
Span<TPixel> pixelRow = imageClone.GetPixelRowSpan(y);
Span<TPixel> pixelRow = imageBlackWhite.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(this.configuration, pixelRow, pixelRowAsGraySpan);
for (int x = 0; x < pixelRow.Length; x++)
{

10
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

@ -21,6 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public static readonly TheoryData<string, TiffBitsPerPixel> TiffBitsPerPixelFiles =
new TheoryData<string, TiffBitsPerPixel>
{
{ TestImages.Tiff.Calliphora_BiColor, TiffBitsPerPixel.Pixel1 },
{ TestImages.Tiff.GrayscaleUncompressed, TiffBitsPerPixel.Pixel8 },
{ TestImages.Tiff.RgbUncompressed, TiffBitsPerPixel.Pixel24 },
};
@ -85,12 +86,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(TestImages.Tiff.Calliphora_BiColor, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithDeflateCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.BiColor, TiffEncoderCompression.Deflate);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel1, TiffEncodingMode.BiColor, TiffEncoderCompression.Deflate);
[Theory]
[WithFile(TestImages.Tiff.Calliphora_BiColor, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.BiColor, TiffEncoderCompression.CcittGroup3Fax);
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel1, TiffEncodingMode.BiColor, TiffEncoderCompression.CcittGroup3Fax);
[Theory]
[WithFile(TestImages.Tiff.Calliphora_BiColor, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel1, TiffEncodingMode.BiColor, TiffEncoderCompression.ModifiedHuffman);
private static void TestTiffEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,

Loading…
Cancel
Save