Python |GIF 解析与构建(1):初步解析
目录
2. 逻辑屏幕描述块(Logical Screen Descriptor)
5. 动画控制块(Graphic Control Extension,仅 GIF89a 支持)
一、GIF 格式概述
GIF(Graphics Interchange Format)是一种基于 LZW 压缩算法 的位图图像格式,诞生于 1987 年,至今仍是网页动画、表情包等场景的常用格式。其核心特点包括:
- 支持动画:通过多帧图像按顺序播放实现动态效果。
- 索引色模式:最多包含 256 种颜色(通过调色板索引映射),压缩效率高但色彩表现有限。
- 无损压缩:LZW 算法保证图像质量不损失,适合线条图、图标等简单图形。
二、GIF 文件结构解析
GIF 格式采用 二进制分块结构,由多个独立的数据块组成。以下是最核心的基础结构(以 GIF89a 版本为例):
1. 文件头(Header)
- 签名(Signature):3 字节固定值
0x47 0x49 0x46
(即 "GIF" 字符),用于标识文件类型。 - 版本号(Version):3 字节,常见值为
"87a"
或"89a"
(后者支持动画和透明色)。
2. 逻辑屏幕描述块(Logical Screen Descriptor)
定义图像的显示区域和全局属性,结构如下:
字段 | 长度(字节) | 说明 |
---|---|---|
屏幕宽度 | 2 | 以像素为单位的宽度(低字节在前,高字节在后) |
屏幕高度 | 2 | 以像素为单位的高度(低字节在前,高字节在后) |
标志位 | 1 | 包含调色板信息(如是否存在全局调色板、颜色深度、是否支持透明色等) |
背景色索引 | 1 | 全局背景色在调色板中的索引(0 表示无背景色) |
像素 aspect 比 | 1 | 像素宽高比(通常为 0 表示默认值) |
示例:
若标志位为 0x87
(二进制 10000111
),表示:
- 存在全局调色板(最高位为 1),调色板大小为 2(3+1)=16 色(低 3 位
011
表示索引位数为 3+1)。
3. 全局调色板(Global Color Table)
可选块,当逻辑屏幕描述块的标志位中全局调色板存在时出现。结构为:
- 由连续的
3*N
字节组成,每个颜色占 3 字节(R、G、B 分量,各 1 字节),N
为颜色数(2n,n 由标志位确定)。
4. 图像数据块(Image Data)
每个图像数据块对应一帧画面,结构如下:
(1)图像标识符(Image Descriptor)
字段 | 长度(字节) | 说明 |
---|---|---|
标识符 | 1 | 固定值 0x2C (逗号 , ),表示图像数据开始 |
左偏移量 | 2 | 图像在屏幕中的水平起始位置(低字节在前) |
顶偏移量 | 2 | 图像在屏幕中的垂直起始位置(低字节在前) |
图像宽度 | 2 | 图像宽度(像素) |
图像高度 | 2 | 图像高度(像素) |
标志位 | 1 | 包含局部调色板信息、是否为透明色、是否需要交错显示等 |
(2)局部调色板(Local Color Table)
可选块,若标志位中指定存在局部调色板,则结构与全局调色板一致,但仅作用于当前图像帧。
(3)图像数据(Image Data)
- 压缩数据块:采用 LZW 压缩算法,先存储 初始代码大小(Initial Code Size)(1 字节,表示初始编码位数,通常为 7 或 8),随后是 压缩数据流(由多个数据块组成,每个数据块以
0x00
结尾)。 - 像素解码:通过 LZW 算法解压缩后,得到每个像素的颜色索引值,通过局部或全局调色板映射为 RGB 颜色。
5. 动画控制块(Graphic Control Extension,仅 GIF89a 支持)
用于定义动画帧的播放规则,结构如下:
字段 | 长度(字节) | 说明 |
---|---|---|
扩展标识符 | 1 | 固定值 0x21 (感叹号 ! ) |
块标识符 | 1 | 固定值 0xF9 (图形控制扩展) |
块大小 | 1 | 固定值 0x04 |
标志位 | 1 | 包含透明色索引是否有效、动画播放方式(如是否循环)等 |
延迟时间 | 2 | 帧停留时间(单位为 1/100 秒,低字节在前) |
透明色索引 | 1 | 可选,透明像素的颜色索引 |
结束符 | 1 | 固定值 0x00 |
6. 文件尾(Trailer)
1 字节固定值 0x3B
(分号 ;
),标识文件结束。
from PIL import Image import struct dict_ex = {"ff":"扩展应用块","f9":'图形控制扩展'} # 读取文件 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 ) print(data) print(data.hex(' ')) # 步进 start_number = 0 end_number = 0 # 步数处理 def deal(end_add): global start_number, end_number start_number = end_number end_number += end_add return data[start_number:end_number] # 解码 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("开头GIF89a") bytes1 = deal(6) print(bytes1.decode('ascii', errors='replace')) print(bytes1.hex(' ')) print("画布宽度") bytes2 = deal(2) print(struct.unpack('<H', bytes2)[0]) print(bytes2.hex(' ')) print("画布长度") bytes3 = deal(2) print(struct.unpack('<H', bytes3)[0]) print(bytes3.hex(' ')) print("画布颜色位") bytes4 = deal(1) print(bool(bytes4[0] & 0b10000000)) print(bytes4.hex(' ')) """ 假设: 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) """ print("背景颜色索引") bytes5 = deal(1) print(bytes5.hex(' ')) print("像素宽高比") bytes6 = deal(1) print(bytes6.hex(' ')) # 默认0不使用 print("颜色计算") color_number = 2 ** ((bytes4[0] & 0b00000111) + 1) # 颜色数量不够需要补齐 解包采用掩码提取 +1是容纳更多颜色 而且因为2^0=1 一种颜色没有意义 print(f"颜色数量:{color_number}") bytes7 = deal(3 * color_number) print(bytes7.hex(' ')) print("扩展引导符号") bytes8 = deal(1) print(bytes8.decode('ascii', errors='replace')) print(bytes8.hex(' ')) print("扩展类型") bytes9 = deal(1) print(dict_ex[bytes9.hex(' ')]) print(bytes9.hex(' ')) print("块长度") bytes10 = deal(1) print(struct.unpack('<B', bytes10)[0]) print(bytes10.hex(' ')) print("扩展应用标识符") bytes11 = deal(struct.unpack('<B', bytes10)[0]) print(bytes11.decode('ascii', errors='replace')) print(bytes11.hex(' ')) # 扩展应用ff 0b 4e 45 54 53 43 41 50 45 32 2e 30 03 01 00 00【循环次数】 00 """ 以 ASCII 编码表示应用程序名称:NETSCAPE2.0。 这是 Netscape 浏览器定义的 GIF 动画循环控制扩展。 """ print("子块长度") bytes12 = deal(1) print(struct.unpack('<B', bytes12)[0]) print(bytes12.hex(' ')) print("循环类型") bytes13 = deal(1) print(bytes13.hex(' ')) print("循环参数") bytes14 = deal(2) print(bytes14.hex(' ')) """ 0x00 00 表示 无限循环。 若为 0x01 00,则表示循环 1 次(播放 2 次后停止)。 若为 0x05 00,则表示循环 5 次(播放 6 次后停止)。 """ print("块结束符") bytes15 = deal(1) print(bytes15.hex(' ')) print("扩展引导符号") bytes16 = deal(1) print(bytes16.decode('ascii', errors='replace')) print(bytes16.hex(' ')) print("扩展类型") bytes17 = deal(1) print(dict_ex[bytes17.hex(' ')]) print(bytes17.hex(' ')) print("块长度") bytes18 = deal(1) print(struct.unpack('<B', bytes18)[0]) print(bytes18.hex(' ')) print("标志位") bytes19 = deal(1) print(bytes19.hex(' ')) """ - 第 7 位(最高位):透明度标志(0 = 关闭) - 第 6 位:用户输入标志(0 = 无需等待输入) - 第 3-5 位: disposal 方法(0 = 不指定,默认保留) - 第 0-2 位:保留位(0) """ print("延迟时间") bytes20 = deal(2) print(struct.unpack('<H', bytes20)[0]*0.01,"秒") print("透明色索引") bytes20 = deal(1) print(bytes20.hex(' ')) """ 若标志位启用透明度(第 7 位为 1),此处为有效颜色索引。 """ print("块结束符") bytes21 = deal(1) print(bytes21.hex(' ')) print("图像分隔符 开始") bytes22 = deal(1) print(bytes22.hex(' ')) print("图像左偏移量") bytes23 = deal(2) print(struct.unpack('<H', bytes23)[0]) print(bytes23.hex(' ')) print("图像顶偏移量") bytes24 = deal(2) print(struct.unpack('<H', bytes24)[0]) print(bytes24.hex(' ')) print("画布宽度") bytes25 = deal(2) print(struct.unpack('<H', bytes25)[0]) print(bytes25.hex(' ')) print("画布长度") bytes26 = deal(2) print(struct.unpack('<H', bytes26)[0]) print(bytes26.hex(' ')) print("局部颜色表标志") bytes27 = deal(1) print(bytes27.hex(' ')) """ - 第 7 位:局部颜色表标志(0 = 无局部颜色表) - 第 6 位:交织标志(0 = 非交织) - 第 5 位:排序标志(0 = 未排序) - 第 3-4 位:保留位(0) - 第 0-2 位:局部颜色表大小(0 = 无局部颜色表) """ print("压缩位数") bytes27 = deal(1) print(struct.unpack('<B', bytes27)[0]) print(bytes27.hex(' ')) print("子块字节") bytes28 = deal(1) print(struct.unpack('<B', bytes28)[0]) print(bytes28.hex(' ')) print("子块数据") bytes29 = deal(struct.unpack('<B', bytes28)[0]) print(bytes29.hex(' ')) print(bytes29) print(lzw_decode(bytes29)) print("终止符") bytes30 = deal(1) print(bytes30.hex(' ')) print("扩展引导符号") bytes31 = deal(1) print(bytes31.decode('ascii', errors='replace')) print(bytes31.hex(' ')) print("扩展类型") bytes32 = deal(1) print(dict_ex[bytes32.hex(' ')]) print(bytes32.hex(' ')) print("块长度") bytes33 = deal(1) print(struct.unpack('<B', bytes33)[0]) print(bytes33.hex(' ')) print("标志位") bytes34 = deal(1) print(bytes34.hex(' ')) """ - 第 7 位(最高位):透明度标志(0 = 关闭) - 第 6 位:用户输入标志(0 = 无需等待输入) - 第 3-5 位: disposal 方法(0 = 不指定,默认保留) - 第 0-2 位:保留位(0) """ print("延迟时间") bytes35 = deal(2) print(struct.unpack('<H', bytes35)[0]*0.01,"秒") print(bytes35.hex(' ')) print("透明色索引") bytes36 = deal(1) print(bytes36.hex(' ')) """ 若标志位启用透明度(第 7 位为 1),此处为有效颜色索引。 """ print("块结束符") bytes37 = deal(1) print(bytes37.hex(' ')) print("图像分隔符 开始") bytes38 = deal(1) print(bytes38.hex(' ')) print("图像左偏移量") bytes39 = deal(2) print(struct.unpack('<H', bytes39)[0]) print(bytes39.hex(' ')) print("图像顶偏移量") bytes40 = deal(2) print(struct.unpack('<H', bytes40)[0]) print(bytes40.hex(' ')) print("画布宽度") bytes41 = deal(2) print(struct.unpack('<H', bytes41)[0]) print(bytes41.hex(' ')) print("画布长度") bytes42 = deal(2) print(struct.unpack('<H', bytes42)[0]) print(bytes42.hex(' ')) print("局部颜色表标志") bytes43 = deal(1) print(bytes43.hex(' ')) """ - 第 7 位:局部颜色表标志(0 = 无局部颜色表) - 第 6 位:交织标志(0 = 非交织) - 第 5 位:排序标志(0 = 未排序) - 第 3-4 位:保留位(0) - 第 0-2 位:局部颜色表大小(0 = 无局部颜色表) """ color_number = 2 ** ((bytes43[0] & 0b00000111) + 1) # 颜色数量不够需要补齐 解包采用掩码提取 +1是容纳更多颜色 而且因为2^0=1 一种颜色没有意义 print(f"颜色数量:{color_number}") bytes44 = deal(3 * color_number) print(bytes44.hex(' ')) print("压缩位数") bytes45 = deal(1) print(struct.unpack('<B', bytes45)[0]) print(bytes45.hex(' ')) print("子块字节") bytes46 = deal(1) print(struct.unpack('<B', bytes46)[0]) print(bytes46.hex(' ')) print("子块数据") bytes47 = deal(struct.unpack('<B', bytes46)[0]) print(bytes47.hex(' ')) print(bytes47) print(lzw_decode(bytes47)) print("终止符") bytes48 = deal(1) print(bytes48.hex(' ')) print("结束") bytes49 = deal(1) print(bytes49.hex(' ')) print(bytes49)
from PIL import Image
import struct
dict_ex = {"ff":"扩展应用块","f9":'图形控制扩展'}
# 读取文件
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
)
print(data)
print(data.hex(' '))
# 步进
start_number = 0
end_number = 0
# 步数处理
def deal(end_add):
global start_number, end_number
start_number = end_number
end_number += end_add
return data[start_number:end_number]
# 解码
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("开头GIF89a")
bytes1 = deal(6)
print(bytes1.decode('ascii', errors='replace'))
print(bytes1.hex(' '))
print("画布宽度")
bytes2 = deal(2)
print(struct.unpack('<H', bytes2)[0])
print(bytes2.hex(' '))
print("画布长度")
bytes3 = deal(2)
print(struct.unpack('<H', bytes3)[0])
print(bytes3.hex(' '))
print("画布颜色位")
bytes4 = deal(1)
print(bool(bytes4[0] & 0b10000000))
print(bytes4.hex(' '))
"""
假设:
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)
"""
print("背景颜色索引")
bytes5 = deal(1)
print(bytes5.hex(' '))
print("像素宽高比")
bytes6 = deal(1)
print(bytes6.hex(' ')) # 默认0不使用
print("颜色计算")
color_number = 2 ** ((bytes4[0] & 0b00000111) + 1) # 颜色数量不够需要补齐 解包采用掩码提取 +1是容纳更多颜色 而且因为2^0=1 一种颜色没有意义
print(f"颜色数量:{color_number}")
bytes7 = deal(3 * color_number)
print(bytes7.hex(' '))
print("扩展引导符号")
bytes8 = deal(1)
print(bytes8.decode('ascii', errors='replace'))
print(bytes8.hex(' '))
print("扩展类型")
bytes9 = deal(1)
print(dict_ex[bytes9.hex(' ')])
print(bytes9.hex(' '))
print("块长度")
bytes10 = deal(1)
print(struct.unpack('<B', bytes10)[0])
print(bytes10.hex(' '))
print("扩展应用标识符")
bytes11 = deal(struct.unpack('<B', bytes10)[0])
print(bytes11.decode('ascii', errors='replace'))
print(bytes11.hex(' '))
# 扩展应用ff 0b 4e 45 54 53 43 41 50 45 32 2e 30 03 01 00 00【循环次数】 00
"""
以 ASCII 编码表示应用程序名称:NETSCAPE2.0。
这是 Netscape 浏览器定义的 GIF 动画循环控制扩展。
"""
print("子块长度")
bytes12 = deal(1)
print(struct.unpack('<B', bytes12)[0])
print(bytes12.hex(' '))
print("循环类型")
bytes13 = deal(1)
print(bytes13.hex(' '))
print("循环参数")
bytes14 = deal(2)
print(bytes14.hex(' '))
"""
0x00 00 表示 无限循环。
若为 0x01 00,则表示循环 1 次(播放 2 次后停止)。
若为 0x05 00,则表示循环 5 次(播放 6 次后停止)。
"""
print("块结束符")
bytes15 = deal(1)
print(bytes15.hex(' '))
print("扩展引导符号")
bytes16 = deal(1)
print(bytes16.decode('ascii', errors='replace'))
print(bytes16.hex(' '))
print("扩展类型")
bytes17 = deal(1)
print(dict_ex[bytes17.hex(' ')])
print(bytes17.hex(' '))
print("块长度")
bytes18 = deal(1)
print(struct.unpack('<B', bytes18)[0])
print(bytes18.hex(' '))
print("标志位")
bytes19 = deal(1)
print(bytes19.hex(' '))
"""
- 第 7 位(最高位):透明度标志(0 = 关闭)
- 第 6 位:用户输入标志(0 = 无需等待输入)
- 第 3-5 位: disposal 方法(0 = 不指定,默认保留)
- 第 0-2 位:保留位(0)
"""
print("延迟时间")
bytes20 = deal(2)
print(struct.unpack('<H', bytes20)[0]*0.01,"秒")
print("透明色索引")
bytes20 = deal(1)
print(bytes20.hex(' '))
"""
若标志位启用透明度(第 7 位为 1),此处为有效颜色索引。
"""
print("块结束符")
bytes21 = deal(1)
print(bytes21.hex(' '))
print("图像分隔符 开始")
bytes22 = deal(1)
print(bytes22.hex(' '))
print("图像左偏移量")
bytes23 = deal(2)
print(struct.unpack('<H', bytes23)[0])
print(bytes23.hex(' '))
print("图像顶偏移量")
bytes24 = deal(2)
print(struct.unpack('<H', bytes24)[0])
print(bytes24.hex(' '))
print("画布宽度")
bytes25 = deal(2)
print(struct.unpack('<H', bytes25)[0])
print(bytes25.hex(' '))
print("画布长度")
bytes26 = deal(2)
print(struct.unpack('<H', bytes26)[0])
print(bytes26.hex(' '))
print("局部颜色表标志")
bytes27 = deal(1)
print(bytes27.hex(' '))
"""
- 第 7 位:局部颜色表标志(0 = 无局部颜色表)
- 第 6 位:交织标志(0 = 非交织)
- 第 5 位:排序标志(0 = 未排序)
- 第 3-4 位:保留位(0)
- 第 0-2 位:局部颜色表大小(0 = 无局部颜色表)
"""
print("压缩位数")
bytes27 = deal(1)
print(struct.unpack('<B', bytes27)[0])
print(bytes27.hex(' '))
print("子块字节")
bytes28 = deal(1)
print(struct.unpack('<B', bytes28)[0])
print(bytes28.hex(' '))
print("子块数据")
bytes29 = deal(struct.unpack('<B', bytes28)[0])
print(bytes29.hex(' '))
print(bytes29)
print(lzw_decode(bytes29))
print("终止符")
bytes30 = deal(1)
print(bytes30.hex(' '))
print("扩展引导符号")
bytes31 = deal(1)
print(bytes31.decode('ascii', errors='replace'))
print(bytes31.hex(' '))
print("扩展类型")
bytes32 = deal(1)
print(dict_ex[bytes32.hex(' ')])
print(bytes32.hex(' '))
print("块长度")
bytes33 = deal(1)
print(struct.unpack('<B', bytes33)[0])
print(bytes33.hex(' '))
print("标志位")
bytes34 = deal(1)
print(bytes34.hex(' '))
"""
- 第 7 位(最高位):透明度标志(0 = 关闭)
- 第 6 位:用户输入标志(0 = 无需等待输入)
- 第 3-5 位: disposal 方法(0 = 不指定,默认保留)
- 第 0-2 位:保留位(0)
"""
print("延迟时间")
bytes35 = deal(2)
print(struct.unpack('<H', bytes35)[0]*0.01,"秒")
print(bytes35.hex(' '))
print("透明色索引")
bytes36 = deal(1)
print(bytes36.hex(' '))
"""
若标志位启用透明度(第 7 位为 1),此处为有效颜色索引。
"""
print("块结束符")
bytes37 = deal(1)
print(bytes37.hex(' '))
print("图像分隔符 开始")
bytes38 = deal(1)
print(bytes38.hex(' '))
print("图像左偏移量")
bytes39 = deal(2)
print(struct.unpack('<H', bytes39)[0])
print(bytes39.hex(' '))
print("图像顶偏移量")
bytes40 = deal(2)
print(struct.unpack('<H', bytes40)[0])
print(bytes40.hex(' '))
print("画布宽度")
bytes41 = deal(2)
print(struct.unpack('<H', bytes41)[0])
print(bytes41.hex(' '))
print("画布长度")
bytes42 = deal(2)
print(struct.unpack('<H', bytes42)[0])
print(bytes42.hex(' '))
print("局部颜色表标志")
bytes43 = deal(1)
print(bytes43.hex(' '))
"""
- 第 7 位:局部颜色表标志(0 = 无局部颜色表)
- 第 6 位:交织标志(0 = 非交织)
- 第 5 位:排序标志(0 = 未排序)
- 第 3-4 位:保留位(0)
- 第 0-2 位:局部颜色表大小(0 = 无局部颜色表)
"""
color_number = 2 ** ((bytes43[0] & 0b00000111) + 1) # 颜色数量不够需要补齐 解包采用掩码提取 +1是容纳更多颜色 而且因为2^0=1 一种颜色没有意义
print(f"颜色数量:{color_number}")
bytes44 = deal(3 * color_number)
print(bytes44.hex(' '))
print("压缩位数")
bytes45 = deal(1)
print(struct.unpack('<B', bytes45)[0])
print(bytes45.hex(' '))
print("子块字节")
bytes46 = deal(1)
print(struct.unpack('<B', bytes46)[0])
print(bytes46.hex(' '))
print("子块数据")
bytes47 = deal(struct.unpack('<B', bytes46)[0])
print(bytes47.hex(' '))
print(bytes47)
print(lzw_decode(bytes47))
print("终止符")
bytes48 = deal(1)
print(bytes48.hex(' '))
print("结束")
bytes49 = deal(1)
print(bytes49.hex(' '))
print(bytes49)