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() break except: # 创建全白帧 50x50 white_frame = Image.new('RGB', (5, 5), color=(255, 255, 255)) # 创建全黑帧 50x50 black_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 = 258 result = bytearray() # 处理压缩数据 buffer = 0 bits_in_buffer = 0 code_size = 9 prev_code = None for byte in compressed: buffer |= byte << bits_in_buffer bits_in_buffer += 8 while bits_in_buffer >= code_size: code = buffer & ((1 << code_size) - 1) buffer >>= code_size bits_in_buffer -= code_size if code == 256: # 清除码 dictionary = {i: bytes([i]) for i in range(256)} next_code = 258 code_size = 9 prev_code = None continue elif code == 257: # 结束码 return bytes(result) if prev_code is None: result.extend(dictionary[code]) prev_code = code continue if 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] = entry next_code += 1 prev_code = code if next_code >= (1 << code_size) and code_size < 12: code_size += 1 return bytes(result) # 压缩 def lzw_decompress(compressed): dictionary = {i: bytes([i]) for i in range(256)} next_code = 258 result = bytearray() buffer = 0 bits = 0 code_size = 9 prev = None for byte in compressed: buffer |= byte << bits bits += 8 while bits >= code_size: code = buffer & ((1 << code_size) - 1) buffer >>= code_size bits -= code_size if code == 256: dictionary = {i: bytes([i]) for i in range(256)} next_code = 258 code_size = 9 prev = None continue elif code == 257: return bytes(result) if prev is None: result.extend(dictionary[code]) prev = code continue if 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] = entry next_code += 1 prev = code if next_code > (1 << code_size) - 1 and code_size < 12: code_size += 1 return 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) != 0 self.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 * 3 self.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 in range(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 True if 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.01 self.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) * 3 self.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 in range(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 = 258 result = bytearray() # 处理压缩数据 buffer = 0 bits_in_buffer = 0 code_size = 9 prev_code = None for byte in compressed: buffer |= byte << bits_in_buffer bits_in_buffer += 8 while bits_in_buffer >= code_size: code = buffer & ((1 << code_size) - 1) buffer >>= code_size bits_in_buffer -= code_size if code == 256: # 清除码 dictionary = {i: bytes([i]) for i in range(256)} next_code = 258 code_size = 9 prev_code = None continue elif code == 257: # 结束码 return bytes(result) if prev_code is None: result.extend(dictionary[code]) prev_code = code continue if 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] = entry next_code += 1 prev_code = code if next_code >= (1 << code_size) and code_size < 12: code_size += 1 return bytes(result) pass decoder = 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()
break
except:
# 创建全白帧 50x50
white_frame = Image.new('RGB', (5, 5), color=(255, 255, 255))
# 创建全黑帧 50x50
black_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 = 258
result = bytearray()
# 处理压缩数据
buffer = 0
bits_in_buffer = 0
code_size = 9
prev_code = None
for byte in compressed:
buffer |= byte << bits_in_buffer
bits_in_buffer += 8
while bits_in_buffer >= code_size:
code = buffer & ((1 << code_size) - 1)
buffer >>= code_size
bits_in_buffer -= code_size
if code == 256: # 清除码
dictionary = {i: bytes([i]) for i in range(256)}
next_code = 258
code_size = 9
prev_code = None
continue
elif code == 257: # 结束码
return bytes(result)
if prev_code is None:
result.extend(dictionary[code])
prev_code = code
continue
if 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] = entry
next_code += 1
prev_code = code
if next_code >= (1 << code_size) and code_size < 12:
code_size += 1
return bytes(result)
# 压缩
def lzw_decompress(compressed):
dictionary = {i: bytes([i]) for i in range(256)}
next_code = 258
result = bytearray()
buffer = 0
bits = 0
code_size = 9
prev = None
for byte in compressed:
buffer |= byte << bits
bits += 8
while bits >= code_size:
code = buffer & ((1 << code_size) - 1)
buffer >>= code_size
bits -= code_size
if code == 256:
dictionary = {i: bytes([i]) for i in range(256)}
next_code = 258
code_size = 9
prev = None
continue
elif code == 257:
return bytes(result)
if prev is None:
result.extend(dictionary[code])
prev = code
continue
if 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] = entry
next_code += 1
prev = code
if next_code > (1 << code_size) - 1 and code_size < 12:
code_size += 1
return 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) != 0
self.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 * 3
self.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 in
range(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 True
if 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.01
self.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) * 3
self.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 in
range(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 = 258
result = bytearray()
# 处理压缩数据
buffer = 0
bits_in_buffer = 0
code_size = 9
prev_code = None
for byte in compressed:
buffer |= byte << bits_in_buffer
bits_in_buffer += 8
while bits_in_buffer >= code_size:
code = buffer & ((1 << code_size) - 1)
buffer >>= code_size
bits_in_buffer -= code_size
if code == 256: # 清除码
dictionary = {i: bytes([i]) for i in range(256)}
next_code = 258
code_size = 9
prev_code = None
continue
elif code == 257: # 结束码
return bytes(result)
if prev_code is None:
result.extend(dictionary[code])
prev_code = code
continue
if 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] = entry
next_code += 1
prev_code = code
if next_code >= (1 << code_size) and code_size < 12:
code_size += 1
return bytes(result)
pass
decoder = GIFDecode()
for byte in data:
decoder.feed(byte)
print(decoder.version)
print(decoder.image_data)
print(decoder)