Browse Source

Add tiff encoder option to choose the deflate compression level

pull/1467/head
Brian Popow 5 years ago
parent
commit
20fcf84311
  1. 2
      src/ImageSharp/Formats/Png/DeflateCompressionLevel.cs
  2. 4
      src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
  3. 2
      src/ImageSharp/Formats/Png/PngEncoder.cs
  4. 2
      src/ImageSharp/Formats/Png/PngEncoderOptions.cs
  5. 2
      src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
  6. 7
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  7. 22
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  8. 17
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  9. 44
      src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
  10. 28
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  11. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  12. 2
      tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs

2
src/ImageSharp/Formats/Png/PngCompressionLevel.cs → src/ImageSharp/Formats/Png/DeflateCompressionLevel.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Provides enumeration of available PNG compression levels.
/// </summary>
public enum PngCompressionLevel
public enum DeflateCompressionLevel
{
/// <summary>
/// Level 0. Equivalent to <see cref="NoCompression"/>.

4
src/ImageSharp/Formats/Png/IPngEncoderOptions.cs

@ -28,9 +28,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Gets the compression level 1-9.
/// <remarks>Defaults to <see cref="PngCompressionLevel.DefaultCompression"/>.</remarks>
/// <remarks>Defaults to <see cref="DeflateCompressionLevel.DefaultCompression"/>.</remarks>
/// </summary>
PngCompressionLevel CompressionLevel { get; }
DeflateCompressionLevel CompressionLevel { get; }
/// <summary>
/// Gets the threshold of characters in text metadata, when compression should be used.

2
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public PngFilterMethod? FilterMethod { get; set; }
/// <inheritdoc/>
public PngCompressionLevel CompressionLevel { get; set; } = PngCompressionLevel.DefaultCompression;
public DeflateCompressionLevel CompressionLevel { get; set; } = DeflateCompressionLevel.DefaultCompression;
/// <inheritdoc/>
public int TextCompressionThreshold { get; set; } = 1024;

2
src/ImageSharp/Formats/Png/PngEncoderOptions.cs

@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public PngFilterMethod? FilterMethod { get; }
/// <inheritdoc/>
public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression;
public DeflateCompressionLevel CompressionLevel { get; } = DeflateCompressionLevel.DefaultCompression;
/// <inheritdoc/>
public int TextCompressionThreshold { get; }

2
src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs

@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <param name="stream">The stream to compress.</param>
/// <param name="level">The compression level.</param>
public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, PngCompressionLevel level)
public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, DeflateCompressionLevel level)
{
int compressionLevel = (int)level;
this.rawStream = stream;

7
src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
@ -15,6 +16,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
TiffEncoderCompression Compression { get; }
/// <summary>
/// Gets the compression level 1-9 for the deflate compression mode.
/// <remarks>Defaults to <see cref="DeflateCompressionLevel.DefaultCompression"/>.</remarks>
/// </summary>
DeflateCompressionLevel CompressionLevel { get; }
/// <summary>
/// Gets the encoding mode to use. RGB, RGB with color palette or gray.
/// </summary>

22
src/ImageSharp/Formats/Tiff/TiffEncoder.cs

@ -4,7 +4,9 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -15,25 +17,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
{
/// <summary>
/// Gets or sets a value indicating which compression to use.
/// </summary>
/// <inheritdoc/>
public TiffEncoderCompression Compression { get; set; } = TiffEncoderCompression.None;
/// <summary>
/// Gets or sets the encoding mode to use. RGB, RGB with a color palette or gray.
/// </summary>
/// <inheritdoc/>
public DeflateCompressionLevel CompressionLevel { get; } = DeflateCompressionLevel.DefaultCompression;
/// <inheritdoc/>
public TiffEncodingMode Mode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to use horizontal prediction. This can improve the compression ratio with deflate or lzw compression.
/// </summary>
/// <inheritdoc/>
public bool UseHorizontalPredictor { get; set; }
/// <summary>
/// Gets or sets the quantizer for color images with a palette.
/// Defaults to OctreeQuantizer.
/// </summary>
/// <inheritdoc/>
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>

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

@ -9,6 +9,7 @@ using System.Threading;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -51,7 +52,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>
/// Indicating whether to use horizontal prediction. This can improve the compression ratio with deflate compression.
/// </summary>
private bool useHorizontalPredictor;
private readonly bool useHorizontalPredictor;
/// <summary>
/// Sets the deflate compression level.
/// </summary>
private readonly DeflateCompressionLevel compressionLevel;
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
@ -65,6 +71,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.Mode = options.Mode;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.useHorizontalPredictor = options.UseHorizontalPredictor;
this.compressionLevel = options.CompressionLevel;
}
/// <summary>
@ -165,16 +172,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
switch (this.Mode)
{
case TiffEncodingMode.ColorPalette:
imageDataBytes = writer.WritePalettedRgb(image, this.quantizer, this.padding, this.CompressionType, this.useHorizontalPredictor, out colorMap);
imageDataBytes = writer.WritePalettedRgb(image, this.quantizer, this.padding, this.CompressionType, this.compressionLevel, this.useHorizontalPredictor, out colorMap);
break;
case TiffEncodingMode.Gray:
imageDataBytes = writer.WriteGray(image, this.padding, this.CompressionType, this.useHorizontalPredictor);
imageDataBytes = writer.WriteGray(image, this.padding, this.CompressionType, this.compressionLevel, this.useHorizontalPredictor);
break;
case TiffEncodingMode.BiColor:
imageDataBytes = writer.WriteBiColor(image, this.CompressionType);
imageDataBytes = writer.WriteBiColor(image, this.CompressionType, this.compressionLevel);
break;
default:
imageDataBytes = writer.WriteRgb(image, this.padding, this.CompressionType, this.useHorizontalPredictor);
imageDataBytes = writer.WriteRgb(image, this.padding, this.CompressionType, this.compressionLevel, this.useHorizontalPredictor);
break;
}

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

@ -138,16 +138,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <param name="image">The image to write to the stream.</param>
/// <param name="padding">The padding bytes for each row.</param>
/// <param name="compression">The compression to use.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used. Should only be used with deflate compression.</param>
/// <returns>The number of bytes written.</returns>
public int WriteRgb<TPixel>(Image<TPixel> image, int padding, TiffEncoderCompression compression, bool useHorizontalPredictor)
public int WriteRgb<TPixel>(Image<TPixel> image, int padding, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
using IManagedByteBuffer row = this.AllocateRow(image.Width, 3, padding);
Span<byte> rowSpan = row.GetSpan();
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteDeflateCompressedRgb(image, rowSpan, useHorizontalPredictor);
return this.WriteDeflateCompressedRgb(image, rowSpan, compressionLevel, useHorizontalPredictor);
}
if (compression == TiffEncoderCompression.Lzw)
@ -179,16 +180,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="rowSpan">A Span for a pixel row.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used. Should only be used with deflate compression.</param>
/// <returns>The number of bytes written.</returns>
private int WriteDeflateCompressedRgb<TPixel>(Image<TPixel> image, Span<byte> rowSpan, bool useHorizontalPredictor)
private int WriteDeflateCompressedRgb<TPixel>(Image<TPixel> image, Span<byte> rowSpan, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesWritten = 0;
using var memoryStream = new MemoryStream();
// TODO: move zlib compression from png to a common place?
using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, PngCompressionLevel.Level6); // TODO: make compression level configurable
using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, compressionLevel); // TODO: move zlib compression from png to a common place?
for (int y = 0; y < image.Height; y++)
{
@ -286,10 +286,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <param name="quantizer">The quantizer to use.</param>
/// <param name="padding">The padding bytes for each row.</param>
/// <param name="compression">The compression to use.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</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>
/// <returns>The number of bytes written.</returns>
public int WritePalettedRgb<TPixel>(Image<TPixel> image, IQuantizer quantizer, int padding, TiffEncoderCompression compression, bool useHorizontalPredictor, out IExifValue colorMap)
public int WritePalettedRgb<TPixel>(Image<TPixel> image, IQuantizer quantizer, int padding, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor, out IExifValue colorMap)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorsPerChannel = 256;
@ -341,7 +342,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteDeflateCompressedPalettedRgb(image, quantized, padding, useHorizontalPredictor);
return this.WriteDeflateCompressedPalettedRgb(image, quantized, padding, compressionLevel, useHorizontalPredictor);
}
if (compression == TiffEncoderCompression.Lzw)
@ -379,14 +380,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <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>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used.</param>
/// <returns>The number of bytes written.</returns>
public int WriteDeflateCompressedPalettedRgb<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, int padding, bool useHorizontalPredictor)
public int WriteDeflateCompressedPalettedRgb<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, int padding, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
using IManagedByteBuffer tmpBuffer = this.memoryAllocator.AllocateManagedByteBuffer(image.Width);
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, compressionLevel);
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
@ -513,9 +515,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <param name="image">The image to write to the stream.</param>
/// <param name="padding">The padding bytes for each row.</param>
/// <param name="compression">The compression to use.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used. Should only be used with deflate or lzw compression.</param>
/// <returns>The number of bytes written.</returns>
public int WriteGray<TPixel>(Image<TPixel> image, int padding, TiffEncoderCompression compression, bool useHorizontalPredictor)
public int WriteGray<TPixel>(Image<TPixel> image, int padding, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
using IManagedByteBuffer row = this.AllocateRow(image.Width, 1, padding);
@ -523,7 +526,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteGrayDeflateCompressed(image, rowSpan, useHorizontalPredictor);
return this.WriteGrayDeflateCompressed(image, rowSpan, compressionLevel, useHorizontalPredictor);
}
if (compression == TiffEncoderCompression.Lzw)
@ -553,16 +556,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// </summary>
/// <param name="image">The image to write to the stream.</param>
/// <param name="rowSpan">A span of a row of pixels.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used.</param>
/// <returns>The number of bytes written.</returns>
private int WriteGrayDeflateCompressed<TPixel>(Image<TPixel> image, Span<byte> rowSpan, bool useHorizontalPredictor)
private int WriteGrayDeflateCompressed<TPixel>(Image<TPixel> image, Span<byte> rowSpan, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesWritten = 0;
using var memoryStream = new MemoryStream();
// TODO: move zlib compression from png to a common place?
using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, PngCompressionLevel.Level6); // TODO: make compression level configurable
using var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, compressionLevel); // TODO: move zlib compression from png to a common place?
for (int y = 0; y < image.Height; y++)
{
@ -656,8 +658,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="compression">The compression to use.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <returns>The number of bytes written.</returns>
public int WriteBiColor<TPixel>(Image<TPixel> image, TiffEncoderCompression compression)
public int WriteBiColor<TPixel>(Image<TPixel> image, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel)
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = image.Width % 8 == 0 ? 0 : 1;
@ -674,7 +677,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteBiColorDeflate(imageBlackWhite, pixelRowAsGraySpan, outputRow);
return this.WriteBiColorDeflate(imageBlackWhite, pixelRowAsGraySpan, outputRow, compressionLevel);
}
if (compression == TiffEncoderCompression.PackBits)
@ -734,12 +737,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <param name="image">The image to write to the stream.</param>
/// <param name="pixelRowAsGraySpan">A span for converting a pixel row to gray.</param>
/// <param name="outputRow">A span which will be used to store the output pixels.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <returns>The number of bytes written.</returns>
public int WriteBiColorDeflate<TPixel>(Image<TPixel> image, Span<L8> pixelRowAsGraySpan, Span<byte> outputRow)
public int WriteBiColorDeflate<TPixel>(Image<TPixel> image, Span<L8> pixelRowAsGraySpan, Span<byte> outputRow, DeflateCompressionLevel compressionLevel)
where TPixel : unmanaged, IPixel<TPixel>
{
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, compressionLevel);
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)

