1094 lines
34 KiB
C#
Executable File
1094 lines
34 KiB
C#
Executable File
//#define mgGIF_UNSAFE
|
|
|
|
using UnityEngine;
|
|
using System;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using System.Runtime.InteropServices; // unsafe
|
|
|
|
namespace MG.GIF
|
|
{
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
public class Image : ICloneable
|
|
{
|
|
public int Width;
|
|
public int Height;
|
|
public int Delay; // milliseconds
|
|
public Color32[] RawImage;
|
|
|
|
public Image()
|
|
{
|
|
}
|
|
|
|
public Image( Image img )
|
|
{
|
|
Width = img.Width;
|
|
Height = img.Height;
|
|
Delay = img.Delay;
|
|
RawImage = img.RawImage != null ? (Color32[]) img.RawImage.Clone() : null;
|
|
}
|
|
|
|
public object Clone()
|
|
{
|
|
return new Image( this );
|
|
}
|
|
|
|
public Texture2D CreateTexture()
|
|
{
|
|
var tex = new Texture2D( Width, Height, TextureFormat.ARGB32, false )
|
|
{
|
|
filterMode = FilterMode.Point,
|
|
wrapMode = TextureWrapMode.Clamp
|
|
};
|
|
|
|
tex.SetPixels32( RawImage );
|
|
tex.Apply();
|
|
|
|
return tex;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#if mgGIF_UNSAFE
|
|
unsafe
|
|
#endif
|
|
public class Decoder : IDisposable
|
|
{
|
|
public string Version;
|
|
public ushort Width;
|
|
public ushort Height;
|
|
public Color32 BackgroundColour;
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// GIF format enums
|
|
|
|
[Flags]
|
|
enum ImageFlag
|
|
{
|
|
Interlaced = 0x40,
|
|
ColourTable = 0x80,
|
|
TableSizeMask = 0x07,
|
|
BitDepthMask = 0x70,
|
|
}
|
|
|
|
enum Block
|
|
{
|
|
Image = 0x2C,
|
|
Extension = 0x21,
|
|
End = 0x3B
|
|
}
|
|
|
|
enum Extension
|
|
{
|
|
GraphicControl = 0xF9,
|
|
Comments = 0xFE,
|
|
PlainText = 0x01,
|
|
ApplicationData = 0xFF
|
|
}
|
|
|
|
enum Disposal
|
|
{
|
|
None = 0x00,
|
|
DoNotDispose = 0x04,
|
|
RestoreBackground = 0x08,
|
|
ReturnToPrevious = 0x0C
|
|
}
|
|
|
|
[Flags]
|
|
enum ControlFlags
|
|
{
|
|
HasTransparency = 0x01,
|
|
DisposalMask = 0x0C
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
const uint NoCode = 0xFFFF;
|
|
const ushort NoTransparency = 0xFFFF;
|
|
|
|
// input stream to decode
|
|
byte[] Input;
|
|
int D;
|
|
|
|
// colour table
|
|
Color32[] GlobalColourTable;
|
|
Color32[] LocalColourTable;
|
|
Color32[] ActiveColourTable;
|
|
ushort TransparentIndex;
|
|
|
|
// current image
|
|
Image Image = new Image();
|
|
ushort ImageLeft;
|
|
ushort ImageTop;
|
|
ushort ImageWidth;
|
|
ushort ImageHeight;
|
|
|
|
Color32[] Output;
|
|
Color32[] PreviousImage;
|
|
|
|
readonly int[] Pow2 = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 };
|
|
|
|
//------------------------------------------------------------------------------
|
|
// ctor
|
|
|
|
public Decoder( byte[] data )
|
|
: this()
|
|
{
|
|
Load( data );
|
|
}
|
|
|
|
public Decoder Load( byte[] data )
|
|
{
|
|
Input = data;
|
|
D = 0;
|
|
|
|
GlobalColourTable = new Color32[ 256 ];
|
|
LocalColourTable = new Color32[ 256 ];
|
|
TransparentIndex = NoTransparency;
|
|
Output = null;
|
|
PreviousImage = null;
|
|
|
|
Image.Delay = 0;
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// reading data utility functions
|
|
|
|
[MethodImpl( MethodImplOptions.AggressiveInlining )]
|
|
byte ReadByte()
|
|
{
|
|
return Input[ D++ ];
|
|
}
|
|
|
|
[MethodImpl( MethodImplOptions.AggressiveInlining )]
|
|
ushort ReadUInt16()
|
|
{
|
|
return (ushort) ( Input[ D++ ] | Input[ D++ ] << 8 );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void ReadHeader()
|
|
{
|
|
if( Input == null || Input.Length <= 12 )
|
|
{
|
|
throw new Exception( "Invalid data" );
|
|
}
|
|
|
|
// signature
|
|
|
|
Version = Encoding.ASCII.GetString( Input, 0, 6 );
|
|
D = 6;
|
|
|
|
if( Version != "GIF87a" && Version != "GIF89a" )
|
|
{
|
|
throw new Exception( "Unsupported GIF version" );
|
|
}
|
|
|
|
// read header
|
|
|
|
Width = ReadUInt16();
|
|
Height = ReadUInt16();
|
|
|
|
Image.Width = Width;
|
|
Image.Height = Height;
|
|
|
|
var flags = (ImageFlag) ReadByte();
|
|
var bgIndex = ReadByte(); // background colour
|
|
|
|
ReadByte(); // aspect ratio
|
|
|
|
if( flags.HasFlag( ImageFlag.ColourTable ) )
|
|
{
|
|
ReadColourTable( GlobalColourTable, flags );
|
|
}
|
|
|
|
BackgroundColour = GlobalColourTable[ bgIndex ];
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
public Image NextImage()
|
|
{
|
|
// if at start of data, read header
|
|
|
|
if( D == 0 )
|
|
{
|
|
ReadHeader();
|
|
}
|
|
|
|
// read blocks until we find an image block
|
|
|
|
while( true )
|
|
{
|
|
var block = (Block) ReadByte();
|
|
|
|
switch( block )
|
|
{
|
|
case Block.Image:
|
|
{
|
|
// return the image if we got one
|
|
|
|
var img = ReadImageBlock();
|
|
|
|
if( img != null )
|
|
{
|
|
return img;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Block.Extension:
|
|
{
|
|
var ext = (Extension) ReadByte();
|
|
|
|
if( ext == Extension.GraphicControl )
|
|
{
|
|
ReadControlBlock();
|
|
}
|
|
else
|
|
{
|
|
SkipBlocks();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Block.End:
|
|
{
|
|
// end block - stop!
|
|
return null;
|
|
}
|
|
|
|
default:
|
|
{
|
|
throw new Exception( "Unexpected block type" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
Color32[] ReadColourTable( Color32[] colourTable, ImageFlag flags )
|
|
{
|
|
var tableSize = Pow2[ (int)( flags & ImageFlag.TableSizeMask ) + 1 ];
|
|
|
|
for( var i = 0; i < tableSize; i++ )
|
|
{
|
|
colourTable[ i ] = new Color32(
|
|
Input[ D++ ],
|
|
Input[ D++ ],
|
|
Input[ D++ ],
|
|
0xFF
|
|
);
|
|
}
|
|
|
|
return colourTable;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void SkipBlocks()
|
|
{
|
|
var blockSize = Input[ D++ ];
|
|
|
|
while( blockSize != 0x00 )
|
|
{
|
|
D += blockSize;
|
|
blockSize = Input[ D++ ];
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void ReadControlBlock()
|
|
{
|
|
// read block
|
|
|
|
ReadByte(); // block size (0x04)
|
|
var flags = (ControlFlags) ReadByte(); // flags
|
|
Image.Delay = ReadUInt16() * 10; // delay (1/100th -> milliseconds)
|
|
var transparentColour = ReadByte(); // transparent colour
|
|
ReadByte(); // terminator (0x00)
|
|
|
|
// has transparent colour?
|
|
|
|
if( flags.HasFlag( ControlFlags.HasTransparency ) )
|
|
{
|
|
TransparentIndex = transparentColour;
|
|
}
|
|
else
|
|
{
|
|
TransparentIndex = NoTransparency;
|
|
}
|
|
|
|
// dispose of current image
|
|
|
|
switch( (Disposal)( flags & ControlFlags.DisposalMask ) )
|
|
{
|
|
default:
|
|
case Disposal.None:
|
|
case Disposal.DoNotDispose:
|
|
// remember current image in case we need to "return to previous"
|
|
PreviousImage = Output;
|
|
break;
|
|
|
|
case Disposal.RestoreBackground:
|
|
// empty image - don't track
|
|
Output = new Color32[ Width * Height ];
|
|
break;
|
|
|
|
case Disposal.ReturnToPrevious:
|
|
|
|
// return to previous image
|
|
|
|
Output = new Color32[ Width * Height ];
|
|
|
|
if( PreviousImage != null )
|
|
{
|
|
Array.Copy( PreviousImage, Output, Output.Length );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
Image ReadImageBlock()
|
|
{
|
|
// read image block header
|
|
|
|
ImageLeft = ReadUInt16();
|
|
ImageTop = ReadUInt16();
|
|
ImageWidth = ReadUInt16();
|
|
ImageHeight = ReadUInt16();
|
|
var flags = (ImageFlag) ReadByte();
|
|
|
|
// bad image if we don't have any dimensions
|
|
|
|
if( ImageWidth == 0 || ImageHeight == 0 )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// read colour table
|
|
|
|
if( flags.HasFlag( ImageFlag.ColourTable ) )
|
|
{
|
|
ActiveColourTable = ReadColourTable( LocalColourTable, flags );
|
|
}
|
|
else
|
|
{
|
|
ActiveColourTable = GlobalColourTable;
|
|
}
|
|
|
|
if( Output == null )
|
|
{
|
|
Output = new Color32[ Width * Height ];
|
|
PreviousImage = Output;
|
|
}
|
|
|
|
// read image data
|
|
|
|
DecompressLZW();
|
|
|
|
// deinterlace
|
|
|
|
if( flags.HasFlag( ImageFlag.Interlaced ) )
|
|
{
|
|
Deinterlace();
|
|
}
|
|
|
|
// return image
|
|
|
|
Image.RawImage = Output;
|
|
return Image;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// decode interlaced images
|
|
|
|
void Deinterlace()
|
|
{
|
|
var numRows = Output.Length / Width;
|
|
var writePos = Output.Length - Width; // NB: work backwards due to Y-coord flip
|
|
var input = Output;
|
|
|
|
Output = new Color32[ Output.Length ];
|
|
|
|
for( var row = 0; row < numRows; row++ )
|
|
{
|
|
int copyRow;
|
|
|
|
// every 8th row starting at 0
|
|
if( row % 8 == 0 )
|
|
{
|
|
copyRow = row / 8;
|
|
}
|
|
// every 8th row starting at 4
|
|
else if( ( row + 4 ) % 8 == 0 )
|
|
{
|
|
var o = numRows / 8;
|
|
copyRow = o + ( row - 4 ) / 8;
|
|
}
|
|
// every 4th row starting at 2
|
|
else if( ( row + 2 ) % 4 == 0 )
|
|
{
|
|
var o = numRows / 4;
|
|
copyRow = o + ( row - 2 ) / 4;
|
|
}
|
|
// every 2nd row starting at 1
|
|
else // if( ( r + 1 ) % 2 == 0 )
|
|
{
|
|
var o = numRows / 2;
|
|
copyRow = o + ( row - 1 ) / 2;
|
|
}
|
|
|
|
Array.Copy( input, ( numRows - copyRow - 1 ) * Width, Output, writePos, Width );
|
|
|
|
writePos -= Width;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// DecompressLZW()
|
|
|
|
#if mgGIF_UNSAFE
|
|
|
|
bool Disposed = false;
|
|
|
|
int CodesLength;
|
|
IntPtr CodesHandle;
|
|
ushort* pCodes;
|
|
|
|
IntPtr CurBlock;
|
|
uint* pCurBlock;
|
|
|
|
const int MaxCodes = 4096;
|
|
IntPtr Indices;
|
|
ushort** pIndicies;
|
|
|
|
public Decoder()
|
|
{
|
|
// unmanaged allocations
|
|
|
|
CodesLength = 128 * 1024;
|
|
CodesHandle = Marshal.AllocHGlobal( CodesLength * sizeof( ushort ) );
|
|
pCodes = (ushort*) CodesHandle.ToPointer();
|
|
|
|
CurBlock = Marshal.AllocHGlobal( 64 * sizeof( uint ) );
|
|
pCurBlock = (uint*) CurBlock.ToPointer();
|
|
|
|
Indices = Marshal.AllocHGlobal( MaxCodes * sizeof( ushort* ) );
|
|
pIndicies = (ushort**) Indices.ToPointer();
|
|
}
|
|
|
|
protected virtual void Dispose( bool disposing )
|
|
{
|
|
if( Disposed )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// release unmanaged resources
|
|
|
|
Marshal.FreeHGlobal( CodesHandle );
|
|
Marshal.FreeHGlobal( CurBlock );
|
|
Marshal.FreeHGlobal( Indices );
|
|
|
|
Disposed = true;
|
|
}
|
|
|
|
~Decoder()
|
|
{
|
|
Dispose( false );
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose( true );
|
|
GC.SuppressFinalize( this );
|
|
}
|
|
|
|
void DecompressLZW()
|
|
{
|
|
var pCodeBufferEnd = pCodes + CodesLength;
|
|
|
|
fixed( byte* pData = Input )
|
|
{
|
|
fixed( Color32* pOutput = Output, pColourTable = ActiveColourTable )
|
|
{
|
|
var row = ( Height - ImageTop - 1 ) * Width; // start at end of array as we are reversing the row order
|
|
var safeWidth = ImageLeft + ImageWidth > Width ? Width - ImageLeft : ImageWidth;
|
|
|
|
var pWrite = &pOutput[ row + ImageLeft ];
|
|
var pRow = pWrite;
|
|
var pRowEnd = pWrite + ImageWidth;
|
|
var pImageEnd = pWrite + safeWidth;
|
|
|
|
// setup codes
|
|
|
|
int minimumCodeSize = Input[ D++ ];
|
|
|
|
if( minimumCodeSize > 11 )
|
|
{
|
|
minimumCodeSize = 11;
|
|
}
|
|
|
|
var codeSize = minimumCodeSize + 1;
|
|
var nextSize = Pow2[ codeSize ];
|
|
var maximumCodeSize = Pow2[ minimumCodeSize ];
|
|
var clearCode = maximumCodeSize;
|
|
var endCode = maximumCodeSize + 1;
|
|
|
|
// initialise buffers
|
|
|
|
var numCodes = maximumCodeSize + 2;
|
|
var pCodesEnd = pCodes;
|
|
|
|
for( ushort i = 0; i < numCodes; i++ )
|
|
{
|
|
pIndicies[ i ] = pCodesEnd;
|
|
*pCodesEnd++ = 1;
|
|
*pCodesEnd++ = i;
|
|
}
|
|
|
|
// LZW decode loop
|
|
|
|
uint previousCode = NoCode; // last code processed
|
|
uint mask = (uint) ( nextSize - 1 ); // mask out code bits
|
|
uint shiftRegister = 0; // shift register holds the bytes coming in from the input stream, we shift down by the number of bits
|
|
|
|
int bitsAvailable = 0; // number of bits available to read in the shift register
|
|
int bytesAvailable = 0; // number of bytes left in current block
|
|
|
|
uint* pD = pCurBlock; // pointer to next bits in current block
|
|
|
|
while( true )
|
|
{
|
|
// get next code
|
|
|
|
uint curCode = shiftRegister & mask;
|
|
|
|
// did we read enough bits?
|
|
|
|
if( bitsAvailable >= codeSize )
|
|
{
|
|
// we had enough bits in the shift register so shunt it down
|
|
bitsAvailable -= codeSize;
|
|
shiftRegister >>= codeSize;
|
|
}
|
|
else
|
|
{
|
|
// not enough bits in register, so get more
|
|
|
|
// if start of new block
|
|
|
|
if( bytesAvailable <= 0 )
|
|
{
|
|
// read blocksize
|
|
|
|
var pBlock = &pData[ D++ ];
|
|
bytesAvailable = *pBlock++;
|
|
D += bytesAvailable;
|
|
|
|
// exit if end of stream
|
|
|
|
if( bytesAvailable == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// copy block into buffer
|
|
|
|
pCurBlock[ ( bytesAvailable - 1 ) / 4 ] = 0; // zero last entry
|
|
Buffer.MemoryCopy( pBlock, pCurBlock, 256, bytesAvailable );
|
|
|
|
// reset data pointer
|
|
pD = pCurBlock;
|
|
}
|
|
|
|
// load shift register from data pointer
|
|
|
|
shiftRegister = *pD++;
|
|
int newBits = bytesAvailable >= 4 ? 32 : bytesAvailable * 8;
|
|
bytesAvailable -= 4;
|
|
|
|
// read remaining bits
|
|
|
|
if( bitsAvailable > 0 )
|
|
{
|
|
var bitsRemaining = codeSize - bitsAvailable;
|
|
curCode |= ( shiftRegister << bitsAvailable ) & mask;
|
|
shiftRegister >>= bitsRemaining;
|
|
bitsAvailable = newBits - bitsRemaining;
|
|
}
|
|
else
|
|
{
|
|
curCode = shiftRegister & mask;
|
|
shiftRegister >>= codeSize;
|
|
bitsAvailable = newBits - codeSize;
|
|
}
|
|
}
|
|
|
|
// process code
|
|
|
|
if( curCode == clearCode )
|
|
{
|
|
// reset codes
|
|
codeSize = minimumCodeSize + 1;
|
|
nextSize = Pow2[ codeSize ];
|
|
numCodes = maximumCodeSize + 2;
|
|
|
|
// reset buffer write pos
|
|
pCodesEnd = &pCodes[ numCodes * 2 ];
|
|
|
|
// clear previous code
|
|
previousCode = NoCode;
|
|
mask = (uint)( nextSize - 1 );
|
|
|
|
continue;
|
|
}
|
|
else if( curCode == endCode )
|
|
{
|
|
// stop
|
|
break;
|
|
}
|
|
|
|
bool plusOne = false;
|
|
ushort* pCodePos = null;
|
|
|
|
if( curCode < numCodes )
|
|
{
|
|
// write existing code
|
|
pCodePos = pIndicies[ curCode ];
|
|
}
|
|
else if( previousCode != NoCode )
|
|
{
|
|
// write previous code
|
|
pCodePos = pIndicies[ previousCode ];
|
|
plusOne = true;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
// output colours
|
|
|
|
var codeLength = *pCodePos++;
|
|
var newCode = *pCodePos;
|
|
var pEnd = pCodePos + codeLength;
|
|
|
|
do
|
|
{
|
|
var code = *pCodePos++;
|
|
|
|
if( code != TransparentIndex && pWrite < pImageEnd )
|
|
{
|
|
*pWrite = pColourTable[ code ];
|
|
}
|
|
|
|
if( ++pWrite == pRowEnd )
|
|
{
|
|
pRow -= Width;
|
|
pWrite = pRow;
|
|
pRowEnd = pRow + ImageWidth;
|
|
pImageEnd = pRow + safeWidth;
|
|
|
|
if( pWrite < pOutput )
|
|
{
|
|
SkipBlocks();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
while( pCodePos < pEnd );
|
|
|
|
if( plusOne )
|
|
{
|
|
if( newCode != TransparentIndex && pWrite < pImageEnd )
|
|
{
|
|
*pWrite = pColourTable[ newCode ];
|
|
}
|
|
|
|
if( ++pWrite == pRowEnd )
|
|
{
|
|
pRow -= Width;
|
|
pWrite = pRow;
|
|
pRowEnd = pRow + ImageWidth;
|
|
pImageEnd = pRow + safeWidth;
|
|
|
|
if( pWrite < pOutput )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// create new code
|
|
|
|
if( previousCode != NoCode && numCodes != MaxCodes )
|
|
{
|
|
// get previous code from buffer
|
|
|
|
pCodePos = pIndicies[ previousCode ];
|
|
codeLength = *pCodePos++;
|
|
|
|
// resize buffer if required (should be rare)
|
|
|
|
if( pCodesEnd + codeLength + 1 >= pCodeBufferEnd )
|
|
{
|
|
var pBase = pCodes;
|
|
|
|
// realloc buffer
|
|
CodesLength *= 2;
|
|
CodesHandle = Marshal.ReAllocHGlobal( CodesHandle, (IntPtr)( CodesLength * sizeof( ushort ) ) );
|
|
|
|
pCodes = (ushort*) CodesHandle.ToPointer();
|
|
pCodeBufferEnd = pCodes + CodesLength;
|
|
|
|
// rebase pointers
|
|
|
|
pCodesEnd = pCodes + ( pCodesEnd - pBase );
|
|
|
|
for( int i=0; i < numCodes; i++ )
|
|
{
|
|
pIndicies[ i ] = pCodes + ( pIndicies[ i ] - pBase );
|
|
}
|
|
|
|
pCodePos = pIndicies[ previousCode ];
|
|
pCodePos++;
|
|
}
|
|
|
|
// add new code
|
|
|
|
pIndicies[ numCodes++ ] = pCodesEnd;
|
|
*pCodesEnd++ = (ushort)( codeLength + 1 );
|
|
|
|
// copy previous code sequence
|
|
|
|
Buffer.MemoryCopy( pCodePos, pCodesEnd, codeLength * sizeof( ushort ), codeLength * sizeof( ushort ) );
|
|
pCodesEnd += codeLength;
|
|
|
|
// append new code
|
|
|
|
*pCodesEnd++ = newCode;
|
|
}
|
|
|
|
// increase code size?
|
|
|
|
if( numCodes >= nextSize && codeSize < 12 )
|
|
{
|
|
nextSize = Pow2[ ++codeSize ];
|
|
mask = (uint)( nextSize - 1 );
|
|
}
|
|
|
|
// remember last code processed
|
|
previousCode = curCode;
|
|
}
|
|
|
|
// consume any remaining blocks
|
|
SkipBlocks();
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
// dispose isn't needed for the safe implementation but keep here for interface parity
|
|
|
|
public Decoder() { }
|
|
public void Dispose() { Dispose( true ); }
|
|
protected virtual void Dispose( bool disposing ) { }
|
|
|
|
|
|
int[] Indices = new int[ 4096 ];
|
|
ushort[] Codes = new ushort[ 128 * 1024 ];
|
|
uint[] CurBlock = new uint[ 64 ];
|
|
|
|
void DecompressLZW()
|
|
{
|
|
// output write position
|
|
|
|
int row = ( Height - ImageTop - 1 ) * Width; // reverse rows for unity texture coords
|
|
int col = ImageLeft;
|
|
int rightEdge = ImageLeft + ImageWidth;
|
|
|
|
// setup codes
|
|
|
|
int minimumCodeSize = Input[ D++ ];
|
|
|
|
if( minimumCodeSize > 11 )
|
|
{
|
|
minimumCodeSize = 11;
|
|
}
|
|
|
|
var codeSize = minimumCodeSize + 1;
|
|
var nextSize = Pow2[ codeSize ];
|
|
var maximumCodeSize = Pow2[ minimumCodeSize ];
|
|
var clearCode = maximumCodeSize;
|
|
var endCode = maximumCodeSize + 1;
|
|
|
|
// initialise buffers
|
|
|
|
var codesEnd = 0;
|
|
var numCodes = maximumCodeSize + 2;
|
|
|
|
for( ushort i = 0; i < numCodes; i++ )
|
|
{
|
|
Indices[ i ] = codesEnd;
|
|
Codes[ codesEnd++ ] = 1; // length
|
|
Codes[ codesEnd++ ] = i; // code
|
|
}
|
|
|
|
// LZW decode loop
|
|
|
|
uint previousCode = NoCode; // last code processed
|
|
uint mask = (uint) ( nextSize - 1 ); // mask out code bits
|
|
uint shiftRegister = 0; // shift register holds the bytes coming in from the input stream, we shift down by the number of bits
|
|
|
|
int bitsAvailable = 0; // number of bits available to read in the shift register
|
|
int bytesAvailable = 0; // number of bytes left in current block
|
|
|
|
int blockPos = 0;
|
|
|
|
while( true )
|
|
{
|
|
// get next code
|
|
|
|
uint curCode = shiftRegister & mask;
|
|
|
|
if( bitsAvailable >= codeSize )
|
|
{
|
|
bitsAvailable -= codeSize;
|
|
shiftRegister >>= codeSize;
|
|
}
|
|
else
|
|
{
|
|
// reload shift register
|
|
|
|
|
|
// if start of new block
|
|
|
|
if( bytesAvailable <= 0 )
|
|
{
|
|
// read blocksize
|
|
bytesAvailable = Input[ D++ ];
|
|
|
|
// exit if end of stream
|
|
if( bytesAvailable == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// read block
|
|
CurBlock[ ( bytesAvailable - 1 ) / 4 ] = 0; // zero last entry
|
|
Buffer.BlockCopy( Input, D, CurBlock, 0, bytesAvailable );
|
|
blockPos = 0;
|
|
D += bytesAvailable;
|
|
}
|
|
|
|
// load shift register
|
|
|
|
shiftRegister = CurBlock[ blockPos++ ];
|
|
int newBits = bytesAvailable >= 4 ? 32 : bytesAvailable * 8;
|
|
bytesAvailable -= 4;
|
|
|
|
// read remaining bits
|
|
|
|
if( bitsAvailable > 0 )
|
|
{
|
|
var bitsRemaining = codeSize - bitsAvailable;
|
|
curCode |= ( shiftRegister << bitsAvailable ) & mask;
|
|
shiftRegister >>= bitsRemaining;
|
|
bitsAvailable = newBits - bitsRemaining;
|
|
}
|
|
else
|
|
{
|
|
curCode = shiftRegister & mask;
|
|
shiftRegister >>= codeSize;
|
|
bitsAvailable = newBits - codeSize;
|
|
}
|
|
}
|
|
|
|
// process code
|
|
|
|
if( curCode == clearCode )
|
|
{
|
|
// reset codes
|
|
codeSize = minimumCodeSize + 1;
|
|
nextSize = Pow2[ codeSize ];
|
|
numCodes = maximumCodeSize + 2;
|
|
|
|
// reset buffer write pos
|
|
codesEnd = numCodes * 2;
|
|
|
|
// clear previous code
|
|
previousCode = NoCode;
|
|
mask = (uint) ( nextSize - 1 );
|
|
|
|
continue;
|
|
}
|
|
else if( curCode == endCode )
|
|
{
|
|
// stop
|
|
break;
|
|
}
|
|
|
|
bool plusOne = false;
|
|
int codePos = 0;
|
|
|
|
if( curCode < numCodes )
|
|
{
|
|
// write existing code
|
|
codePos = Indices[ curCode ];
|
|
}
|
|
else if( previousCode != NoCode )
|
|
{
|
|
// write previous code
|
|
codePos = Indices[ previousCode ];
|
|
plusOne = true;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
// output colours
|
|
|
|
var codeLength = Codes[ codePos++ ];
|
|
var newCode = Codes[ codePos ];
|
|
|
|
for( int i = 0; i < codeLength; i++ )
|
|
{
|
|
var code = Codes[ codePos++ ];
|
|
|
|
if( code != TransparentIndex && col < Width )
|
|
{
|
|
Output[ row + col ] = ActiveColourTable[ code ];
|
|
}
|
|
|
|
if( ++col == rightEdge )
|
|
{
|
|
col = ImageLeft;
|
|
row -= Width;
|
|
|
|
if( row < 0 )
|
|
{
|
|
SkipBlocks();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( plusOne )
|
|
{
|
|
if( newCode != TransparentIndex && col < Width )
|
|
{
|
|
Output[ row + col ] = ActiveColourTable[ newCode ];
|
|
}
|
|
|
|
if( ++col == rightEdge )
|
|
{
|
|
col = ImageLeft;
|
|
row -= Width;
|
|
|
|
if( row < 0 )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// create new code
|
|
|
|
if( previousCode != NoCode && numCodes != Indices.Length )
|
|
{
|
|
// get previous code from buffer
|
|
|
|
codePos = Indices[ previousCode ];
|
|
codeLength = Codes[ codePos++ ];
|
|
|
|
// resize buffer if required (should be rare)
|
|
|
|
if( codesEnd + codeLength + 1 >= Codes.Length )
|
|
{
|
|
Array.Resize( ref Codes, Codes.Length * 2 );
|
|
}
|
|
|
|
// add new code
|
|
|
|
Indices[ numCodes++ ] = codesEnd;
|
|
Codes[ codesEnd++ ] = (ushort) ( codeLength + 1 );
|
|
|
|
// copy previous code sequence
|
|
|
|
var stop = codesEnd + codeLength;
|
|
|
|
while( codesEnd < stop )
|
|
{
|
|
Codes[ codesEnd++ ] = Codes[ codePos++ ];
|
|
}
|
|
|
|
// append new code
|
|
|
|
Codes[ codesEnd++ ] = newCode;
|
|
}
|
|
|
|
// increase code size?
|
|
|
|
if( numCodes >= nextSize && codeSize < 12 )
|
|
{
|
|
nextSize = Pow2[ ++codeSize ];
|
|
mask = (uint) ( nextSize - 1 );
|
|
}
|
|
|
|
// remember last code processed
|
|
previousCode = curCode;
|
|
}
|
|
|
|
// skip any remaining blocks
|
|
SkipBlocks();
|
|
}
|
|
#endif // mgGIF_UNSAFE
|
|
|
|
public static string Ident()
|
|
{
|
|
var v = "1.1";
|
|
var e = BitConverter.IsLittleEndian ? "L" : "B";
|
|
|
|
#if ENABLE_IL2CPP
|
|
var b = "N";
|
|
#else
|
|
var b = "M";
|
|
#endif
|
|
|
|
#if mgGIF_UNSAFE
|
|
var s = "U";
|
|
#else
|
|
var s = "S";
|
|
#endif
|
|
|
|
#if NET_4_6
|
|
var n = "4.x";
|
|
#else
|
|
var n = "2.0";
|
|
#endif
|
|
|
|
return String.Format("{0} {1}{2}{3} {4}", v, e, s, b, n);
|
|
}
|
|
}
|
|
}
|
|
|