Browse Source

Optimize DeflaterPendingBuffer

af/merge-core
James Jackson-South 6 years ago
parent
commit
16759b0429
  1. 4
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  2. 57
      src/ImageSharp/Formats/Png/Zlib/Deflater.cs
  3. 6
      src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
  4. 4
      src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
  5. 12
      src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs
  6. 21
      src/ImageSharp/Formats/Png/Zlib/DeflaterPending.cs
  7. 187
      src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
  8. 276
      src/ImageSharp/Formats/Png/Zlib/PendingBuffer.cs
  9. 13
      src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs

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

@ -699,7 +699,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
using (var memoryStream = new MemoryStream())
{
using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel))
using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel))
{
deflateStream.Write(textBytes);
}
@ -790,7 +790,7 @@ namespace SixLabors.ImageSharp.Formats.Png
using (var memoryStream = new MemoryStream())
{
using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel))
using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel))
{
if (this.options.InterlaceMethod == PngInterlaceMode.Adam7)
{

57
src/ImageSharp/Formats/Png/Zlib/Deflater.cs

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
@ -18,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
///
/// author of the original java version : Jochen Hoenicke
/// </summary>
public class Deflater
public sealed class Deflater : IDisposable
{
#region Deflater Documentation
@ -149,28 +150,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
#region Constructors
/// <summary>
/// Creates a new deflater with default compression level.
/// </summary>
public Deflater() : this(DEFAULT_COMPRESSION, false)
{
}
/// <summary>
/// Creates a new deflater with given compression level.
/// </summary>
/// <param name="level">
/// the compression level, a value between NO_COMPRESSION
/// and BEST_COMPRESSION, or DEFAULT_COMPRESSION.
/// </param>
/// <exception cref="System.ArgumentOutOfRangeException">if lvl is out of range.</exception>
public Deflater(int level) : this(level, false)
{
}
/// <summary>
/// Creates a new deflater with given compression level.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <param name="level">
/// the compression level, a value between NO_COMPRESSION
/// and BEST_COMPRESSION.
@ -181,7 +164,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// useful for the GZIP/PKZIP formats.
/// </param>
/// <exception cref="System.ArgumentOutOfRangeException">if lvl is out of range.</exception>
public Deflater(int level, bool noZlibHeaderOrFooter)
public Deflater(MemoryAllocator memoryAllocator, int level, bool noZlibHeaderOrFooter)
{
if (level == DEFAULT_COMPRESSION)
{
@ -192,7 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
throw new ArgumentOutOfRangeException(nameof(level));
}
pending = new DeflaterPending();
pending = new DeflaterPendingBuffer(memoryAllocator);
engine = new DeflaterEngine(pending, noZlibHeaderOrFooter);
this.noZlibHeaderOrFooter = noZlibHeaderOrFooter;
SetStrategy(DeflateStrategy.Default);
@ -598,13 +581,41 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary>
/// The pending output.
/// </summary>
private DeflaterPending pending;
private DeflaterPendingBuffer pending;
/// <summary>
/// The deflater engine.
/// </summary>
private DeflaterEngine engine;
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
this.pending.Dispose();
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
this.pending = null;
disposedValue = true;
}
}
/// <inheritdoc/>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#endregion Instance Fields
}
}

6
src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs

