diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
index 35e0e26485..bbdd3220f0 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
@@ -65,6 +65,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// Yields codewords by index consisting of [run length | bitsize].
private HuffmanLut[] huffmanTables;
+ ///
+ /// Emitted bits 'micro buffer' before being transferred to the .
+ ///
+ private uint accumulatedBits;
+
///
/// Buffer for temporal storage of huffman rle encoding bit data.
///
@@ -82,18 +87,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
private readonly byte[] streamWriteBuffer;
- private int emitWriteIndex;
-
- ///
- /// Emitted bits 'micro buffer' before being transferred to the .
- ///
- private uint accumulatedBits;
-
///
/// Number of jagged bits stored in
///
private int bitCount;
+ private int emitWriteIndex;
+
private Block8x8 tempBlock;
///
@@ -101,9 +101,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
private readonly Stream target;
- public HuffmanScanEncoder(int componentCount, Stream outputStream)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Amount of encoded 8x8 blocks per single jpeg macroblock.
+ /// Output stream for saving encoded data.
+ public HuffmanScanEncoder(int blocksPerCodingUnit, Stream outputStream)
{
- int emitBufferByteLength = MaxBytesPerBlock * componentCount;
+ int emitBufferByteLength = MaxBytesPerBlock * blocksPerCodingUnit;
this.emitBuffer = new uint[emitBufferByteLength / sizeof(uint)];
this.emitWriteIndex = this.emitBuffer.Length;
@@ -112,7 +117,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.target = outputStream;
}
- private bool IsFlushNeeded
+ ///
+ /// Gets a value indicating whether is full
+ /// and must be flushed using
+ /// before encoding next 8x8 coding block.
+ ///
+ private bool IsStreamFlushNeeded
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2;
@@ -174,7 +184,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref pixelConverter.Cr,
ref chrominanceQuantTable);
- if (this.IsFlushNeeded)
+ if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
@@ -249,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref pixelConverter.Cr,
ref chrominanceQuantTable);
- if (this.IsFlushNeeded)
+ if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
@@ -300,7 +310,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref pixelConverter.Y,
ref luminanceQuantTable);
- if (this.IsFlushNeeded)
+ if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
@@ -364,7 +374,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref pixelConverter.B,
ref luminanceQuantTable);
- if (this.IsFlushNeeded)
+ if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
@@ -447,15 +457,48 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
///
- /// Emits the least significant count of bits to the stream write buffer.
- /// The precondition is bits
- ///
- /// < 1<<nBits && nBits <= 16
- ///
- /// .
+ /// Emits the most significant count of bits to the buffer.
///
- /// The packed bits.
- /// The number of bits
+ ///
+ ///
+ /// Supports up to 32 count of bits but, generally speaking, jpeg
+ /// standard assures that there won't be more than 16 bits per single
+ /// value.
+ ///
+ ///
+ /// Emitting algorithm uses 3 intermediate buffers for caching before
+ /// writing to the stream:
+ ///
+ /// -
+ /// uint32
+ ///
+ /// Bit buffer. Encoded spectral values can occupy up to 16 bits, bits
+ /// are assembled to whole bytes via this intermediate buffer.
+ ///
+ ///
+ /// -
+ /// uint32[]
+ ///
+ /// Assembled bytes from uint32 buffer are saved into this buffer.
+ /// uint32 buffer values are saved using indices from the last to the first.
+ /// As bytes are saved to the memory as 4-byte packages endianness matters:
+ /// Jpeg stream is big-endian, indexing buffer bytes from the last index to the
+ /// first eliminates all operations to extract separate bytes. This only works for
+ /// little-endian machines (there are no known examples of big-endian users atm).
+ /// For big-endians this approach is slower due to the separate byte extraction.
+ ///
+ ///
+ /// -
+ /// byte[]
+ ///
+ /// Byte buffer used only during method.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Bits to emit, must be shifted to the left.
+ /// Bits count stored in the bits parameter.
[MethodImpl(InliningOptions.ShortMethod)]
private void Emit(uint bits, int count)
{
@@ -475,10 +518,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
///
- /// Emits the given value with the given Huffman encoder.
+ /// Emits the given value with the given Huffman table.
///
- /// Compiled Huffman spec values.
- /// The value to encode.
+ /// Huffman table.
+ /// Value to encode.
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuff(int[] table, int value)
{
@@ -489,9 +532,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
/// Emits given value via huffman rle encoding.
///
- /// Compiled Huffman spec values.
+ /// Huffman table.
/// The number of preceding zeroes, preshifted by 4 to the left.
- /// The value to encode.
+ /// Value to encode.
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuffRLE(int[] table, int runLength, int value)
{
@@ -555,11 +598,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
///
- /// Flushes cached bytes to the ouput stream respecting stuff bytes.
+ /// General method for flushing cached spectral data bytes to
+ /// the ouput stream respecting stuff bytes.
///
///
- /// Bytes cached via are stored in 4-bytes blocks which makes
- /// this method endianness dependent.
+ /// Bytes cached via are stored in 4-bytes blocks
+ /// which makes this method endianness dependent.
///
[MethodImpl(InliningOptions.ShortMethod)]
private void FlushToStream(int endIndex)
@@ -623,12 +667,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.target.Write(this.streamWriteBuffer, 0, writeIdx);
}
+ ///
+ /// Flushes spectral data bytes after encoding all channel blocks
+ /// in a single jpeg macroblock using .
+ ///
+ ///
+ /// This must be called only if is true
+ /// only during the macroblocks encoding routine.
+ ///
private void FlushToStream()
{
this.FlushToStream(this.emitWriteIndex * 4);
this.emitWriteIndex = this.emitBuffer.Length;
}
+ ///
+ /// Flushes final cached bits to the stream padding 1's to
+ /// complement full bytes.
+ ///
+ ///
+ /// This must be called only once at the end of the encoding routine.
+ /// check is not needed.
+ ///
[MethodImpl(InliningOptions.ShortMethod)]
private void FlushRemainingBytes()
{