gif编解码python实战
前言:去年曾写过一个维信机器人,目的是为了方便管理我个人建的一个微信群,当时想在维信消息里面做个带UI的小游戏,但是会受到微信消息框的约束,思来想去,就干脆通过gif来呈现吧,大致就是用户在微信群里发命令,机器人就会根据命令做出相应动作,并通过gif的形式呈现出来。
本例代码可以从如下链接中获得
https://github.com/shanlihou/pythonFunc/tree/master/gif
python test.py,可以通过一个小游戏飞行棋,生成对应的gif
python gifHelper.py可以解析当前文件夹下box.gif的文件,为了测试方便
正题:
0x01:GIF头
先上uedit上的二进制截图,被红框框出来的就是gif的头部
下面是如上头部解析后的内容,可以看到有:版本号,宽高
m1:占1位全局颜色列表标志,置位则后面紧跟全局颜色列表
cr3:占3位颜色深度,为取值+1为某一原色位数,则111为7,7+1,所以每个像素颜色深度占3*8位
s1:占1位,排序标志,置位情况下颜色列表按照频率排序
pixel3:占3位,2^(pixel + 1)全局颜色列表大小,010则为2^(2 + 1)=8
bgColor:背景色,在全局列表中索引的位置
W:H:宽高比
接下来13-37字节为全局颜色列表,这个大小为3 * 8,8就是前面的全局颜色列表大小,每三个字节一个rgb值。
下面是python解析gif头并打印的代码
def parseGif(self, fileName):fileRead = open(fileName, 'rb')tmp = fileRead.read(6)print '_' * 60 self.formatPrint(0, 6, tmp, 'ver')tmp = fileRead.read(7)self.formatPrint(6, 8, ord(tmp[0]) + ord(tmp[1]) * 256, 'width')self.formatPrint(8, 10, ord(tmp[2]) + ord(tmp[3]) * 256, 'height')comment = ['m1:cr3:s1:pixel3', 'bgColor', 'W:H']for i in xrange(3):self.formatPrint(i + 10, i + 11, self.get2(ord(tmp[i + 4])), comment[i])pixel = ord(tmp[4]) & 0x7pixel_size = int(math.pow(2, pixel + 1))m = ord(tmp[4]) & 0x80#print 'pixel', pixel_sizeif m == 0x80:tell = fileRead.tell()tmp = fileRead.read(3 * pixel_size)self.formatPrint(tell, tell + 3 * pixel_size, 'rgb * %d' % pixel_size, 'pixel_table')print '_' * 60self.parseFlag(fileRead)
0x02:解析图形控制扩展
接着解析如下红色框出来的块
如下为程序解析后的结果
____________________________________________________________37- 38| 21| flag|38- 39| f9| flag|39- 40| 4| block size|40- 41| 00000101| res3:method3:i1:t1|41- 43| 50| delay time|43- 44| 7| transparent|44- 45| 0| terminator|
____________________________________________________________
首先是第一个标志:21这个标识本块为扩展块,具体类型需要下一字节确认
f9为图形控制扩展块,本例的图形控制扩展块主要用来说明接下来的一帧图片的延迟时间
block size:说明本块的大小,包括当前字节,不包括终结器
res3:method3:i1:t1:
res3:为3位的保留字段
method3:3位的处置方法,当前方法为1,移去当前图片
i1:1位的用户输入标志
t1:1位的透明色标志,置位的话,接下来的透明色索引有意义
delay time:延迟50 * (1/100)秒
transparent:透明色的索引
terminator:终结器,标志块终结,默认为0
下面为当前块的解析代码:
def parseF9(self, fRead):tell = fRead.tell()block_size = ord(fRead.read(1))self.formatPrint(tell, tell + 1, block_size, 'block size')tell += 1tmp = fRead.read(block_size)self.formatPrint(tell, tell + 1, self.get2(ord(tmp[0])), 'res3:method3:i1:t1')tell += 1self.formatPrint(tell, tell + 2, ord(tmp[1]) + ord(tmp[2]) * 256, 'delay time') tell += 2self.formatPrint(tell, tell + 1, ord(tmp[3]), 'transparent')tell += 1tmp = fRead.read(1)self.formatPrint(tell, tell + 1, ord(tmp), 'terminator')print '_' * 60return fRead.read(1)
0x03:解析图像帧
如上是图像帧的帧头,解析后如下所示
____________________________________________________________45- 46| 2c| image|46- 48| 0| x offset|48- 50| 0| y offset|50- 52| 256| width|52- 54| 256| height|54- 55| 00000000| m1:i1:s1:r2:pix3|55- 56| 4| bits per pixel|
2c为图像帧的标志,检测到2c即为一个图像帧的开始
x offset:为x方向的偏移量
y offset:为y方向的偏移量
width:为图像的宽
height:为图像的高
m1:i1:s1:r2:pix3解释如下
m1:占1位,局部颜色列表标志位
i1:占1位,交织标志,决定了图像排列顺序
s1:占1位,决定紧跟着的颜色列表是否分类排列
r2:两位的保留字
pix3:决定接下里的局部颜色列表大小
bits per pixel:这个字节非常重要,他表示接下来要解析的图像的首个要读入数据的位数
0x04:lzw算法
终于激动人心的算法时刻了,lzw是一种压缩算法,就是gif中所运用的编码算法。
要想知道怎么用lzw解码,一定要首先介绍lzw的编码算法
编码:
先上代码
def encode(self):#encode tabledictCode = {}#clear codeclearCode = 1 << self.bit#initial running bitsrunBits = self.bit + 1#end codeendCode = clearCode + 1runCode = endCode + 1for i in range(clearCode):dictCode[chr(i)] = ipre = ''self.putBit(runBits, clearCode)for i in self.data:if pre == '':pre = ielse:strWhole = pre + iif dictCode.has_key(strWhole):pre = strWholeelse:#print runCodedictCode[strWhole] = runCodeself.putBit(runBits, dictCode[pre])pre = irunCode += 1if runCode > 1 << runBits:runBits += 1self.putBit(runBits, dictCode[pre]) runCode += 1if runCode > 1 << runBits:runBits += 1 self.putBit(runBits, endCode)self.putBit(-1)
dictCode:就是编码表
clearcode:是清除码表标志位,每次遇到clearCode,dictCode就要清空,dictCode={},大家可以看到我代码中并没有做这个操作,原因是因为我“懒”,通常情况下当码表达到4096长度时候才需要将clearCode输入缓冲区,并清空码表,但是一般不会达到这种情况,所以我这里就没有判断
runBits:这个是当前向数据流中输出的位数,由于lzw是一种动态位数的编码方式,每一个数据流中的数据的位是在编译过程中不断增加的,初始位根据全局或者局部颜色列表大小决定,比如如果全局表中有7种颜色,先向上去整为8,就是三位,之后再+1,则为4位,因为clearCode要使用2^3,且endcode=clearCode+1=2^3+1 =9,这就占了两位,所以要多加一位
endCode:这个为编码结束位,当检测到endCode,意味着图像数据流结束,为clearCode+1
runCode:当前编码索引,由于编码表从endCode之后开始,所以runCode=endCode+1
正式开始讲解编码过程:
bits per pixel:这个字节非常重要,他表示接下来要解析的图像的首个要读入数据的位数
先向输出流中输出clearCode,再把pre初始化为空,作为前缀,之后,读入第一个像素i,先将pre初始化为i,不做任何操作,继续读入下一个像素i,组成当前字符串pre+i,然后在编码表中比对,看是否有这个key,然后分成两个流程:
如果有dictCode有pre+i这个key,则pre=pre+i并继续读入下一个数据
如果dictCode中没有pre+i这个可以,则更新编码表dictCode[pre+i]=runCode,并将dictCode中pre这个key对应的runCode输出到输出流中,然后让pre赋值为i,并且增加编码索引runCode。
解码:
先上代码
cur = self.fFetch(fRead, RunningBits)while cur != EOFCode:if cur == ClearCode:pre = 0RunningCode = EOFCode + 1RunningBits = BitsPerPixel + 1cur = self.fFetch(fRead, RunningBits)continueif RunningCode == EOFCode + 1:passelif cur == RunningCode - 1:dictCode[RunningCode - 1] = pre + pre[0]else:dictCode[RunningCode - 1] = pre + dictCode[cur][0]pre = dictCode[cur]output += preRunningCode += 1cur = self.fFetch(fRead, RunningBits)if RunningCode == (1 << RunningBits):RunningBits += 1
fFetch:这个函数是从fRead文件流中取出RunningBits个比特,之前也说,lzw算法中,编码的bit长度是逐渐递增的,所以这个runningBits是随着编码表的扩大而增大
cur:这个为当前取出的第一个索引,并将pre赋值为cur,然后取出下一个cur并继续后面循环,然后分两种情况来处理:
第一种情况:当编码表中有这个cur的时候,这时候更新编码表,将pre+索引表中对应cur的这段编码的首位,也就是pre+dictCode[cur][0]放到编码表最后一个的位置,并将pre赋值为cur对应的编码,然后将pre加入到输出流中。
第二种情况:当编码表中没有这个cur的时候,这时候只有一种情况就是cur刚好对应于编码表即将添加的位置,这个将要添加的编码就为pre+pre[0]。要解释为什么,就需要大家回忆一下编码的过程,在我们进行编码时候,遇到了一个pre+i未知,将pre+i加入到编码表中,将pre输出,并用i作为下一个的首位来编码,下一次编码到i+mid+j时候出现未知编码,则将i+mid对应的索引输出,如果i+mid的索引刚好为新添加到编码表中的编码时候,则pre+i=i+mid,所以pre的首和mid的为相等,都为i。这也就是为什么如果我们在解码过程中遇到一个cur的索引刚好为即将加入编码表的位置时候,要添加pre+pre[0]这个编码。
0x05:结束
如上就是gif编解码的python实现。完整版代码在最上方链接中。
鸣谢:在写python实现的时候也是看了这位朋友的博客,讲的还是很详细的
https://blog.csdn.net/wzy198852/article/details/17266507
gif编解码python实战相关推荐
- python 图片base64 编解码,转换成Opencv,PIL.Image图片格式
Python PIL.Image和OpenCV图像格式相互转换 二进制打开图片文件,base64编解码转成Opencv格式: # coding: utf-8 import base64 import ...
- Python 下JSON的两种编解码方式实例解析
概念 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于人阅读和编写.在日常的工作中,应用范围极其广泛.这里就介绍python下它的两种编解码方法: 使 ...
- Netty实战 IM即时通讯系统(八)服务端和客户端通信协议编解码
Netty实战 IM即时通讯系统(八)服务端和客户端通信协议编解码 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 客户端启动流程 实战: 客户端和服务端双向通信 数 ...
- python编码格式有哪些_Python JSON编解码的方式有哪些
Python JSON编解码的方式有哪些 发布时间:2020-11-04 17:52:46 来源:亿速云 阅读:92 今天就跟大家聊聊有关Python JSON编解码的方式有哪些,可能很多人都不太了解 ...
- Python学习教程:Python3内置模块之base64编解码方法小结
Python学习教程:Python3内置模块之base64编解码方法小结 概述 Base64 是网络上最常见的用于传输 8Bit 字节码的编码方式之一,Base64 就是一种基于 64 个可打印字符来 ...
- python 编码解码原理_Python JSON编解码方式原理详解
这篇文章主要介绍了Python JSON编解码方式原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 概念 JSON(JavaScript Ob ...
- Python基于二维码实现的在线编解码系统
目 录 摘 要 I Abstract II 第一章 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 工作环境和背景 2 1.3.1 操作系统 3 1.3.2 编程语言 3 1.3.3编码 ...
- Python学习笔记(八)爬虫基础(正则和编解码)
知识点 正则 正则匹配url,引用re库,将需要匹配的字段用(.*?)来匹配,可以匹配任何字符串.如果有换行,可以用如下方式解决: 1. ([\s\S]*?) 2. re.findall(reg,ht ...
- 【听如子说】-python模块系列-AIS编解码Pyais
Pyais Module Introduce pyais一个简单实用的ais编解码模块 工作中需要和ais打交道,在摸鱼的过程中发现了一个牛逼的模块,对ais编解码感兴趣的可以拿项目学习一下,或者运用 ...
最新文章
- 什么?搞不定Kafka重复消费?
- NSNotificationCenter
- 安卓自动化测试(1)安卓自动化测试原理概念
- 测验1: Python基本语法元素 (第1周)
- 【python数据挖掘课程】十五.Matplotlib调用imshow()函数绘制热图
- CoreCLR源码探索(一) Object是什么
- noip模拟赛 写代码
- vscode删除文件夹,VSCode:删除文件中的所有注释
- python装饰器class_PYTHON里的装饰器能装饰类吗
- 安卓mqtt调试工具_MOTT工具调试阿里云物联网平台
- 【Oracle】ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
- html怎么编辑文字位置,html – 修正文本的位置背景剪辑
- Unity中设置对象匀速移动
- 百度云网盘批量复制文件,在线复制到每个文件夹中PC版
- 图的常见衡量指标及算法调研
- centOS最全下载地址
- 通过XMind Update制作思维导图
- K3老单开发-销售订单计算比例(实际价格反推)
- 全平台小程序开发框架Uni-app重点概览
- A.O.史密斯净水热饮机 -- 直接获得多温度净水,热饮不再等待
热门文章
- 使用JsonFormat映射protobuf和javabean
- dotnet 一些代码审查套路
- 【iOS-iap防护】验证用户付费收据!拒绝iap Cracker!拒绝iap Free!让iphone越狱用户无从下手!
- 微信公众平台开发简要说明 —— 基本原理
- 从赌场逻辑,分析平台币的投资价值 2020-03-03
- windows server上novnc的部署和使用
- Windows 7 fails to install; Status: 0xc0000225
- Linux系统时间的设置
- 怎样设置计算机u盘启动程序,怎么进入BIOS设置U盘启动_如何设置U盘为第一启动项?-192路由网...
- linux pushd 不起作用,Linux中的pushd和popd