@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="pending">
/// Pending buffer to use
/// </param>
public DeflaterEngine(DeflaterPending pending)
public DeflaterEngine(DeflaterPendingBuffer pending)
: this(pending, false)
{
}
@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="noAdlerCalculation">
/// If no adler calculation should be performed
/// </param>
public DeflaterEngine(DeflaterPending pending, bool noAdlerCalculation)
public DeflaterEngine(DeflaterPendingBuffer pending, bool noAdlerCalculation)
{
this.pending = pending;
huffman = new DeflaterHuffman(pending);
@ -938,7 +938,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// </summary>
private int inputEnd;
private DeflaterPending pending;
private DeflaterPendingBuffer pending;
private DeflaterHuffman huffman;
/// <summary>

4
src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs

@ -590,7 +590,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary>
/// Pending buffer to use
/// </summary>
public DeflaterPending pending;
public DeflaterPendingBuffer pending;
private Tree literalTree;
private Tree distTree;
@ -651,7 +651,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// Construct instance with pending buffer
/// </summary>
/// <param name="pending">Pending buffer to use</param>
public DeflaterHuffman(DeflaterPending pending)
public DeflaterHuffman(DeflaterPendingBuffer pending)
{
this.pending = pending;

12
src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs

@ -17,18 +17,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public class DeflaterOutputStream : Stream
{
#region Constructors
/// <summary>
/// Creates a new DeflaterOutputStream with a default Deflater and default buffer size.
/// </summary>
/// <param name="baseOutputStream">
/// the output stream where deflated output should be written.
/// </param>
public DeflaterOutputStream(Stream baseOutputStream)
: this(baseOutputStream, new Deflater(), 512)
{
}
/// <summary>
/// Creates a new DeflaterOutputStream with the given Deflater and
/// default buffer size.

21
src/ImageSharp/Formats/Png/Zlib/DeflaterPending.cs

@ -1,21 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// <auto-generated/>
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// This class stores the pending output of the Deflater.
///
/// author of the original java version : Jochen Hoenicke
/// </summary>
public class DeflaterPending : PendingBuffer
{
/// <summary>
/// Construct instance with default buffer size
/// </summary>
public DeflaterPending() : base(DeflaterConstants.PENDING_BUF_SIZE)
{
}
}
}

187
src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs

@ -0,0 +1,187 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// Stores pending data for writing data to the Deflater.
/// </summary>
public sealed unsafe class DeflaterPendingBuffer : IDisposable
{
private readonly byte[] buffer;
private readonly byte* pinnedBuffer;
private readonly IManagedByteBuffer managedBuffer;
private MemoryHandle handle;
private int start;
private int end;
private uint bits;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="DeflaterPendingBuffer"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
public DeflaterPendingBuffer(MemoryAllocator memoryAllocator)
{
this.buffer = new byte[DeflaterConstants.PENDING_BUF_SIZE];
this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE);
this.buffer = this.managedBuffer.Array;
this.handle = this.managedBuffer.Memory.Pin();
this.pinnedBuffer = (byte*)this.handle.Pointer;
}
/// <summary>
/// Gets the number of bits written to the buffer.
/// </summary>
public int BitCount { get; private set; }
/// <summary>
/// Gets a value indicating whether indicates the buffer has been flushed.
/// </summary>
public bool IsFlushed => this.end == 0;
/// <summary>
/// Clear internal state/buffers.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Reset() => this.start = this.end = this.BitCount = 0;
/// <summary>
/// Write a short value to buffer LSB first.
/// </summary>
/// <param name="value">The value to write.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void WriteShort(int value)
{
byte* pinned = this.pinnedBuffer;
pinned[this.end++] = unchecked((byte)value);
pinned[this.end++] = unchecked((byte)(value >> 8));
}
/// <summary>
/// Write a block of data to the internal buffer.
/// </summary>
/// <param name="block">The data to write.</param>
/// <param name="offset">The offset of first byte to write.</param>
/// <param name="length">The number of bytes to write.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void WriteBlock(byte[] block, int offset, int length)
{
Unsafe.CopyBlockUnaligned(ref this.buffer[this.end], ref block[offset], unchecked((uint)length));
this.end += length;
}
/// <summary>
/// Aligns internal buffer on a byte boundary.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void AlignToByte()
{
if (this.BitCount > 0)
{
byte* pinned = this.pinnedBuffer;
pinned[this.end++] = unchecked((byte)this.bits);
if (this.BitCount > 8)
{
pinned[this.end++] = unchecked((byte)(this.bits >> 8));
}
}
this.bits = 0;
this.BitCount = 0;
}
/// <summary>
/// Write bits to internal buffer
/// </summary>
/// <param name="b">source of bits</param>
/// <param name="count">number of bits to write</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void WriteBits(int b, int count)
{
this.bits |= (uint)(b << this.BitCount);
this.BitCount += count;
if (this.BitCount >= 16)
{
byte* pinned = this.pinnedBuffer;
pinned[this.end++] = unchecked((byte)this.bits);
pinned[this.end++] = unchecked((byte)(this.bits >> 8));
this.bits >>= 16;
this.BitCount -= 16;
}
}
/// <summary>
/// Write a short value to internal buffer most significant byte first
/// </summary>
/// <param name="value">The value to write</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void WriteShortMSB(int value)
{
byte* pinned = this.pinnedBuffer;
pinned[this.end++] = unchecked((byte)(value >> 8));
pinned[this.end++] = unchecked((byte)value);
}
/// <summary>
/// Flushes the pending buffer into the given output array.
/// If the output array is to small, only a partial flush is done.
/// </summary>
/// <param name="output">The output array.</param>
/// <param name="offset">The offset into output array.</param>
/// <param name="length">The maximum number of bytes to store.</param>
/// <returns>The number of bytes flushed.</returns>
public int Flush(byte[] output, int offset, int length)
{
if (this.BitCount >= 8)
{
this.pinnedBuffer[this.end++] = unchecked((byte)this.bits);
this.bits >>= 8;
this.BitCount -= 8;
}
if (length > this.end - this.start)
{
length = this.end - this.start;
Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length));
this.start = 0;
this.end = 0;
}
else
{
Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length));
this.start += length;
}
return length;
}
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.isDisposed)
{
if (disposing)
{
this.handle.Dispose();
this.managedBuffer.Dispose();
}
this.isDisposed = true;
}
}
}
}

