chicken_dy/Assets/WX-WASM-SDK/UnityMarkdownViewer/Editor/Scripts/mgGif.cs

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);
}
}
}