28
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -62,19 +62,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
/// <summary>
/// All types except Palette
/// </summary>
public static readonly TheoryData<PngCompressionLevel> CompressionLevels
= new TheoryData<PngCompressionLevel>
public static readonly TheoryData<DeflateCompressionLevel> CompressionLevels
= new TheoryData<DeflateCompressionLevel>
{
PngCompressionLevel.Level0,
PngCompressionLevel.Level1,
PngCompressionLevel.Level2,
PngCompressionLevel.Level3,
PngCompressionLevel.Level4,
PngCompressionLevel.Level5,
PngCompressionLevel.Level6,
PngCompressionLevel.Level7,
PngCompressionLevel.Level8,
PngCompressionLevel.Level9,
DeflateCompressionLevel.Level0,
DeflateCompressionLevel.Level1,
DeflateCompressionLevel.Level2,
DeflateCompressionLevel.Level3,
DeflateCompressionLevel.Level4,
DeflateCompressionLevel.Level5,
DeflateCompressionLevel.Level6,
DeflateCompressionLevel.Level7,
DeflateCompressionLevel.Level8,
DeflateCompressionLevel.Level9,
};
public static readonly TheoryData<int> PaletteSizes = new TheoryData<int>
@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[Theory]
[WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)]
public void WorksWithAllCompressionLevels<TPixel>(TestImageProvider<TPixel> provider, PngCompressionLevel compressionLevel)
public void WorksWithAllCompressionLevels<TPixel>(TestImageProvider<TPixel> provider, DeflateCompressionLevel compressionLevel)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
@ -573,7 +573,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
PngFilterMethod pngFilterMethod,
PngBitDepth bitDepth,
PngInterlaceMode interlaceMode,
PngCompressionLevel compressionLevel = PngCompressionLevel.DefaultCompression,
DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression,
int paletteSize = 255,
bool appendPngColorType = false,
bool appendPngFilterMethod = false,

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
Stream compressedStream = new MemoryStream();
using (Stream uncompressedStream = new MemoryStream(data),
deflateStream = new ZlibDeflateStream(Configuration.Default.MemoryAllocator, compressedStream, ImageSharp.Formats.Png.PngCompressionLevel.Level6))
deflateStream = new ZlibDeflateStream(Configuration.Default.MemoryAllocator, compressedStream, ImageSharp.Formats.Png.DeflateCompressionLevel.Level6))
{
uncompressedStream.CopyTo(deflateStream);
}

2
tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs

@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Tests
public async Task SaveAsync_WithNonSeekableStream_IsCancellable()
{
using var image = new Image<Rgba32>(4000, 4000);
var encoder = new PngEncoder() { CompressionLevel = PngCompressionLevel.BestCompression };
var encoder = new PngEncoder() { CompressionLevel = DeflateCompressionLevel.BestCompression };
using var stream = new MemoryStream();
var asyncStream = new AsyncStreamWrapper(stream, () => false);
var cts = new CancellationTokenSource();

Loading…
Cancel
Save