Browse Source

Add option to use PackBits with paletted tiff's

pull/1457/head
Brian Popow 5 years ago
parent
commit
8562403ec7
  1. 10
      src/ImageSharp/Formats/Tiff/Compression/PackBitsWriter.cs
  2. 5
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  3. 46
      src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
  4. 6
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

10
src/ImageSharp/Formats/Tiff/Compression/PackBitsWriter.cs

@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// </summary>
internal static class PackBitsWriter
{
public static int PackBits(Span<byte> rowSpan, Span<byte> compressedRowSpan)
public static int PackBits(ReadOnlySpan<byte> rowSpan, Span<byte> compressedRowSpan)
{
int maxRunLength = 127;
int posInRowSpan = 0;
@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
return bytesWritten;
}
private static void WriteLiteralRun(Span<byte> rowSpan, int end, int literalRunLength, Span<byte> compressedRowSpan, int compressedRowPos)
private static void WriteLiteralRun(ReadOnlySpan<byte> rowSpan, int end, int literalRunLength, Span<byte> compressedRowSpan, int compressedRowPos)
{
DebugGuard.MustBeLessThanOrEqualTo(literalRunLength, 127, nameof(literalRunLength));
@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
rowSpan.Slice(literalRunStart, literalRunLength).CopyTo(compressedRowSpan.Slice(compressedRowPos + 1));
}
private static void WriteRun(Span<byte> rowSpan, int start, int runLength, Span<byte> compressedRowSpan, int compressedRowPos)
private static void WriteRun(ReadOnlySpan<byte> rowSpan, int start, int runLength, Span<byte> compressedRowSpan, int compressedRowPos)
{
DebugGuard.MustBeLessThanOrEqualTo(runLength, 127, nameof(runLength));
@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
compressedRowSpan[compressedRowPos + 1] = rowSpan[start];
}
private static bool IsReplicateRun(Span<byte> rowSpan, int startPos)
private static bool IsReplicateRun(ReadOnlySpan<byte> rowSpan, int startPos)
{
// We consider run which has at least 3 same consecutive bytes a candidate for a run.
var startByte = rowSpan[startPos];
@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
return false;
}
private static int FindRunLength(Span<byte> rowSpan, int startPos, int maxRunLength)
private static int FindRunLength(ReadOnlySpan<byte> rowSpan, int startPos, int maxRunLength)
{
var startByte = rowSpan[startPos];
int count = 1;

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

@ -436,6 +436,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return (ushort)TiffCompression.Deflate;
}
if (this.CompressionType == TiffEncoderCompression.PackBits && this.Mode == TiffEncodingMode.ColorPalette)
{
return (ushort)TiffCompression.PackBits;
}
if (this.CompressionType == TiffEncoderCompression.Deflate && this.Mode == TiffEncodingMode.BiColor)
{
return (ushort)TiffCompression.Deflate;

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

@ -289,6 +289,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
return this.WriteDeflateCompressedPalettedRgb(image, quantized, padding);
}
if (compression == TiffEncoderCompression.PackBits)
{
return this.WritePackBitsCompressedPalettedRgb(image, quantized, padding);
}
// No compression.
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
@ -341,6 +346,47 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
return bytesWritten;
}
/// <summary>
/// Writes the image data as indices into a color map compressed with deflate compression to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="quantized">The quantized frame.</param>
/// <param name="padding">The padding bytes for each row.</param>
/// <returns>The number of bytes written.</returns>
public int WritePackBitsCompressedPalettedRgb<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, int padding)
where TPixel : unmanaged, IPixel<TPixel>
{
// Worst case is that the actual compressed data is larger then the input data. In this case we need 1 additional byte per 127 bytes.
int additionalBytes = (image.Width * 3 / 127) + 1;
using IManagedByteBuffer compressedRow = this.memoryAllocator.AllocateManagedByteBuffer((image.Width * 3) + additionalBytes, AllocationOptions.Clean);
using IManagedByteBuffer pixelRowWithPadding = this.memoryAllocator.AllocateManagedByteBuffer((image.Width * 3) + padding, AllocationOptions.Clean);
Span<byte> compressedRowSpan = compressedRow.GetSpan();
Span<byte> pixelRowWithPaddingSpan = pixelRowWithPadding.GetSpan();
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
ReadOnlySpan<byte> pixelSpan = quantized.GetPixelRowSpan(y);
int size = 0;
if (padding != 0)
{
pixelSpan.CopyTo(pixelRowWithPaddingSpan);
size = PackBitsWriter.PackBits(pixelRowWithPaddingSpan, compressedRowSpan);
}
else
{
size = PackBitsWriter.PackBits(pixelSpan, compressedRowSpan);
}
this.output.Write(compressedRowSpan.Slice(0, size));
bytesWritten += size;
}
return bytesWritten;
}
/// <summary>
/// Writes the image data as 8 bit gray to the stream.
/// </summary>

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

@ -88,6 +88,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Deflate);
// TODO: this test fails, but the output looks correct. I thinks its due to the fact that a quantizer is used to create the palette.
[Theory]
[WithFile(TestImages.Tiff.Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel24, TiffEncodingMode.ColorPalette, TiffEncoderCompression.PackBits);
[Theory]
[WithFile(TestImages.Tiff.Calliphora_BiColor, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_Works<TPixel>(TestImageProvider<TPixel> provider)

Loading…
Cancel
Save