Python |GIF 解析与构建(2):状态机解析

Python |GIF 解析与构建(2):状态机解析

目录

Python |GIF 解析与构建(2):状态机解析

引言

一、状态机概述

状态机的优势与改进方向

总结


引言

在《Python |GIF 解析与构建(1):初步解析》中,我们初步进行解析gif文件。

本文将深入探讨如何使用 ** 状态机(State Machine)** 实现 GIF 文件的解析,通过分阶段处理数据流,逐步提取关键信息(如版本、颜色表、图像数据等)。状态机的优势在于将复杂的解析逻辑拆解为多个可管理的状态,每个状态专注于处理特定的数据流片段,从而提高代码的可读性和可维护性。

一、状态机概述

GIF 文件由一系列区块(Block)组成,每个区块包含特定类型的数据(如文件头、颜色表、图像数据、扩展指令等)。状态机的核心思想是:根据当前解析进度,将程序划分为多个状态(State),每个状态负责解析某一类区块或数据片段,并根据输入数据决定下一步跳转的状态。

GIF 解析的主要状态包括

  1. 开头(Header):验证 GIF 文件签名(GIF87aGIF89a)。
  2. 逻辑屏幕标识符(Logical Screen Descriptor):解析画布尺寸、颜色表标志等基础信息。
  3. 全局颜色表(Global Color Table):提取全局调色板(若存在)。
  4. 区块检测(Block Detection):识别后续区块类型(如图像标识符、扩展块等)。
  5. 扩展块处理(Extension Blocks):解析图形控制扩展(如动画延迟)、应用程序扩展(如循环参数)。
  6. 图像标识符(Image Descriptor):解析图像位置、尺寸、局部颜色表(若存在)及压缩数据。
  7. 结束符检测(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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值