Browse Source

Implemented fallback code for big-endian machines

pull/1761/head
Dmitry Pentin 5 years ago
parent
commit
91a95b5814
  1. 101
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

101
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -445,21 +445,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
return dc;
}
[MethodImpl(InliningOptions.ShortMethod)]
private void FlushRemainingBytes()
{
// Bytes count we want to write to the output stream
int valuableBytesCount = (int)Numerics.DivideCeil((uint)this.bitCount, 8);
// Padding all 4 bytes with 1's while not corrupting initial bits stored in accumulatedBits
uint packedBytes = this.accumulatedBits | (uint.MaxValue >> this.bitCount);
int writeIndex = this.emitWriteIndex;
this.emitBuffer[writeIndex - 1] = packedBytes;
this.FlushToStream((writeIndex * 4) - valuableBytesCount);
}
/// <summary>
/// Emits the least significant count of bits to the stream write buffer.
/// The precondition is bits
@ -568,28 +553,96 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
#endif
}
/// <summary>
/// Flushes cached bytes to the ouput stream respecting stuff bytes.
/// </summary>
/// <remarks>
/// Bytes cached via <see cref="Emit"/> are stored in 4-bytes blocks which makes
/// this method endianness dependent.
/// </remarks>
[MethodImpl(InliningOptions.ShortMethod)]
private void FlushToStream() => this.FlushToStream(this.emitWriteIndex * 4);
[MethodImpl(InliningOptions.ShortMethod)]
private void FlushToStream(int endIndex)
private void FlushToStream()
{
Span<byte> emitBytes = MemoryMarshal.AsBytes(this.emitBuffer.AsSpan());
int writeIdx = 0;
int startIndex = emitBytes.Length - 1;
for (int i = startIndex; i >= endIndex; i--)
int endIndex = this.emitWriteIndex * sizeof(uint);
// Some platforms may fail to eliminate this if-else branching
// Even if it happens - buffer is flushed in big packs,
// branching overhead shouldn't be noticeable
if (BitConverter.IsLittleEndian)
{
byte value = emitBytes[i];
this.streamWriteBuffer[writeIdx++] = value;
if (value == 0xff)
// For little endian case bytes are ordered and can be
// safely written to the stream with stuff bytes
// First byte is cached on the most significant index
// so we are going from the end of the array to its beginning:
// ... [ double word #1 ] [ double word #0 ]
// ... [idx3|idx2|idx1|idx0] [idx3|idx2|idx1|idx0]
for (int i = startIndex; i >= endIndex; i--)
{
this.streamWriteBuffer[writeIdx++] = 0x00;
byte value = emitBytes[i];
this.streamWriteBuffer[writeIdx++] = value;
// Inserting stuff byte
if (value == 0xff)
{
this.streamWriteBuffer[writeIdx++] = 0x00;
}
}
}
else
{
// For big endian case bytes are ordered in 4-byte packs
// which are ordered like bytes in the little endian case by in 4-byte packs:
// ... [ double word #1 ] [ double word #0 ]
// ... [idx0|idx1|idx2|idx3] [idx0|idx1|idx2|idx3]
// So we must write each 4-bytes in 'natural order'
for (int i = startIndex; i >= endIndex; i -= 4)
{
// This loop is caused by the nature of underlying byte buffer
// implementation and indeed causes performace by somewhat 5%
// compared to little endian scenario
// Even with this performance drop this cached buffer implementation
// is faster than individually writing bytes using binary shifts and binary and(s)
for (int j = i - 3; j <= i; j++)
{
byte value = emitBytes[j];
this.streamWriteBuffer[writeIdx++] = value;
// Inserting stuff byte
if (value == 0xff)
{
this.streamWriteBuffer[writeIdx++] = 0x00;
}
}
}
}
this.target.Write(this.streamWriteBuffer, 0, writeIdx);
this.emitWriteIndex = this.emitBuffer.Length;
}
[MethodImpl(InliningOptions.ShortMethod)]
private void FlushRemainingBytes()
{
// Flush full 4-byte blocks
this.FlushToStream();
// Padding all 4 bytes with 1's while not corrupting initial bits stored in accumulatedBits
// And writing only valuable count of bytes count we want to write to the output stream
int valuableBytesCount = (int)Numerics.DivideCeil((uint)this.bitCount, 8);
uint packedBytes = this.accumulatedBits | (uint.MaxValue >> this.bitCount);
Span<byte> emitBytes = MemoryMarshal.AsBytes(this.emitBuffer.AsSpan());
for (int i = 0; i < valuableBytesCount; i++)
{
emitBytes[i] = (byte)((packedBytes >> ((3 - i) * 8)) & 0xff);
}
// Flush remaining 'tail' bytes
this.target.Write(emitBytes, 0, valuableBytesCount);
}
}
}

Loading…
Cancel
Save