276
src/ImageSharp/Formats/Png/Zlib/PendingBuffer.cs

@ -1,276 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// <auto-generated/>
using System;
using System.Collections.Generic;
using System.Text;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// This class is general purpose class for writing data to a buffer.
///
/// It allows you to write bits as well as bytes
/// Based on DeflaterPending.java
///
/// author of the original java version : Jochen Hoenicke
/// </summary>
public class PendingBuffer
{
#region Instance Fields
/// <summary>
/// Internal work buffer
/// </summary>
private readonly byte[] buffer;
private int start;
private int end;
private uint bits;
private int bitCount;
#endregion Instance Fields
#region Constructors
/// <summary>
/// construct instance using default buffer size of 4096
/// </summary>
public PendingBuffer() : this(4096)
{
}
/// <summary>
/// construct instance using specified buffer size
/// </summary>
/// <param name="bufferSize">
/// size to use for internal buffer
/// </param>
public PendingBuffer(int bufferSize)
{
buffer = new byte[bufferSize];
}
#endregion Constructors
/// <summary>
/// Clear internal state/buffers
/// </summary>
public void Reset()
{
start = end = bitCount = 0;
}
/// <summary>
/// Write a byte to buffer
/// </summary>
/// <param name="value">
/// The value to write
/// </param>
public void WriteByte(int value)
{
#if DebugDeflation
if (DeflaterConstants.DEBUGGING && (start != 0) )
{
throw new ImageFormatException("Debug check: start != 0");
}
#endif
buffer[end++] = unchecked((byte)value);
}
/// <summary>
/// Write a short value to buffer LSB first
/// </summary>
/// <param name="value">
/// The value to write.
/// </param>
public void WriteShort(int value)
{
#if DebugDeflation
if (DeflaterConstants.DEBUGGING && (start != 0) )
{
throw new ImageFormatException("Debug check: start != 0");
}
#endif
buffer[end++] = unchecked((byte)value);
buffer[end++] = unchecked((byte)(value >> 8));
}
/// <summary>
/// write an integer LSB first
/// </summary>
/// <param name="value">The value to write.</param>
public void WriteInt(int value)
{
#if DebugDeflation
if (DeflaterConstants.DEBUGGING && (start != 0) )
{
throw new ImageFormatException("Debug check: start != 0");
}
#endif
buffer[end++] = unchecked((byte)value);
buffer[end++] = unchecked((byte)(value >> 8));
buffer[end++] = unchecked((byte)(value >> 16));
buffer[end++] = unchecked((byte)(value >> 24));
}
/// <summary>
/// Write a block of data to buffer
/// </summary>
/// <param name="block">data to write</param>
/// <param name="offset">offset of first byte to write</param>
/// <param name="length">number of bytes to write</param>
public void WriteBlock(byte[] block, int offset, int length)
{
#if DebugDeflation
if (DeflaterConstants.DEBUGGING && (start != 0) )
{
throw new ImageFormatException("Debug check: start != 0");
}
#endif
System.Array.Copy(block, offset, buffer, end, length);
end += length;
}
/// <summary>
/// The number of bits written to the buffer
/// </summary>
public int BitCount
{
get
{
return bitCount;
}
}
/// <summary>
/// Align internal buffer on a byte boundary
/// </summary>
public void AlignToByte()
{
#if DebugDeflation
if (DeflaterConstants.DEBUGGING && (start != 0) )
{
throw new ImageFormatException("Debug check: start != 0");
}
#endif
if (bitCount > 0)
{
buffer[end++] = unchecked((byte)bits);
if (bitCount > 8)
{
buffer[end++] = unchecked((byte)(bits >> 8));
}
}
bits = 0;
bitCount = 0;
}
/// <summary>
/// Write bits to internal buffer
/// </summary>
/// <param name="b">source of bits</param>
/// <param name="count">number of bits to write</param>
public void WriteBits(int b, int count)
{
#if DebugDeflation
if (DeflaterConstants.DEBUGGING && (start != 0) )
{
throw new ImageFormatException("Debug check: start != 0");
}
// if (DeflaterConstants.DEBUGGING) {
// //Console.WriteLine("writeBits("+b+","+count+")");
// }
#endif
bits |= (uint)(b << bitCount);
bitCount += count;
if (bitCount >= 16)
{
buffer[end++] = unchecked((byte)bits);
buffer[end++] = unchecked((byte)(bits >> 8));
bits >>= 16;
bitCount -= 16;
}
}
/// <summary>
/// Write a short value to internal buffer most significant byte first
/// </summary>
/// <param name="s">value to write</param>
public void WriteShortMSB(int s)
{
#if DebugDeflation
if (DeflaterConstants.DEBUGGING && (start != 0) )
{
throw new ImageFormatException("Debug check: start != 0");
}
#endif
buffer[end++] = unchecked((byte)(s >> 8));
buffer[end++] = unchecked((byte)s);
}
/// <summary>
/// Indicates if buffer has been flushed
/// </summary>
public bool IsFlushed
{
get
{
return end == 0;
}
}
/// <summary>
/// Flushes the pending buffer into the given output array. If the
/// output array is to small, only a partial flush is done.
/// </summary>
/// <param name="output">The output array.</param>
/// <param name="offset">The offset into output array.</param>
/// <param name="length">The maximum number of bytes to store.</param>
/// <returns>The number of bytes flushed.</returns>
public int Flush(byte[] output, int offset, int length)
{
if (bitCount >= 8)
{
buffer[end++] = unchecked((byte)bits);
bits >>= 8;
bitCount -= 8;
}
if (length > end - start)
{
length = end - start;
System.Array.Copy(buffer, start, output, offset, length);
start = 0;
end = 0;
}
else
{
System.Array.Copy(buffer, start, output, offset, length);
start += length;
}
return length;
}
/// <summary>
/// Convert internal buffer to byte array.
/// Buffer is empty on completion
/// </summary>
/// <returns>
/// The internal buffer contents converted to a byte array.
/// </returns>
public byte[] ToByteArray()
{
AlignToByte();
byte[] result = new byte[end - start];
System.Array.Copy(buffer, start, result, 0, result.Length);
start = 0;
end = 0;
return result;
}
}
}

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

