Browse Source

Allow horizontal prediction with palette and deflate

pull/1570/head
Brian Popow 5 years ago
parent
commit
c119adb18e
  1. 4
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  2. 28
      src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
  3. 17
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

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

@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
switch (this.Mode) switch (this.Mode)
{ {
case TiffEncodingMode.ColorPalette: case TiffEncodingMode.ColorPalette:
imageDataBytes = writer.WritePalettedRgb(image, this.quantizer, this.padding, this.CompressionType, out colorMap); imageDataBytes = writer.WritePalettedRgb(image, this.quantizer, this.padding, this.CompressionType, this.useHorizontalPredictor, out colorMap);
break; break;
case TiffEncodingMode.Gray: case TiffEncodingMode.Gray:
imageDataBytes = writer.WriteGray(image, this.padding, this.CompressionType, this.useHorizontalPredictor); imageDataBytes = writer.WriteGray(image, this.padding, this.CompressionType, this.useHorizontalPredictor);
@ -346,7 +346,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
if (this.useHorizontalPredictor) if (this.useHorizontalPredictor)
{ {
if (this.Mode == TiffEncodingMode.Rgb || this.Mode == TiffEncodingMode.Gray) if (this.Mode == TiffEncodingMode.Rgb || this.Mode == TiffEncodingMode.Gray || this.Mode == TiffEncodingMode.ColorPalette)
{ {
var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal }; var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal };

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

@ -217,7 +217,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <typeparam name="TPixel">The pixel data.</typeparam> /// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param> /// <param name="image">The image to write to the stream.</param>
/// <param name="rowSpan">A Span for a pixel row.</param> /// <param name="rowSpan">A Span for a pixel row.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used. Should only be used with deflate compression.</param> /// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used.</param>
/// <returns>The number of bytes written.</returns> /// <returns>The number of bytes written.</returns>
private int WriteLzwCompressedRgb<TPixel>(Image<TPixel> image, Span<byte> rowSpan, bool useHorizontalPredictor) private int WriteLzwCompressedRgb<TPixel>(Image<TPixel> image, Span<byte> rowSpan, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -286,9 +286,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <param name="quantizer">The quantizer to use.</param> /// <param name="quantizer">The quantizer to use.</param>
/// <param name="padding">The padding bytes for each row.</param> /// <param name="padding">The padding bytes for each row.</param>
/// <param name="compression">The compression to use.</param> /// <param name="compression">The compression to use.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used. Should only be used in combination with deflate or LZW compression.</param>
/// <param name="colorMap">The color map.</param> /// <param name="colorMap">The color map.</param>
/// <returns>The number of bytes written.</returns> /// <returns>The number of bytes written.</returns>
public int WritePalettedRgb<TPixel>(Image<TPixel> image, IQuantizer quantizer, int padding, TiffEncoderCompression compression, out IExifValue colorMap) public int WritePalettedRgb<TPixel>(Image<TPixel> image, IQuantizer quantizer, int padding, TiffEncoderCompression compression, bool useHorizontalPredictor, out IExifValue colorMap)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int colorsPerChannel = 256; int colorsPerChannel = 256;
@ -340,7 +341,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
if (compression == TiffEncoderCompression.Deflate) if (compression == TiffEncoderCompression.Deflate)
{ {
return this.WriteDeflateCompressedPalettedRgb(image, quantized, padding); return this.WriteDeflateCompressedPalettedRgb(image, quantized, padding, useHorizontalPredictor);
} }
if (compression == TiffEncoderCompression.PackBits) if (compression == TiffEncoderCompression.PackBits)
@ -373,18 +374,31 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <param name="image">The image to write to the stream.</param> /// <param name="image">The image to write to the stream.</param>
/// <param name="quantized">The quantized frame.</param> /// <param name="quantized">The quantized frame.</param>
/// <param name="padding">The padding bytes for each row.</param> /// <param name="padding">The padding bytes for each row.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used.</param>
/// <returns>The number of bytes written.</returns> /// <returns>The number of bytes written.</returns>
public int WriteDeflateCompressedPalettedRgb<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, int padding) public int WriteDeflateCompressedPalettedRgb<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, int padding, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using IManagedByteBuffer tmpBuffer = this.memoryAllocator.AllocateManagedByteBuffer(image.Width);
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, PngCompressionLevel.Level6); // TODO: make compression level configurable using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, PngCompressionLevel.Level6); // TODO: make compression level configurable
int bytesWritten = 0; int bytesWritten = 0;
for (int y = 0; y < image.Height; y++) for (int y = 0; y < image.Height; y++)
{ {
ReadOnlySpan<byte> pixelSpan = quantized.GetPixelRowSpan(y); ReadOnlySpan<byte> pixelRow = quantized.GetPixelRowSpan(y);
deflateStream.Write(pixelSpan); if (useHorizontalPredictor)
{
// We need a writable Span here.
Span<byte> pixelRowCopy = tmpBuffer.GetSpan();
pixelRow.CopyTo(pixelRowCopy);
HorizontalPredictor.ApplyHorizontalPrediction8Bit(pixelRowCopy);
deflateStream.Write(pixelRowCopy);
}
else
{
deflateStream.Write(pixelRow);
}
for (int i = 0; i < padding; i++) for (int i = 0; i < padding; i++)
{ {
@ -423,7 +437,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
{ {
ReadOnlySpan<byte> pixelSpan = quantized.GetPixelRowSpan(y); ReadOnlySpan<byte> pixelSpan = quantized.GetPixelRowSpan(y);
int size = 0; int size;
if (padding != 0) if (padding != 0)
{ {
pixelSpan.CopyTo(pixelRowWithPaddingSpan); pixelSpan.CopyTo(pixelRowWithPaddingSpan);

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

@ -143,6 +143,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
TiffTestUtils.CompareWithReferenceDecoder(encodedImagePath, encodedImage); TiffTestUtils.CompareWithReferenceDecoder(encodedImagePath, encodedImage);
} }
[Theory]
[WithFile(TestImages.Tiff.Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithDeflateCompressionAndPredictor_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
using var memStream = new MemoryStream();
var encoder = new TiffEncoder { Mode = TiffEncodingMode.ColorPalette, Compression = TiffEncoderCompression.Deflate, UseHorizontalPredictor = true};
image.Save(memStream, encoder);
memStream.Position = 0;
using var encodedImage = (Image<TPixel>)Image.Load(memStream);
var encodedImagePath = provider.Utility.SaveTestOutputFile(encodedImage, "tiff", encoder);
TiffTestUtils.CompareWithReferenceDecoder(encodedImagePath, encodedImage);
}
[Theory] [Theory]
[WithFile(TestImages.Tiff.Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] [WithFile(TestImages.Tiff.Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider) public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider)

Loading…
Cancel
Save