|
|
@ -1,4 +1,4 @@ |
|
|
// Copyright (c) Six Labors and contributors.
|
|
|
// Copyright (c) Six Labors and contributors.
|
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
|
|
|
|
|
|
using System; |
|
|
using System; |
|
|
@ -41,13 +41,33 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private const int HashSize = 5003; |
|
|
private const int HashSize = 5003; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The amount to shift each code.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private const int HashShift = 4; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Mask used when shifting pixel values
|
|
|
/// Mask used when shifting pixel values
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private static readonly int[] Masks = |
|
|
private static readonly int[] Masks = |
|
|
{ |
|
|
{ |
|
|
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, |
|
|
0b0, |
|
|
0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF |
|
|
0b1, |
|
|
|
|
|
0b11, |
|
|
|
|
|
0b111, |
|
|
|
|
|
0b1111, |
|
|
|
|
|
0b11111, |
|
|
|
|
|
0b111111, |
|
|
|
|
|
0b1111111, |
|
|
|
|
|
0b11111111, |
|
|
|
|
|
0b111111111, |
|
|
|
|
|
0b1111111111, |
|
|
|
|
|
0b11111111111, |
|
|
|
|
|
0b111111111111, |
|
|
|
|
|
0b1111111111111, |
|
|
|
|
|
0b11111111111111, |
|
|
|
|
|
0b111111111111111, |
|
|
|
|
|
0b1111111111111111 |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -80,16 +100,6 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private readonly byte[] accumulators = new byte[256]; |
|
|
private readonly byte[] accumulators = new byte[256]; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// For dynamic table sizing
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private readonly int hsize = HashSize; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The current position within the pixelArray.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int position; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Number of bits/code
|
|
|
/// Number of bits/code
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
@ -177,15 +187,13 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Encodes and compresses the indexed pixels to the stream.
|
|
|
/// Encodes and compresses the indexed pixels to the stream.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <param name="indexedPixels">The span of indexed pixels.</param>
|
|
|
/// <param name="indexedPixels">The 2D buffer of indexed pixels.</param>
|
|
|
/// <param name="stream">The stream to write to.</param>
|
|
|
/// <param name="stream">The stream to write to.</param>
|
|
|
public void Encode(ReadOnlySpan<byte> indexedPixels, Stream stream) |
|
|
public void Encode(Buffer2D<byte> indexedPixels, Stream stream) |
|
|
{ |
|
|
{ |
|
|
// Write "initial code size" byte
|
|
|
// Write "initial code size" byte
|
|
|
stream.WriteByte((byte)this.initialCodeSize); |
|
|
stream.WriteByte((byte)this.initialCodeSize); |
|
|
|
|
|
|
|
|
this.position = 0; |
|
|
|
|
|
|
|
|
|
|
|
// Compress and write the pixel data
|
|
|
// Compress and write the pixel data
|
|
|
this.Compress(indexedPixels, this.initialCodeSize + 1, stream); |
|
|
this.Compress(indexedPixels, this.initialCodeSize + 1, stream); |
|
|
|
|
|
|
|
|
@ -199,10 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// <param name="bitCount">The number of bits</param>
|
|
|
/// <param name="bitCount">The number of bits</param>
|
|
|
/// <returns>See <see cref="int"/></returns>
|
|
|
/// <returns>See <see cref="int"/></returns>
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
private static int GetMaxcode(int bitCount) |
|
|
private static int GetMaxcode(int bitCount) => (1 << bitCount) - 1; |
|
|
{ |
|
|
|
|
|
return (1 << bitCount) - 1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Add a character to the end of the current packet, and if it is 254 characters,
|
|
|
/// Add a character to the end of the current packet, and if it is 254 characters,
|
|
|
@ -239,25 +244,16 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// Reset the code table.
|
|
|
/// Reset the code table.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
private void ResetCodeTable() |
|
|
private void ResetCodeTable() => this.hashTable.GetSpan().Fill(-1); |
|
|
{ |
|
|
|
|
|
this.hashTable.GetSpan().Fill(-1); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Compress the packets to the stream.
|
|
|
/// Compress the packets to the stream.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <param name="indexedPixels">The span of indexed pixels.</param>
|
|
|
/// <param name="indexedPixels">The 2D buffer of indexed pixels.</param>
|
|
|
/// <param name="initialBits">The initial bits.</param>
|
|
|
/// <param name="initialBits">The initial bits.</param>
|
|
|
/// <param name="stream">The stream to write to.</param>
|
|
|
/// <param name="stream">The stream to write to.</param>
|
|
|
private void Compress(ReadOnlySpan<byte> indexedPixels, int initialBits, Stream stream) |
|
|
private void Compress(Buffer2D<byte> indexedPixels, int initialBits, Stream stream) |
|
|
{ |
|
|
{ |
|
|
int fcode; |
|
|
|
|
|
int c; |
|
|
|
|
|
int ent; |
|
|
|
|
|
int hsizeReg; |
|
|
|
|
|
int hshift; |
|
|
|
|
|
|
|
|
|
|
|
// Set up the globals: globalInitialBits - initial number of bits
|
|
|
// Set up the globals: globalInitialBits - initial number of bits
|
|
|
this.globalInitialBits = initialBits; |
|
|
this.globalInitialBits = initialBits; |
|
|
|
|
|
|
|
|
@ -265,92 +261,82 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
this.clearFlag = false; |
|
|
this.clearFlag = false; |
|
|
this.bitCount = this.globalInitialBits; |
|
|
this.bitCount = this.globalInitialBits; |
|
|
this.maxCode = GetMaxcode(this.bitCount); |
|
|
this.maxCode = GetMaxcode(this.bitCount); |
|
|
|
|
|
|
|
|
this.clearCode = 1 << (initialBits - 1); |
|
|
this.clearCode = 1 << (initialBits - 1); |
|
|
this.eofCode = this.clearCode + 1; |
|
|
this.eofCode = this.clearCode + 1; |
|
|
this.freeEntry = this.clearCode + 2; |
|
|
this.freeEntry = this.clearCode + 2; |
|
|
|
|
|
this.accumulatorCount = 0; // Clear packet
|
|
|
|
|
|
|
|
|
this.accumulatorCount = 0; // clear packet
|
|
|
this.ResetCodeTable(); // Clear hash table
|
|
|
|
|
|
|
|
|
ent = this.NextPixel(indexedPixels); |
|
|
|
|
|
|
|
|
|
|
|
// TODO: PERF: It looks like hshift could be calculated once statically.
|
|
|
|
|
|
hshift = 0; |
|
|
|
|
|
for (fcode = this.hsize; fcode < 65536; fcode *= 2) |
|
|
|
|
|
{ |
|
|
|
|
|
++hshift; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
hshift = 8 - hshift; // set hash code range bound
|
|
|
|
|
|
|
|
|
|
|
|
hsizeReg = this.hsize; |
|
|
|
|
|
|
|
|
|
|
|
this.ResetCodeTable(); // clear hash table
|
|
|
|
|
|
|
|
|
|
|
|
this.Output(this.clearCode, stream); |
|
|
this.Output(this.clearCode, stream); |
|
|
|
|
|
|
|
|
ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan()); |
|
|
ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan()); |
|
|
ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan()); |
|
|
ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan()); |
|
|
|
|
|
|
|
|
while (this.position < indexedPixels.Length) |
|
|
int entry = indexedPixels[0, 0]; |
|
|
{ |
|
|
|
|
|
c = this.NextPixel(indexedPixels); |
|
|
|
|
|
|
|
|
|
|
|
fcode = (c << MaxBits) + ent; |
|
|
for (int y = 0; y < indexedPixels.Height; y++) |
|
|
int i = (c << hshift) ^ ent /* = 0 */; |
|
|
{ |
|
|
|
|
|
ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.GetRowSpan(y)); |
|
|
|
|
|
int offsetX = y == 0 ? 1 : 0; |
|
|
|
|
|
|
|
|
if (Unsafe.Add(ref hashTableRef, i) == fcode) |
|
|
for (int x = offsetX; x < indexedPixels.Width; x++) |
|
|
{ |
|
|
{ |
|
|
ent = Unsafe.Add(ref codeTableRef, i); |
|
|
int code = Unsafe.Add(ref rowSpanRef, x); |
|
|
continue; |
|
|
int freeCode = (code << MaxBits) + entry; |
|
|
} |
|
|
int hashIndex = (code << HashShift) ^ entry; |
|
|
|
|
|
|
|
|
// Non-empty slot
|
|
|
if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) |
|
|
if (Unsafe.Add(ref hashTableRef, i) >= 0) |
|
|
|
|
|
{ |
|
|
|
|
|
int disp = 1; |
|
|
|
|
|
if (i != 0) |
|
|
|
|
|
{ |
|
|
{ |
|
|
disp = hsizeReg - i; |
|
|
entry = Unsafe.Add(ref codeTableRef, hashIndex); |
|
|
|
|
|
continue; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
do |
|
|
// Non-empty slot
|
|
|
|
|
|
if (Unsafe.Add(ref hashTableRef, hashIndex) >= 0) |
|
|
{ |
|
|
{ |
|
|
if ((i -= disp) < 0) |
|
|
int disp = 1; |
|
|
|
|
|
if (hashIndex != 0) |
|
|
|
|
|
{ |
|
|
|
|
|
disp = HashSize - hashIndex; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
do |
|
|
{ |
|
|
{ |
|
|
i += hsizeReg; |
|
|
if ((hashIndex -= disp) < 0) |
|
|
|
|
|
{ |
|
|
|
|
|
hashIndex += HashSize; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) |
|
|
|
|
|
{ |
|
|
|
|
|
entry = Unsafe.Add(ref codeTableRef, hashIndex); |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
while (Unsafe.Add(ref hashTableRef, hashIndex) >= 0); |
|
|
|
|
|
|
|
|
if (Unsafe.Add(ref hashTableRef, i) == fcode) |
|
|
if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) |
|
|
{ |
|
|
{ |
|
|
ent = Unsafe.Add(ref codeTableRef, i); |
|
|
continue; |
|
|
break; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
while (Unsafe.Add(ref hashTableRef, i) >= 0); |
|
|
|
|
|
|
|
|
|
|
|
if (Unsafe.Add(ref hashTableRef, i) == fcode) |
|
|
this.Output(entry, stream); |
|
|
|
|
|
entry = code; |
|
|
|
|
|
if (this.freeEntry < MaxMaxCode) |
|
|
{ |
|
|
{ |
|
|
continue; |
|
|
Unsafe.Add(ref codeTableRef, hashIndex) = this.freeEntry++; // code -> hashtable
|
|
|
|
|
|
Unsafe.Add(ref hashTableRef, hashIndex) = freeCode; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
this.ClearBlock(stream); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.Output(ent, stream); |
|
|
|
|
|
ent = c; |
|
|
|
|
|
if (this.freeEntry < MaxMaxCode) |
|
|
|
|
|
{ |
|
|
|
|
|
Unsafe.Add(ref codeTableRef, i) = this.freeEntry++; // code -> hashtable
|
|
|
|
|
|
Unsafe.Add(ref hashTableRef, i) = fcode; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
this.ClearBlock(stream); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Put out the final code.
|
|
|
// Output the final code.
|
|
|
this.Output(ent, stream); |
|
|
this.Output(entry, stream); |
|
|
|
|
|
|
|
|
this.Output(this.eofCode, stream); |
|
|
this.Output(this.eofCode, stream); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -366,19 +352,6 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
this.accumulatorCount = 0; |
|
|
this.accumulatorCount = 0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Reads the next pixel from the image.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="indexedPixels">The span of indexed pixels.</param>
|
|
|
|
|
|
/// <returns>
|
|
|
|
|
|
/// The <see cref="int"/>
|
|
|
|
|
|
/// </returns>
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
|
|
private int NextPixel(ReadOnlySpan<byte> indexedPixels) |
|
|
|
|
|
{ |
|
|
|
|
|
return indexedPixels[this.position++] & 0xFF; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Output the current code to the stream.
|
|
|
/// Output the current code to the stream.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
|