Python |GIF 解析與構建(2):狀態機解析
目錄
Python |GIF 解析與構建(2):狀態機解析
引言
一、狀態機概述
狀態機的優勢與改進方向
總結
引言
在《Python |GIF 解析與構建(1):初步解析》中,我們初步進行解析gif文件。
本文將深入探討如何使用 ** 狀態機(State Machine)** 實現 GIF 文件的解析,通過分階段處理數據流,逐步提取關鍵信息(如版本、顏色表、圖像數據等)。狀態機的優勢在于將復雜的解析邏輯拆解為多個可管理的狀態,每個狀態專注于處理特定的數據流片段,從而提高代碼的可讀性和可維護性。
一、狀態機概述
GIF 文件由一系列區塊(Block)組成,每個區塊包含特定類型的數據(如文件頭、顏色表、圖像數據、擴展指令等)。狀態機的核心思想是:根據當前解析進度,將程序劃分為多個狀態(State),每個狀態負責解析某一類區塊或數據片段,并根據輸入數據決定下一步跳轉的狀態。
GIF 解析的主要狀態包括:
- 開頭(Header):驗證 GIF 文件簽名(
GIF87a
或GIF89a
)。 - 邏輯屏幕標識符(Logical Screen Descriptor):解析畫布尺寸、顏色表標志等基礎信息。
- 全局顏色表(Global Color Table):提取全局調色板(若存在)。
- 區塊檢測(Block Detection):識別后續區塊類型(如圖像標識符、擴展塊等)。
- 擴展塊處理(Extension Blocks):解析圖形控制擴展(如動畫延遲)、應用程序擴展(如循環參數)。
- 圖像標識符(Image Descriptor):解析圖像位置、尺寸、局部顏色表(若存在)及壓縮數據。
- 結束符檢測(Trailer Detection):驗證文件結束符(
0x3B
)。
狀態機的優勢與改進方向
- 優勢:
- 模塊化:每個狀態職責單一,便于調試和擴展。
- 容錯性:通過狀態轉移控制數據流,可優雅處理無效數據(如提前終止解析)。
- 改進方向:
- 支持更多擴展塊類型(如注釋擴展、文本擴展)。
- 優化 LZW 解碼性能(如使用更高效的數據結構存儲字典)。
- 添加動畫幀時序處理(結合圖形控制擴展中的延遲時間)。
總結
本文通過狀態機實現了 GIF 文件的逐步解析,將復雜的格式解析拆解為可管理的狀態轉移過程。狀態機模型不僅適用于 GIF 解析,還可推廣到其他二進制格式(如 PNG、BMP)的解析場景。下一篇文章將探討如何基于狀態機構建 GIF 文件,實現從像素數據到 GIF 動畫的生成。
from PIL import Image import struct# 讀取文件 gif_path = "1.gif" for _ in range(2):try:with open(gif_path, 'rb') as f:data = f.read()breakexcept:# 創建全白幀 50x50white_frame = Image.new('RGB', (5, 5), color=(255, 255, 255))# 創建全黑幀 50x50black_frame = Image.new('RGB', (5, 5), color=(0, 0, 0))# 保存為無限循環的GIF動畫white_frame.save(gif_path,save_all=True,append_images=[black_frame],duration=200,loop=0)# 解碼 def lzw_decode(compressed):# 初始化字典dictionary = {i: bytes([i]) for i in range(256)}next_code = 258result = bytearray()# 處理壓縮數據buffer = 0bits_in_buffer = 0code_size = 9prev_code = Nonefor byte in compressed:buffer |= byte << bits_in_bufferbits_in_buffer += 8while bits_in_buffer >= code_size:code = buffer & ((1 << code_size) - 1)buffer >>= code_sizebits_in_buffer -= code_sizeif code == 256: # 清除碼dictionary = {i: bytes([i]) for i in range(256)}next_code = 258code_size = 9prev_code = Nonecontinueelif code == 257: # 結束碼return bytes(result)if prev_code is None:result.extend(dictionary[code])prev_code = codecontinueif code in dictionary:entry = dictionary[code]result.extend(entry)dictionary[next_code] = dictionary[prev_code] + entry[:1]else:entry = dictionary[prev_code] + dictionary[prev_code][:1]result.extend(entry)dictionary[next_code] = entrynext_code += 1prev_code = codeif next_code >= (1 << code_size) and code_size < 12:code_size += 1return bytes(result)# 壓縮 def lzw_decompress(compressed):dictionary = {i: bytes([i]) for i in range(256)}next_code = 258result = bytearray()buffer = 0bits = 0code_size = 9prev = Nonefor byte in compressed:buffer |= byte << bitsbits += 8while bits >= code_size:code = buffer & ((1 << code_size) - 1)buffer >>= code_sizebits -= code_sizeif code == 256:dictionary = {i: bytes([i]) for i in range(256)}next_code = 258code_size = 9prev = Nonecontinueelif code == 257:return bytes(result)if prev is None:result.extend(dictionary[code])prev = codecontinueif code in dictionary:entry = dictionary[code]result.extend(entry)dictionary[next_code] = dictionary[prev] + entry[:1]else:entry = dictionary[prev] + dictionary[prev][:1]result.extend(entry)dictionary[next_code] = entrynext_code += 1prev = codeif next_code > (1 << code_size) - 1 and code_size < 12:code_size += 1return bytes(result)# 按照分塊處理 print(data) print(data.hex(' '))# dict_ex = {"ff":"應用程序擴展","f9":'圖形控制擴展'}# 狀態機 class GIFDecode:def __init__(self):self.state = "開頭" # 狀態self.state_child = "開始" # 子狀態self.global_color = [] # 全局顏色self.local_color_all = [] # 局部顏色(全部)self.local_color_one = [] # 局部顏色(單個)self.image_data = []self.version = "" # 版本self.buffer = bytearray() # 緩沖字節self.screen_width = 0 # 寬度self.screen_height = 0 # 長度self.has_global_color = 0 # 存在全局顏色表self.color_resolution = 3 # 顏色深度self.sort_flag = 0 # 分類標志self.global_color_table_size = 0 # 全局顏色數量self.bg_color_index = 0 # 背景顏色索引self.color_block_size = 0 # 顏色塊長度self.pixel_aspect_ratio = 0 # 像素高寬比self.application_size = 0 # 應用程序擴展字節長度self.application_text = 0 # 應用程序擴展文本self.application_child_size = 0 # 應用程序擴展子塊字節長度self.loop_type = 0 # 循環類型self.loop_parameter = 0 # 循環參數self.graphic_control_size = 0 # 圖像控制字符長度self.graphic_control_sign_transparent = 0 # 圖像控制標志位透明色self.graphic_control_sign_dispose = 0 # 圖像控制標志位處置方法 # 0不使用 1把圖形移去 2恢復到背景色 3恢復到先前狀態 4-7自定義self.graphic_control_sign_input = 0 # 圖像控制標志位用戶輸入self.graphic_control_sign_customize = 0 # 圖像控制標志位自定義self.graphic_control_delay = 0 # 圖像控制延遲時間self.graphic_control_sign_transparent_index = 0 # 圖像控制透明色索引self.image_offset_x = 0 # 圖像左偏移量 即X軸self.image_offset_y = 0 # 圖像頂部偏移量 即Y軸self.image_width = 0 # 畫布寬度self.image_length = 0 # 畫布長度self.local_color_sign_number = 0 # 0-2顏色多少self.local_color_sign_retain = 0 # 3-4保留位self.image_local_color_sign_sort = 0 # 5排序self.local_color_sign_interwoven = 0 # 6交織self.local_color_sign_has_color_table = 0 # 7局部顏色表標志self.local_color_size = 0 # 局部顏色表字節self.image_compressed_bit = 0 # 壓縮位self.image_block_size = 0 # 圖像子塊字節self.image_bytes_data = b"" # 圖片壓縮字節數據def feed(self, byte):print(self.buffer)if self.state == "開頭":self.buffer.append(byte)if len(self.buffer) == 6:if self.buffer == b'GIF87a' or self.buffer == b'GIF89a':self.state = "邏輯屏幕標識符"self.version = self.buffer.decode('ascii')self.buffer.clear()else:raise ValueError(f"無效的GIF版本: {bytes(self.buffer)}")elif self.state == "邏輯屏幕標識符":self.buffer.append(byte)if len(self.buffer) == 7:# 解析邏輯屏幕描述符self.screen_width = int.from_bytes(self.buffer[0:2], 'little')self.screen_height = int.from_bytes(self.buffer[2:4], 'little')# 解析標志字節flags = self.buffer[4]"""假設:m = 1(存在全局顏色表);cr = 3(顏色深度為 3,二進制 011);s = 0(不使用分類標志);pixel = 4(全局顏色列表大小為 4,二進制 100)。計算各字段的位位置m 占位 7 → 需左移 7 位(m << 7);cr 占位 6-4 → 需左移 4 位(cr << 4);s 占位 3 → 需左移 3 位(s << 3);pixel 占位 2-0 → 無需位移(直接取 pixel)"""self.has_global_color = (flags & 0b10000000)self.color_resolution = (flags & 0b01110000)self.sort_flag = (flags & 0b00001000) != 0self.global_color_table_size = 2 ** ((flags & 0b00000111) + 1)self.bg_color_index = self.buffer[5]self.pixel_aspect_ratio = self.buffer[6]if self.has_global_color:self.state = "全局顏色表"self.color_block_size = self.global_color_table_size * 3self.buffer.clear()else:self.state = "區塊檢測"self.buffer.clear()elif self.state == "全局顏色表":self.buffer.append(byte)if len(self.buffer) == self.color_block_size:# 解析全局顏色表self.global_color = [(self.buffer[i], self.buffer[i + 1], self.buffer[i + 2]) for i inrange(0, len(self.buffer), 3)]self.state = "區塊檢測"self.buffer.clear()elif self.state == "結束符檢測":self.state_child = "開始" # 重置子狀態if byte == 0x00:self.state = "區塊檢測"else:raise ValueError(f"無效的結束符: {bytes(byte)}")elif self.state == "區塊檢測":self.buffer.append(byte)if byte == 0x3B: # 檢測結束return Trueif self.state_child == "開始":if len(self.buffer) == 1:if self.buffer[0] == 0x21:self.state_child = "拓展塊"self.buffer.clear()elif self.buffer[0] == 0x2C:self.state = "圖像標識符"self.state_child = "開始" # 重置子狀態self.buffer.clear()else:raise ValueError(f"無效的區塊: {bytes(self.buffer)}")elif self.state_child == "拓展塊":if self.buffer[0] == 0xFF:self.state = "應用程序擴展"elif self.buffer[0] == 0xF9:self.state = "圖形控制擴展"self.state_child = "開始" # 重置子狀態self.buffer.clear()else:raise ValueError(f"無效的區塊: {bytes(self.buffer)}")elif self.state == "應用程序擴展":self.buffer.append(byte)if self.state_child == "開始":if len(self.buffer) == 1:self.application_size = struct.unpack('<B', self.buffer)[0]self.state_child = "解析"self.buffer.clear()elif self.state_child == "解析":if len(self.buffer) == self.application_size:self.application_text = self.buffer[0:self.application_size].decode('ascii', errors='replace')self.state_child = "子塊長度"self.buffer.clear()elif self.state_child == "子塊長度":if len(self.buffer) == 1:self.application_child_size = struct.unpack('<B', self.buffer)[0]self.state_child = "子塊解析"self.buffer.clear()elif self.state_child == "子塊解析":if len(self.buffer) == self.application_child_size:self.loop_type = self.buffer[0]self.loop_parameter = self.buffer[1:2]self.state = "結束符檢測"self.buffer.clear()elif self.state == "圖形控制擴展":self.buffer.append(byte)if self.state_child == "開始":if len(self.buffer) == 1:self.graphic_control_size = struct.unpack('<B', self.buffer)[0]self.state_child = "解析"self.buffer.clear()elif self.state_child == "解析":if len(self.buffer) == self.graphic_control_size:sign = self.buffer[0]# 解析標志位self.graphic_control_sign_transparent = (sign & 0b00000001)self.graphic_control_sign_dispose = (sign & 0b00000010)self.graphic_control_sign_input = (sign & 0b00011100)self.graphic_control_sign_customize = (sign & 0b11100000)self.graphic_control_delay = struct.unpack('<H', self.buffer[1:3])[0] * 0.01self.graphic_control_sign_transparent_index = self.buffer[3]self.state = "結束符檢測"self.buffer.clear()elif self.state == "圖像標識符":self.buffer.append(byte)if self.state_child == "開始":if len(self.buffer) == 9:self.image_offset_x = struct.unpack('<H', self.buffer[0:2])[0]self.image_offset_y = struct.unpack('<H', self.buffer[2:4])[0]self.image_width = struct.unpack('<H', self.buffer[4:6])[0]self.image_length = struct.unpack('<H', self.buffer[6:8])[0]sign = self.buffer[8]"""- 第 0-2 位:局部顏色表大小(0 = 無局部顏色表)- 第 3-4 位:保留位(0)- 第 5 位:排序標志(0 = 未排序)- 第 6 位:交織標志(0 = 非交織)- 第 7 位:局部顏色表標志(0 = 無局部顏色表)"""self.local_color_sign_number = (sign & 0b00000111)self.local_color_sign_retain = (sign & 0b00011000)self.image_local_color_sign_sort = (sign & 0b00100000)self.local_color_sign_interwoven = (sign & 0b01000000)self.local_color_sign_has_color_table = (sign & 0b10000000)# 檢測是否存在局部顏色表格if self.local_color_sign_has_color_table == 128:self.local_color_size = 2 ** (self.local_color_sign_number + 1) * 3self.state_child = "獲取局部顏色表"self.buffer.clear()else:self.state_child = "解析圖像"self.buffer.clear()elif self.state_child == "獲取局部顏色表":if len(self.buffer) == self.local_color_size:# 獲取局部顏色表放入局部顏色中self.local_color_one = [(self.buffer[i], self.buffer[i + 1], self.buffer[i + 2]) for i inrange(0, len(self.buffer), 3)]self.local_color_all.append(self.local_color_one)self.state_child = "解析圖像"self.buffer.clear()elif self.state_child == "解析圖像":if len(self.buffer) == 2:self.image_compressed_bit = self.buffer[0]self.image_block_size = self.buffer[1]self.state_child = "獲取數據"self.buffer.clear()elif self.state_child == "獲取數據":if len(self.buffer) == self.image_block_size:self.image_bytes_data += bytes(self.buffer) # 轉換字節而非對象self.state_child = "檢測獲取結束"self.buffer.clear()elif self.state_child == "檢測獲取結束":if self.buffer[0] == 0x00:image_data = self.lzw_decode(self.image_bytes_data)if self.local_color_one: # 如果局部存在 則按照局部生成 如果局部不存在則按照全局生成self.image_data.append([self.local_color_one[image_data[i]] for i in range(len(image_data))])else:self.image_data.append([self.global_color[image_data[i]] for i in range(len(image_data))])self.state = "區塊檢測"self.state_child = "開始"# 重置數據self.image_bytes_data = b""self.local_color_one.clear()self.buffer.clear()else:self.image_block_size = self.buffer[0]self.state_child = "獲取數據"self.buffer.clear()# 解碼def lzw_decode(self, compressed):# 初始化字典dictionary = {i: bytes([i]) for i in range(256)}next_code = 258result = bytearray()# 處理壓縮數據buffer = 0bits_in_buffer = 0code_size = 9prev_code = Nonefor byte in compressed:buffer |= byte << bits_in_bufferbits_in_buffer += 8while bits_in_buffer >= code_size:code = buffer & ((1 << code_size) - 1)buffer >>= code_sizebits_in_buffer -= code_sizeif code == 256: # 清除碼dictionary = {i: bytes([i]) for i in range(256)}next_code = 258code_size = 9prev_code = Nonecontinueelif code == 257: # 結束碼return bytes(result)if prev_code is None:result.extend(dictionary[code])prev_code = codecontinueif code in dictionary:entry = dictionary[code]result.extend(entry)dictionary[next_code] = dictionary[prev_code] + entry[:1]else:entry = dictionary[prev_code] + dictionary[prev_code][:1]result.extend(entry)dictionary[next_code] = entrynext_code += 1prev_code = codeif next_code >= (1 << code_size) and code_size < 12:code_size += 1return bytes(result)passdecoder = GIFDecode() for byte in data:decoder.feed(byte)print(decoder.version) print(decoder.image_data) print(decoder)
from PIL import Image
import struct# 讀取文件
gif_path = "1.gif"
for _ in range(2):try:with open(gif_path, 'rb') as f:data = f.read()breakexcept:# 創建全白幀 50x50white_frame = Image.new('RGB', (5, 5), color=(255, 255, 255))# 創建全黑幀 50x50black_frame = Image.new('RGB', (5, 5), color=(0, 0, 0))# 保存為無限循環的GIF動畫white_frame.save(gif_path,save_all=True,append_images=[black_frame],duration=200,loop=0)# 解碼
def lzw_decode(compressed):# 初始化字典dictionary = {i: bytes([i]) for i in range(256)}next_code = 258result = bytearray()# 處理壓縮數據buffer = 0bits_in_buffer = 0code_size = 9prev_code = Nonefor byte in compressed:buffer |= byte << bits_in_bufferbits_in_buffer += 8while bits_in_buffer >= code_size:code = buffer & ((1 << code_size) - 1)buffer >>= code_sizebits_in_buffer -= code_sizeif code == 256: # 清除碼dictionary = {i: bytes([i]) for i in range(256)}next_code = 258code_size = 9prev_code = Nonecontinueelif code == 257: # 結束碼return bytes(result)if prev_code is None:result.extend(dictionary[code])prev_code = codecontinueif code in dictionary:entry = dictionary[code]result.extend(entry)dictionary[next_code] = dictionary[prev_code] + entry[:1]else:entry = dictionary[prev_code] + dictionary[prev_code][:1]result.extend(entry)dictionary[next_code] = entrynext_code += 1prev_code = codeif next_code >= (1 << code_size) and code_size < 12:code_size += 1return bytes(result)# 壓縮
def lzw_decompress(compressed):dictionary = {i: bytes([i]) for i in range(256)}next_code = 258result = bytearray()buffer = 0bits = 0code_size = 9prev = Nonefor byte in compressed:buffer |= byte << bitsbits += 8while bits >= code_size:code = buffer & ((1 << code_size) - 1)buffer >>= code_sizebits -= code_sizeif code == 256:dictionary = {i: bytes([i]) for i in range(256)}next_code = 258code_size = 9prev = Nonecontinueelif code == 257:return bytes(result)if prev is None:result.extend(dictionary[code])prev = codecontinueif code in dictionary:entry = dictionary[code]result.extend(entry)dictionary[next_code] = dictionary[prev] + entry[:1]else:entry = dictionary[prev] + dictionary[prev][:1]result.extend(entry)dictionary[next_code] = entrynext_code += 1prev = codeif next_code > (1 << code_size) - 1 and code_size < 12:code_size += 1return bytes(result)# 按照分塊處理
print(data)
print(data.hex(' '))# dict_ex = {"ff":"應用程序擴展","f9":'圖形控制擴展'}# 狀態機
class GIFDecode:def __init__(self):self.state = "開頭" # 狀態self.state_child = "開始" # 子狀態self.global_color = [] # 全局顏色self.local_color_all = [] # 局部顏色(全部)self.local_color_one = [] # 局部顏色(單個)self.image_data = []self.version = "" # 版本self.buffer = bytearray() # 緩沖字節self.screen_width = 0 # 寬度self.screen_height = 0 # 長度self.has_global_color = 0 # 存在全局顏色表self.color_resolution = 3 # 顏色深度self.sort_flag = 0 # 分類標志self.global_color_table_size = 0 # 全局顏色數量self.bg_color_index = 0 # 背景顏色索引self.color_block_size = 0 # 顏色塊長度self.pixel_aspect_ratio = 0 # 像素高寬比self.application_size = 0 # 應用程序擴展字節長度self.application_text = 0 # 應用程序擴展文本self.application_child_size = 0 # 應用程序擴展子塊字節長度self.loop_type = 0 # 循環類型self.loop_parameter = 0 # 循環參數self.graphic_control_size = 0 # 圖像控制字符長度self.graphic_control_sign_transparent = 0 # 圖像控制標志位透明色self.graphic_control_sign_dispose = 0 # 圖像控制標志位處置方法 # 0不使用 1把圖形移去 2恢復到背景色 3恢復到先前狀態 4-7自定義self.graphic_control_sign_input = 0 # 圖像控制標志位用戶輸入self.graphic_control_sign_customize = 0 # 圖像控制標志位自定義self.graphic_control_delay = 0 # 圖像控制延遲時間self.graphic_control_sign_transparent_index = 0 # 圖像控制透明色索引self.image_offset_x = 0 # 圖像左偏移量 即X軸self.image_offset_y = 0 # 圖像頂部偏移量 即Y軸self.image_width = 0 # 畫布寬度self.image_length = 0 # 畫布長度self.local_color_sign_number = 0 # 0-2顏色多少self.local_color_sign_retain = 0 # 3-4保留位self.image_local_color_sign_sort = 0 # 5排序self.local_color_sign_interwoven = 0 # 6交織self.local_color_sign_has_color_table = 0 # 7局部顏色表標志self.local_color_size = 0 # 局部顏色表字節self.image_compressed_bit = 0 # 壓縮位self.image_block_size = 0 # 圖像子塊字節self.image_bytes_data = b"" # 圖片壓縮字節數據def feed(self, byte):print(self.buffer)if self.state == "開頭":self.buffer.append(byte)if len(self.buffer) == 6:if self.buffer == b'GIF87a' or self.buffer == b'GIF89a':self.state = "邏輯屏幕標識符"self.version = self.buffer.decode('ascii')self.buffer.clear()else:raise ValueError(f"無效的GIF版本: {bytes(self.buffer)}")elif self.state == "邏輯屏幕標識符":self.buffer.append(byte)if len(self.buffer) == 7:# 解析邏輯屏幕描述符self.screen_width = int.from_bytes(self.buffer[0:2], 'little')self.screen_height = int.from_bytes(self.buffer[2:4], 'little')# 解析標志字節flags = self.buffer[4]"""假設:m = 1(存在全局顏色表);cr = 3(顏色深度為 3,二進制 011);s = 0(不使用分類標志);pixel = 4(全局顏色列表大小為 4,二進制 100)。計算各字段的位位置m 占位 7 → 需左移 7 位(m << 7);cr 占位 6-4 → 需左移 4 位(cr << 4);s 占位 3 → 需左移 3 位(s << 3);pixel 占位 2-0 → 無需位移(直接取 pixel)"""self.has_global_color = (flags & 0b10000000)self.color_resolution = (flags & 0b01110000)self.sort_flag = (flags & 0b00001000) != 0self.global_color_table_size = 2 ** ((flags & 0b00000111) + 1)self.bg_color_index = self.buffer[5]self.pixel_aspect_ratio = self.buffer[6]if self.has_global_color:self.state = "全局顏色表"self.color_block_size = self.global_color_table_size * 3self.buffer.clear()else:self.state = "區塊檢測"self.buffer.clear()elif self.state == "全局顏色表":self.buffer.append(byte)if len(self.buffer) == self.color_block_size:# 解析全局顏色表self.global_color = [(self.buffer[i], self.buffer[i + 1], self.buffer[i + 2]) for i inrange(0, len(self.buffer), 3)]self.state = "區塊檢測"self.buffer.clear()elif self.state == "結束符檢測":self.state_child = "開始" # 重置子狀態if byte == 0x00:self.state = "區塊檢測"else:raise ValueError(f"無效的結束符: {bytes(byte)}")elif self.state == "區塊檢測":self.buffer.append(byte)if byte == 0x3B: # 檢測結束return Trueif self.state_child == "開始":if len(self.buffer) == 1:if self.buffer[0] == 0x21:self.state_child = "拓展塊"self.buffer.clear()elif self.buffer[0] == 0x2C:self.state = "圖像標識符"self.state_child = "開始" # 重置子狀態self.buffer.clear()else:raise ValueError(f"無效的區塊: {bytes(self.buffer)}")elif self.state_child == "拓展塊":if self.buffer[0] == 0xFF:self.state = "應用程序擴展"elif self.buffer[0] == 0xF9:self.state = "圖形控制擴展"self.state_child = "開始" # 重置子狀態self.buffer.clear()else:raise ValueError(f"無效的區塊: {bytes(self.buffer)}")elif self.state == "應用程序擴展":self.buffer.append(byte)if self.state_child == "開始":if len(self.buffer) == 1:self.application_size = struct.unpack('<B', self.buffer)[0]self.state_child = "解析"self.buffer.clear()elif self.state_child == "解析":if len(self.buffer) == self.application_size:self.application_text = self.buffer[0:self.application_size].decode('ascii', errors='replace')self.state_child = "子塊長度"self.buffer.clear()elif self.state_child == "子塊長度":if len(self.buffer) == 1:self.application_child_size = struct.unpack('<B', self.buffer)[0]self.state_child = "子塊解析"self.buffer.clear()elif self.state_child == "子塊解析":if len(self.buffer) == self.application_child_size:self.loop_type = self.buffer[0]self.loop_parameter = self.buffer[1:2]self.state = "結束符檢測"self.buffer.clear()elif self.state == "圖形控制擴展":self.buffer.append(byte)if self.state_child == "開始":if len(self.buffer) == 1:self.graphic_control_size = struct.unpack('<B', self.buffer)[0]self.state_child = "解析"self.buffer.clear()elif self.state_child == "解析":if len(self.buffer) == self.graphic_control_size:sign = self.buffer[0]# 解析標志位self.graphic_control_sign_transparent = (sign & 0b00000001)self.graphic_control_sign_dispose = (sign & 0b00000010)self.graphic_control_sign_input = (sign & 0b00011100)self.graphic_control_sign_customize = (sign & 0b11100000)self.graphic_control_delay = struct.unpack('<H', self.buffer[1:3])[0] * 0.01self.graphic_control_sign_transparent_index = self.buffer[3]self.state = "結束符檢測"self.buffer.clear()elif self.state == "圖像標識符":self.buffer.append(byte)if self.state_child == "開始":if len(self.buffer) == 9:self.image_offset_x = struct.unpack('<H', self.buffer[0:2])[0]self.image_offset_y = struct.unpack('<H', self.buffer[2:4])[0]self.image_width = struct.unpack('<H', self.buffer[4:6])[0]self.image_length = struct.unpack('<H', self.buffer[6:8])[0]sign = self.buffer[8]"""- 第 0-2 位:局部顏色表大小(0 = 無局部顏色表)- 第 3-4 位:保留位(0)- 第 5 位:排序標志(0 = 未排序)- 第 6 位:交織標志(0 = 非交織)- 第 7 位:局部顏色表標志(0 = 無局部顏色表)"""self.local_color_sign_number = (sign & 0b00000111)self.local_color_sign_retain = (sign & 0b00011000)self.image_local_color_sign_sort = (sign & 0b00100000)self.local_color_sign_interwoven = (sign & 0b01000000)self.local_color_sign_has_color_table = (sign & 0b10000000)# 檢測是否存在局部顏色表格if self.local_color_sign_has_color_table == 128:self.local_color_size = 2 ** (self.local_color_sign_number + 1) * 3self.state_child = "獲取局部顏色表"self.buffer.clear()else:self.state_child = "解析圖像"self.buffer.clear()elif self.state_child == "獲取局部顏色表":if len(self.buffer) == self.local_color_size:# 獲取局部顏色表放入局部顏色中self.local_color_one = [(self.buffer[i], self.buffer[i + 1], self.buffer[i + 2]) for i inrange(0, len(self.buffer), 3)]self.local_color_all.append(self.local_color_one)self.state_child = "解析圖像"self.buffer.clear()elif self.state_child == "解析圖像":if len(self.buffer) == 2:self.image_compressed_bit = self.buffer[0]self.image_block_size = self.buffer[1]self.state_child = "獲取數據"self.buffer.clear()elif self.state_child == "獲取數據":if len(self.buffer) == self.image_block_size:self.image_bytes_data += bytes(self.buffer) # 轉換字節而非對象self.state_child = "檢測獲取結束"self.buffer.clear()elif self.state_child == "檢測獲取結束":if self.buffer[0] == 0x00:image_data = self.lzw_decode(self.image_bytes_data)if self.local_color_one: # 如果局部存在 則按照局部生成 如果局部不存在則按照全局生成self.image_data.append([self.local_color_one[image_data[i]] for i in range(len(image_data))])else:self.image_data.append([self.global_color[image_data[i]] for i in range(len(image_data))])self.state = "區塊檢測"self.state_child = "開始"# 重置數據self.image_bytes_data = b""self.local_color_one.clear()self.buffer.clear()else:self.image_block_size = self.buffer[0]self.state_child = "獲取數據"self.buffer.clear()# 解碼def lzw_decode(self, compressed):# 初始化字典dictionary = {i: bytes([i]) for i in range(256)}next_code = 258result = bytearray()# 處理壓縮數據buffer = 0bits_in_buffer = 0code_size = 9prev_code = Nonefor byte in compressed:buffer |= byte << bits_in_bufferbits_in_buffer += 8while bits_in_buffer >= code_size:code = buffer & ((1 << code_size) - 1)buffer >>= code_sizebits_in_buffer -= code_sizeif code == 256: # 清除碼dictionary = {i: bytes([i]) for i in range(256)}next_code = 258code_size = 9prev_code = Nonecontinueelif code == 257: # 結束碼return bytes(result)if prev_code is None:result.extend(dictionary[code])prev_code = codecontinueif code in dictionary:entry = dictionary[code]result.extend(entry)dictionary[next_code] = dictionary[prev_code] + entry[:1]else:entry = dictionary[prev_code] + dictionary[prev_code][:1]result.extend(entry)dictionary[next_code] = entrynext_code += 1prev_code = codeif next_code >= (1 << code_size) and code_size < 12:code_size += 1return bytes(result)passdecoder = GIFDecode()
for byte in data:decoder.feed(byte)print(decoder.version)
print(decoder.image_data)
print(decoder)