You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
603 lines
19 KiB
603 lines
19 KiB
/* |
|
UniGif |
|
Copyright (c) 2015 WestHillApps (Hironari Nishioka) |
|
This software is released under the MIT License. |
|
http://opensource.org/licenses/mit-license.php |
|
*/ |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using UnityEngine; |
|
|
|
public static partial class UniGif |
|
{ |
|
/// <summary> |
|
/// Set GIF data |
|
/// </summary> |
|
/// <param name="gifBytes">GIF byte data</param> |
|
/// <param name="gifData">ref GIF data</param> |
|
/// <param name="debugLog">Debug log flag</param> |
|
/// <returns>Result</returns> |
|
private static bool SetGifData(byte[] gifBytes, ref GifData gifData, bool debugLog) |
|
{ |
|
if (debugLog) |
|
{ |
|
Debug.Log("SetGifData Start."); |
|
} |
|
|
|
if (gifBytes == null || gifBytes.Length <= 0) |
|
{ |
|
Debug.LogError("bytes is nothing."); |
|
return false; |
|
} |
|
|
|
int byteIndex = 0; |
|
|
|
if (SetGifHeader(gifBytes, ref byteIndex, ref gifData) == false) |
|
{ |
|
Debug.LogError("GIF header set error."); |
|
return false; |
|
} |
|
|
|
if (SetGifBlock(gifBytes, ref byteIndex, ref gifData) == false) |
|
{ |
|
Debug.LogError("GIF block set error."); |
|
return false; |
|
} |
|
|
|
if (debugLog) |
|
{ |
|
gifData.Dump(); |
|
Debug.Log("SetGifData Finish."); |
|
} |
|
return true; |
|
} |
|
|
|
private static bool SetGifHeader(byte[] gifBytes, ref int byteIndex, ref GifData gifData) |
|
{ |
|
// Signature(3 Bytes) |
|
// 0x47 0x49 0x46 (GIF) |
|
if (gifBytes[0] != 'G' || gifBytes[1] != 'I' || gifBytes[2] != 'F') |
|
{ |
|
Debug.LogError("This is not GIF image."); |
|
return false; |
|
} |
|
gifData.m_sig0 = gifBytes[0]; |
|
gifData.m_sig1 = gifBytes[1]; |
|
gifData.m_sig2 = gifBytes[2]; |
|
|
|
// Version(3 Bytes) |
|
// 0x38 0x37 0x61 (87a) or 0x38 0x39 0x61 (89a) |
|
if ((gifBytes[3] != '8' || gifBytes[4] != '7' || gifBytes[5] != 'a') && |
|
(gifBytes[3] != '8' || gifBytes[4] != '9' || gifBytes[5] != 'a')) |
|
{ |
|
Debug.LogError("GIF version error.\nSupported only GIF87a or GIF89a."); |
|
return false; |
|
} |
|
gifData.m_ver0 = gifBytes[3]; |
|
gifData.m_ver1 = gifBytes[4]; |
|
gifData.m_ver2 = gifBytes[5]; |
|
|
|
// Logical Screen Width(2 Bytes) |
|
gifData.m_logicalScreenWidth = BitConverter.ToUInt16(gifBytes, 6); |
|
|
|
// Logical Screen Height(2 Bytes) |
|
gifData.m_logicalScreenHeight = BitConverter.ToUInt16(gifBytes, 8); |
|
|
|
// 1 Byte |
|
{ |
|
// Global Color Table Flag(1 Bit) |
|
gifData.m_globalColorTableFlag = (gifBytes[10] & 128) == 128; // 0b10000000 |
|
|
|
// Color Resolution(3 Bits) |
|
switch (gifBytes[10] & 112) |
|
{ |
|
case 112: // 0b01110000 |
|
gifData.m_colorResolution = 8; |
|
break; |
|
case 96: // 0b01100000 |
|
gifData.m_colorResolution = 7; |
|
break; |
|
case 80: // 0b01010000 |
|
gifData.m_colorResolution = 6; |
|
break; |
|
case 64: // 0b01000000 |
|
gifData.m_colorResolution = 5; |
|
break; |
|
case 48: // 0b00110000 |
|
gifData.m_colorResolution = 4; |
|
break; |
|
case 32: // 0b00100000 |
|
gifData.m_colorResolution = 3; |
|
break; |
|
case 16: // 0b00010000 |
|
gifData.m_colorResolution = 2; |
|
break; |
|
default: |
|
gifData.m_colorResolution = 1; |
|
break; |
|
} |
|
|
|
// Sort Flag(1 Bit) |
|
gifData.m_sortFlag = (gifBytes[10] & 8) == 8; // 0b00001000 |
|
|
|
// Size of Global Color Table(3 Bits) |
|
int val = (gifBytes[10] & 7) + 1; |
|
gifData.m_sizeOfGlobalColorTable = (int)Math.Pow(2, val); |
|
} |
|
|
|
// Background Color Index(1 Byte) |
|
gifData.m_bgColorIndex = gifBytes[11]; |
|
|
|
// Pixel Aspect Ratio(1 Byte) |
|
gifData.m_pixelAspectRatio = gifBytes[12]; |
|
|
|
byteIndex = 13; |
|
if (gifData.m_globalColorTableFlag) |
|
{ |
|
// Global Color Table(0~255×3 Bytes) |
|
gifData.m_globalColorTable = new List<byte[]>(); |
|
for (int i = byteIndex; i < byteIndex + (gifData.m_sizeOfGlobalColorTable * 3); i += 3) |
|
{ |
|
gifData.m_globalColorTable.Add(new byte[] { gifBytes[i], gifBytes[i + 1], gifBytes[i + 2] }); |
|
} |
|
byteIndex = byteIndex + (gifData.m_sizeOfGlobalColorTable * 3); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
private static bool SetGifBlock(byte[] gifBytes, ref int byteIndex, ref GifData gifData) |
|
{ |
|
try |
|
{ |
|
int lastIndex = 0; |
|
while (true) |
|
{ |
|
int nowIndex = byteIndex; |
|
|
|
if (gifBytes[nowIndex] == 0x2c) |
|
{ |
|
// Image Block(0x2c) |
|
SetImageBlock(gifBytes, ref byteIndex, ref gifData); |
|
|
|
} |
|
else if (gifBytes[nowIndex] == 0x21) |
|
{ |
|
// Extension |
|
switch (gifBytes[nowIndex + 1]) |
|
{ |
|
case 0xf9: |
|
// Graphic Control Extension(0x21 0xf9) |
|
SetGraphicControlExtension(gifBytes, ref byteIndex, ref gifData); |
|
break; |
|
case 0xfe: |
|
// Comment Extension(0x21 0xfe) |
|
SetCommentExtension(gifBytes, ref byteIndex, ref gifData); |
|
break; |
|
case 0x01: |
|
// Plain Text Extension(0x21 0x01) |
|
SetPlainTextExtension(gifBytes, ref byteIndex, ref gifData); |
|
break; |
|
case 0xff: |
|
// Application Extension(0x21 0xff) |
|
SetApplicationExtension(gifBytes, ref byteIndex, ref gifData); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
else if (gifBytes[nowIndex] == 0x3b) |
|
{ |
|
// Trailer(1 Byte) |
|
gifData.m_trailer = gifBytes[byteIndex]; |
|
byteIndex++; |
|
break; |
|
} |
|
|
|
if (lastIndex == nowIndex) |
|
{ |
|
Debug.LogError("Infinite loop error."); |
|
return false; |
|
} |
|
|
|
lastIndex = nowIndex; |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
Debug.LogError(ex.Message); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
private static void SetImageBlock(byte[] gifBytes, ref int byteIndex, ref GifData gifData) |
|
{ |
|
ImageBlock ib = new ImageBlock(); |
|
|
|
// Image Separator(1 Byte) |
|
// 0x2c |
|
ib.m_imageSeparator = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Image Left Position(2 Bytes) |
|
ib.m_imageLeftPosition = BitConverter.ToUInt16(gifBytes, byteIndex); |
|
byteIndex += 2; |
|
|
|
// Image Top Position(2 Bytes) |
|
ib.m_imageTopPosition = BitConverter.ToUInt16(gifBytes, byteIndex); |
|
byteIndex += 2; |
|
|
|
// Image Width(2 Bytes) |
|
ib.m_imageWidth = BitConverter.ToUInt16(gifBytes, byteIndex); |
|
byteIndex += 2; |
|
|
|
// Image Height(2 Bytes) |
|
ib.m_imageHeight = BitConverter.ToUInt16(gifBytes, byteIndex); |
|
byteIndex += 2; |
|
|
|
// 1 Byte |
|
{ |
|
// Local Color Table Flag(1 Bit) |
|
ib.m_localColorTableFlag = (gifBytes[byteIndex] & 128) == 128; // 0b10000000 |
|
|
|
// Interlace Flag(1 Bit) |
|
ib.m_interlaceFlag = (gifBytes[byteIndex] & 64) == 64; // 0b01000000 |
|
|
|
// Sort Flag(1 Bit) |
|
ib.m_sortFlag = (gifBytes[byteIndex] & 32) == 32; // 0b00100000 |
|
|
|
// Reserved(2 Bits) |
|
// Unused |
|
|
|
// Size of Local Color Table(3 Bits) |
|
int val = (gifBytes[byteIndex] & 7) + 1; |
|
ib.m_sizeOfLocalColorTable = (int)Math.Pow(2, val); |
|
|
|
byteIndex++; |
|
} |
|
|
|
if (ib.m_localColorTableFlag) |
|
{ |
|
// Local Color Table(0~255×3 Bytes) |
|
ib.m_localColorTable = new List<byte[]>(); |
|
for (int i = byteIndex; i < byteIndex + (ib.m_sizeOfLocalColorTable * 3); i += 3) |
|
{ |
|
ib.m_localColorTable.Add(new byte[] { gifBytes[i], gifBytes[i + 1], gifBytes[i + 2] }); |
|
} |
|
byteIndex = byteIndex + (ib.m_sizeOfLocalColorTable * 3); |
|
} |
|
|
|
// LZW Minimum Code Size(1 Byte) |
|
ib.m_lzwMinimumCodeSize = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Block Size & Image Data List |
|
while (true) |
|
{ |
|
// Block Size(1 Byte) |
|
byte blockSize = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
if (blockSize == 0x00) |
|
{ |
|
// Block Terminator(1 Byte) |
|
break; |
|
} |
|
|
|
var imageDataBlock = new ImageBlock.ImageDataBlock(); |
|
imageDataBlock.m_blockSize = blockSize; |
|
|
|
// Image Data(? Bytes) |
|
imageDataBlock.m_imageData = new byte[imageDataBlock.m_blockSize]; |
|
for (int i = 0; i < imageDataBlock.m_imageData.Length; i++) |
|
{ |
|
imageDataBlock.m_imageData[i] = gifBytes[byteIndex]; |
|
byteIndex++; |
|
} |
|
|
|
if (ib.m_imageDataList == null) |
|
{ |
|
ib.m_imageDataList = new List<ImageBlock.ImageDataBlock>(); |
|
} |
|
ib.m_imageDataList.Add(imageDataBlock); |
|
} |
|
|
|
if (gifData.m_imageBlockList == null) |
|
{ |
|
gifData.m_imageBlockList = new List<ImageBlock>(); |
|
} |
|
gifData.m_imageBlockList.Add(ib); |
|
} |
|
|
|
private static void SetGraphicControlExtension(byte[] gifBytes, ref int byteIndex, ref GifData gifData) |
|
{ |
|
GraphicControlExtension gcEx = new GraphicControlExtension(); |
|
|
|
// Extension Introducer(1 Byte) |
|
// 0x21 |
|
gcEx.m_extensionIntroducer = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Graphic Control Label(1 Byte) |
|
// 0xf9 |
|
gcEx.m_graphicControlLabel = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Block Size(1 Byte) |
|
// 0x04 |
|
gcEx.m_blockSize = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// 1 Byte |
|
{ |
|
// Reserved(3 Bits) |
|
// Unused |
|
|
|
// Disposal Mothod(3 Bits) |
|
// 0 (No disposal specified) |
|
// 1 (Do not dispose) |
|
// 2 (Restore to background color) |
|
// 3 (Restore to previous) |
|
switch (gifBytes[byteIndex] & 28) |
|
{ // 0b00011100 |
|
case 4: // 0b00000100 |
|
gcEx.m_disposalMethod = 1; |
|
break; |
|
case 8: // 0b00001000 |
|
gcEx.m_disposalMethod = 2; |
|
break; |
|
case 12: // 0b00001100 |
|
gcEx.m_disposalMethod = 3; |
|
break; |
|
default: |
|
gcEx.m_disposalMethod = 0; |
|
break; |
|
} |
|
|
|
// User Input Flag(1 Bit) |
|
// Unknown |
|
|
|
// Transparent Color Flag(1 Bit) |
|
gcEx.m_transparentColorFlag = (gifBytes[byteIndex] & 1) == 1; // 0b00000001 |
|
|
|
byteIndex++; |
|
} |
|
|
|
// Delay Time(2 Bytes) |
|
gcEx.m_delayTime = BitConverter.ToUInt16(gifBytes, byteIndex); |
|
byteIndex += 2; |
|
|
|
// Transparent Color Index(1 Byte) |
|
gcEx.m_transparentColorIndex = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Block Terminator(1 Byte) |
|
gcEx.m_blockTerminator = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
if (gifData.m_graphicCtrlExList == null) |
|
{ |
|
gifData.m_graphicCtrlExList = new List<GraphicControlExtension>(); |
|
} |
|
gifData.m_graphicCtrlExList.Add(gcEx); |
|
} |
|
|
|
private static void SetCommentExtension(byte[] gifBytes, ref int byteIndex, ref GifData gifData) |
|
{ |
|
CommentExtension commentEx = new CommentExtension(); |
|
|
|
// Extension Introducer(1 Byte) |
|
// 0x21 |
|
commentEx.m_extensionIntroducer = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Comment Label(1 Byte) |
|
// 0xfe |
|
commentEx.m_commentLabel = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Block Size & Comment Data List |
|
while (true) |
|
{ |
|
// Block Size(1 Byte) |
|
byte blockSize = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
if (blockSize == 0x00) |
|
{ |
|
// Block Terminator(1 Byte) |
|
break; |
|
} |
|
|
|
var commentDataBlock = new CommentExtension.CommentDataBlock(); |
|
commentDataBlock.m_blockSize = blockSize; |
|
|
|
// Comment Data(n Byte) |
|
commentDataBlock.m_commentData = new byte[commentDataBlock.m_blockSize]; |
|
for (int i = 0; i < commentDataBlock.m_commentData.Length; i++) |
|
{ |
|
commentDataBlock.m_commentData[i] = gifBytes[byteIndex]; |
|
byteIndex++; |
|
} |
|
|
|
if (commentEx.m_commentDataList == null) |
|
{ |
|
commentEx.m_commentDataList = new List<CommentExtension.CommentDataBlock>(); |
|
} |
|
commentEx.m_commentDataList.Add(commentDataBlock); |
|
} |
|
|
|
if (gifData.m_commentExList == null) |
|
{ |
|
gifData.m_commentExList = new List<CommentExtension>(); |
|
} |
|
gifData.m_commentExList.Add(commentEx); |
|
} |
|
|
|
private static void SetPlainTextExtension(byte[] gifBytes, ref int byteIndex, ref GifData gifData) |
|
{ |
|
PlainTextExtension plainTxtEx = new PlainTextExtension(); |
|
|
|
// Extension Introducer(1 Byte) |
|
// 0x21 |
|
plainTxtEx.m_extensionIntroducer = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Plain Text Label(1 Byte) |
|
// 0x01 |
|
plainTxtEx.m_plainTextLabel = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Block Size(1 Byte) |
|
// 0x0c |
|
plainTxtEx.m_blockSize = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Text Grid Left Position(2 Bytes) |
|
// Not supported |
|
byteIndex += 2; |
|
|
|
// Text Grid Top Position(2 Bytes) |
|
// Not supported |
|
byteIndex += 2; |
|
|
|
// Text Grid Width(2 Bytes) |
|
// Not supported |
|
byteIndex += 2; |
|
|
|
// Text Grid Height(2 Bytes) |
|
// Not supported |
|
byteIndex += 2; |
|
|
|
// Character Cell Width(1 Bytes) |
|
// Not supported |
|
byteIndex++; |
|
|
|
// Character Cell Height(1 Bytes) |
|
// Not supported |
|
byteIndex++; |
|
|
|
// Text Foreground Color Index(1 Bytes) |
|
// Not supported |
|
byteIndex++; |
|
|
|
// Text Background Color Index(1 Bytes) |
|
// Not supported |
|
byteIndex++; |
|
|
|
// Block Size & Plain Text Data List |
|
while (true) |
|
{ |
|
// Block Size(1 Byte) |
|
byte blockSize = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
if (blockSize == 0x00) |
|
{ |
|
// Block Terminator(1 Byte) |
|
break; |
|
} |
|
|
|
var plainTextDataBlock = new PlainTextExtension.PlainTextDataBlock(); |
|
plainTextDataBlock.m_blockSize = blockSize; |
|
|
|
// Plain Text Data(n Byte) |
|
plainTextDataBlock.m_plainTextData = new byte[plainTextDataBlock.m_blockSize]; |
|
for (int i = 0; i < plainTextDataBlock.m_plainTextData.Length; i++) |
|
{ |
|
plainTextDataBlock.m_plainTextData[i] = gifBytes[byteIndex]; |
|
byteIndex++; |
|
} |
|
|
|
if (plainTxtEx.m_plainTextDataList == null) |
|
{ |
|
plainTxtEx.m_plainTextDataList = new List<PlainTextExtension.PlainTextDataBlock>(); |
|
} |
|
plainTxtEx.m_plainTextDataList.Add(plainTextDataBlock); |
|
} |
|
|
|
if (gifData.m_plainTextExList == null) |
|
{ |
|
gifData.m_plainTextExList = new List<PlainTextExtension>(); |
|
} |
|
gifData.m_plainTextExList.Add(plainTxtEx); |
|
} |
|
|
|
private static void SetApplicationExtension(byte[] gifBytes, ref int byteIndex, ref GifData gifData) |
|
{ |
|
// Extension Introducer(1 Byte) |
|
// 0x21 |
|
gifData.m_appEx.m_extensionIntroducer = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Extension Label(1 Byte) |
|
// 0xff |
|
gifData.m_appEx.m_extensionLabel = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Block Size(1 Byte) |
|
// 0x0b |
|
gifData.m_appEx.m_blockSize = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Application Identifier(8 Bytes) |
|
gifData.m_appEx.m_appId1 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
gifData.m_appEx.m_appId2 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
gifData.m_appEx.m_appId3 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
gifData.m_appEx.m_appId4 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
gifData.m_appEx.m_appId5 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
gifData.m_appEx.m_appId6 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
gifData.m_appEx.m_appId7 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
gifData.m_appEx.m_appId8 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Application Authentication Code(3 Bytes) |
|
gifData.m_appEx.m_appAuthCode1 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
gifData.m_appEx.m_appAuthCode2 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
gifData.m_appEx.m_appAuthCode3 = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
// Block Size & Application Data List |
|
while (true) |
|
{ |
|
// Block Size (1 Byte) |
|
byte blockSize = gifBytes[byteIndex]; |
|
byteIndex++; |
|
|
|
if (blockSize == 0x00) |
|
{ |
|
// Block Terminator(1 Byte) |
|
break; |
|
} |
|
|
|
var appDataBlock = new ApplicationExtension.ApplicationDataBlock(); |
|
appDataBlock.m_blockSize = blockSize; |
|
|
|
// Application Data(n Byte) |
|
appDataBlock.m_applicationData = new byte[appDataBlock.m_blockSize]; |
|
for (int i = 0; i < appDataBlock.m_applicationData.Length; i++) |
|
{ |
|
appDataBlock.m_applicationData[i] = gifBytes[byteIndex]; |
|
byteIndex++; |
|
} |
|
|
|
if (gifData.m_appEx.m_appDataList == null) |
|
{ |
|
gifData.m_appEx.m_appDataList = new List<ApplicationExtension.ApplicationDataBlock>(); |
|
} |
|
gifData.m_appEx.m_appDataList.Add(appDataBlock); |
|
} |
|
} |
|
}
|
|
|