@ -4,6 +4,7 @@
using System;
using System.IO;
using System.IO.Compression;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
@ -41,12 +42,15 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
// private DeflateStream deflateStream;
private DeflaterOutputStream deflateStream;
private Deflater deflater;
/// <summary>
/// Initializes a new instance of the <see cref="ZlibDeflateStream"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <param name="stream">The stream to compress.</param>
/// <param name="compressionLevel">The compression level.</param>
public ZlibDeflateStream(Stream stream, int compressionLevel)
public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, int compressionLevel)
{
this.rawStream = stream;
@ -100,7 +104,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
// {
// level = CompressionLevel.NoCompression;
// }
this.deflateStream = new DeflaterOutputStream(this.rawStream, new Deflater(compressionLevel, true)) { IsStreamOwner = false };
this.deflater = new Deflater(memoryAllocator, compressionLevel, true);
this.deflateStream = new DeflaterOutputStream(this.rawStream, this.deflater) { IsStreamOwner = false };
// this.deflateStream = new DeflateStream(this.rawStream, level, true);
}
@ -170,6 +175,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
this.deflateStream.Dispose();
this.deflateStream = null;
// TODO: Remove temporal coupling here.
this.deflater.Dispose();
this.deflater = null;
}
else
{

Loading…
Cancel
Save