admin管理员组

文章数量:1130349

探索qq发送png透明通道丢失的问题

我在qq发送图片的时候注意到有一些图片发出去能保持透明通道,就像左图这样,而右图就惨不忍睹了,透明通道全成黑色了

那么到底是什么原因呢,我将第一幅图用

from PIL import Imageimg  = Image.open("1.png")#打开文件
img.save("2.png")

也就是另存为生成了一个带有Alpha通道的2.png,他们发送到qq的效果如上所示

 

他们相差0.01MB

于是我找他们的不同,先比较十行

first = open("1.png", "rb")#byte读取数据
second = open("2.png", "rb")line=first.read(10)#读10byte
sline=second.read(10)
#方案二byte对齐
row=1
while(line):if line!=sline:#如果不一样print(row)#显示行号print(line)#显示1.png的byte数据print(sline)#显示2.png的byte数据,换行是为了好看,方便对齐line=first.read(10)#读下一行sline=second.read(10)row+=1#行号标记+1if row==10:#读到9行的时候退出,不读完,先比较一部分exit()
while(sline):print(row,sline)line=first.read(10)sline=second.read(10)row+=1
exit()
'''
#方案一行对齐
row=1
line=first.readline()
sline=second.readline()
while(line):if line!=sline:print(row)print(line)print(sline)line=first.readline()sline=second.readline()if row==10:exit()row+=1
while(sline):print(row,sline)line=first.readline()sline=second.readline()row+=1
'''

结果

发现4,5,6,7,8,9都不一样,如果对比更多就是满屏幕了,于是放弃这个想法

那么我们可以通过读png的编码来了解他们的不同,于是去找png的规范写了个读取png格式的python程序

import binascii
import sys
first = open("1.png", "rb")#以二进制byte数据读取png文件
#读取文件类型
head=first.read(8)#读取文件的开头八个字节
print("格式",head[0:8],head[1:4])#0-7,8 输出检查文件类型
'''
#直接读文件的头
head=first.read(32)
print("head",head)
print("格式",head[0:8],head[1:4])#0-7,8
print("Chunk长度",head[8:12],int.from_bytes(head[8:12], byteorder='big'))#8-11,4
print("数据块类型码",head[12:16])#12-15,4
print("数据块数据",head[16:16+int.from_bytes(head[8:12], byteorder='big')])#16-28,13
print("  宽",head[16:16+4],int.from_bytes(head[16:20], byteorder='big'))#16-19,4
print("  高",head[20:20+4],int.from_bytes(head[20:24], byteorder='big'))#20-23,4
print("  图像深度",head[24],head[24])#24,1
print("  颜色类型",head[25],head[25])#25,1
print("  压缩方法",head[26],head[26])#26,1
print("  滤波器方法",head[27],head[27])#27,1
print("  扫描方法",head[28],head[28])#28,1
print("CRC校验码",head[29:33],int.from_bytes(head[29:33], byteorder='big'),)#29-32,4
print()
'''
#用作重定向标准输出流
class stroutput:def __init__(self,blend=1):#默认绑定输出流self.buff=''self.__console__=sys.stdoutif blend==1:self.blend()def blend(self):#绑定方法sys.stdout=selfdef write(self, output_stream):#构造write方法self.buff+=output_streamdef to_console(self):#输出到控制台方法sys.stdout=self.__console__print(self.buff)sys.stdout=selfdef to_file(self, file_path):#输出到文件方法f=open(file_path,'w')sys.stdout=fprint(self.buff)f.close()def flush(self):#构造flush方法self.buff=''def close(self):#关闭还原输出流方法sys.buff=""sys.stdout=self.__console__def getString(self):#返回当前流内容return self.buffdef getSize(self):#返回当前流大小return len(self.buff)def printSize(self):#打印流大小sys.stdout=self.__console__print(len(self.buff))sys.stdout=selfdef print(self,string):#提供print方法sys.stdout=sys.__stdout__print(string)sys.stdout=self#chunk解码
def Chunkdecode(Chunktype,Chunklength,showtype,showdata,showdatadetail,readonly):showtypekey="".join(showtype)#将所有要输出的类型合并成字符串if readonly==0:print(end="")#保留调试位置if Chunktype==b"IHDR":#如果是IHDR块文件头数据,按照语法读data=first.read(Chunklength)#按照长度byte的数值读取数据if showtypekey.find("IHDR")!=-1:#如果是IHDR数据解析IHDR数据if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  宽",data[0:4],int.from_bytes(data[0:4], byteorder='big'))print("  高",data[4:8],int.from_bytes(data[4:8], byteorder='big'))print("  图像深度",data[8],data[8])print("  颜色类型",data[9],data[9])print("  压缩方法",data[10],data[10])print("  滤波器方法",data[11],data[11])print("  扫描方法",data[12],data[12])return data#返回数据供生成校验字符if Chunktype==b"IDAT":#图像数据块data=first.read(Chunklength)if showtypekey.find("IDAT")!=-1: if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"IEND":#图像结束数据data=first.read(Chunklength)if showtypekey.find("IEND")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"sBIT":#样本有效位数据块data=first.read(Chunklength)if showtypekey.find("sBIT")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"cHRM":#基色和白色点数据块data=first.read(Chunklength)if showtypekey.find("cHRM")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"gAMA":#图像Y数据块data=first.read(Chunklength)if showtypekey.find("gAMA")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"PLTE":#调色板数据块data=first.read(Chunklength)if showtypekey.find("PLTE")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"bKGD":#背景颜色数据块data=first.read(Chunklength)if showtypekey.find("bKGD")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"hIST":#图像直方图数据块data=first.read(Chunklength)if showtypekey.find("hIST")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"tRNS":#图像透明数据块data=first.read(Chunklength)if showtypekey.find("tRNS")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"oFFs":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("oFFs")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"pHYs":#物理像素尺寸数据块data=first.read(Chunklength)if showtypekey.find("pHYs")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"sCAL":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("sCAL")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"tIME":#	图像最后修改时间数据块data=first.read(Chunklength)if showtypekey.find("tIME")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"tEXt":#文本信息数据块data=first.read(Chunklength)if showtypekey.find("tEXt")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"zTXt":#压缩文本数据块data=first.read(Chunklength)if showtypekey.find("zTXt")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"fRAc":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("fRAc")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"gIFg":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("gIFg")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"gIFt":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("gIFt")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"gIFx":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("gIFx")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return data#读块函数,默认不输出任何东西,只有参数不为0的输出
def readChunk(readonly=0,showtype=["IHDR","IDAT","IEND","sBIT"],showChuncktype=0,showdata=0,Changeline=0,showdatadetail=0,checkCRC=0,showlength=0):output=stroutput()Chunklengthbyte=first.read(4)#读取长度比特Chunklength=int.from_bytes(Chunklengthbyte, byteorder='big')#转化长度比特为int型if Chunklength==0:#检查Chunk块是不是读取完毕print("png文件读取完了")return False#结尾if showlength!=0:print("Chunk长度",Chunklengthbyte,Chunklength)#如果带有showlength参数,且不为0则打印Chunktype=first.read(4)#从文件读取4byte的数据作为Chunk类型if ((showChuncktype!=0) and ("".join(showtype).find(str(Chunktype,"utf-8"))!=-1)):print("数据块类型码",str(Chunktype))#判断是否是要输出的chunk块CRCdata=Chunktype+Chunkdecode(Chunktype,Chunklength,showtype,showdata,showdatadetail,readonly)#执行分类解码函数,并与chunk类型合成数据块字符串CRC=first.read(4)#读取CRC32的校验码if checkCRC!=0:#如果需要检查校验码CRCcalc=binascii.crc32(CRCdata)#通过数据块字符串算出CRC数值CRCint=int.from_bytes(CRC, byteorder='big')#转化数据中的CRCprint("CRC校验码",CRC,CRCint,CRCint==CRCcalc)#进行对比if Changeline!=0 and readonly==0 and output.getSize()!=0: output.to_console()#是否换行,如果输出流里没输出那么抛弃output.close()return True#执行成功返回def beginRead(showline=0,begin=1,end=0,**kwargs):#启动函数(是否显示行号,开始位置,结束位置)**kwargs是用来打通嵌套函数的,可以映射子函数的参数到主函数key=True#因为python不支持,或者我不知道的空while,暂时用这个来代替line=1#行数统计if showline>0:#空间换时间,把while里的if提出来进行分支,是否显示行号while(key):#如果还有数据块那么接着读取if line>=begin and (end==0 or line<=end):#判断是否启用区间参数,并检查是不是在输出区间begin和end之间output=stroutput()#劫持输出流,目的是做一个拦截器,拦截多余的空行,而不是用全局变量print(str(line)+".",end="")#打印行号,不换行key=readChunk(**kwargs)#传递参数if output.getSize()!=(len(str(line))+1):output.to_console()output.close()else:key=readChunk(readonly=1)#仅仅按格式空读取文件,什么也不输出,readonly是用来进行函数的区分和调试的line+=1else:#不显示行号while(key):if line>=begin and (end==0 or line<=end):#判断是否启用区间参数,并检查是不是在输出区间begin和end之间key=readChunk(**kwargs)#传递参数else:key=readChunk(readonly=1)#仅仅按格式空读取文件,什么也不输出,readonly是用来进行函数的区分和调试的line+=1beginRead(showline=1,begin=1,end=50,Changeline=1,showChuncktype=1,showdatadetail=1,showtype=["IHDR","sBIT"],showdata=1)#启动读取,showChuncktype等参数定义在readChunk子函数中
#显示行号,从1行开始,读50行,块与块之间空行,显示块类型,显示块细节,显示IHDR和sBIT类型

进行对比,程序我就不说了,我都有详细注释,使用参数

beginRead(showline=1,begin=1,end=2,Changeline=1,showChuncktype=1,showdatadetail=1),结果是

通过对比,知道了两个文件间有什么不同,编码的数据块不一样。具体的png数据块介绍给个传送门点这里,说的挺详细的了。就是8字节的头表示格式,也就是上图的第一行,左边是8字节的byte数据,右边是抽取出来的文件类型。然后就是文件头数据块(数据块有很多种,统称chunk数据块),这个肯定要在前面的,它包括了图片的一些信息,具体的解码已将在上图了,标注b的是直接从文件里读出来的byte数据,没有b标注的是已经转换成int型的数据。

通过对比发现这两个图确实是不一样的,但为什么他会失真呢,其实按道理来说带有alpha通道的除非经过处理,不然是不会发生改变的。那么为什么它的透明通道会丢失呢,原因肯定在腾讯对图片进行了压缩处理。我们通过qq点击发送图片的图片右键另存为

发现他已经严重变形了,甚至成了jpg

但是1.png为什么能保持原来的样子,那肯定是因为有缓存,我们去找找

我进入我的qq文档下使用windows的查找什么都没找到,不知道什么问题,还好装了git,使用git bash

$ find . -name "*.png" -size +1M -size -5M

查找当前文件夹下的png后缀的在1M到3M之间的文件

找到了一个可惜不是它,好的野蛮搜索

$ find /c -name "*.png" -size +1M -size -5M

找到了

删除掉temp后打开群聊找到那张图才会真正下载,出现在D:\My Documents\Tencent Files\1806620741\Image\Group下就能找到图片了,将其用其他照片替换掉我们在qq双击打开的就是我们替换的图片,到此确定这个缓存

用对比程序运行看看

first = open("1.png", "rb")#byte读取数据
second = open("LTKU7DO6N8WM8)Q1~S36{]D (2).png", "rb")line=first.read(10)#读10byte
sline=second.read(10)
#方案二byte对齐
row=1
while(line):if line!=sline:#如果不一样print(row)#显示行号print(line)#显示1.png的byte数据print(sline)#显示2.png的byte数据,换行是为了好看,方便对齐line=first.read(10)#读下一行sline=second.read(10)row+=1#行号标记+1
while(sline):print(row,sline)line=first.read(10)sline=second.read(10)row+=1

其实我已经完全能确定就是一张图了,使用md5验证也是可以的,只是md5不能找出不同,结果很正确

没有任何不同

我们用2.png替换他,看看透明通道改变么,注意1.png和2.png块编码方式是不一样的,双击打开,如上图所示,已经被替换了

透明通道完好,说明无关块编码方式,而是在腾讯服务器有1.png的原图,而我上传的2.png一直在被压缩,电脑传不了原图,我们再拿手机试试,原图发送

2.png完全没问题,至此水落石出,只有手机qq才能传高清大图,电脑qq要传就要传文件或者压缩包

细心的朋友可能就知道平时qq群里的图片点开又是另一张图,改动很大,就是因为这个压缩简略图的机制,上传的png会被压缩成jpg丢失透明通道,所以简略图.jpg一看过去是一张图片但是打开后是带透明通道的.png,很多信息就被隐藏起来了,所以才有两个不同的图像叠在一幅图上。

探索qq发送png透明通道丢失的问题

我在qq发送图片的时候注意到有一些图片发出去能保持透明通道,就像左图这样,而右图就惨不忍睹了,透明通道全成黑色了

那么到底是什么原因呢,我将第一幅图用

from PIL import Imageimg  = Image.open("1.png")#打开文件
img.save("2.png")

也就是另存为生成了一个带有Alpha通道的2.png,他们发送到qq的效果如上所示

 

他们相差0.01MB

于是我找他们的不同,先比较十行

first = open("1.png", "rb")#byte读取数据
second = open("2.png", "rb")line=first.read(10)#读10byte
sline=second.read(10)
#方案二byte对齐
row=1
while(line):if line!=sline:#如果不一样print(row)#显示行号print(line)#显示1.png的byte数据print(sline)#显示2.png的byte数据,换行是为了好看,方便对齐line=first.read(10)#读下一行sline=second.read(10)row+=1#行号标记+1if row==10:#读到9行的时候退出,不读完,先比较一部分exit()
while(sline):print(row,sline)line=first.read(10)sline=second.read(10)row+=1
exit()
'''
#方案一行对齐
row=1
line=first.readline()
sline=second.readline()
while(line):if line!=sline:print(row)print(line)print(sline)line=first.readline()sline=second.readline()if row==10:exit()row+=1
while(sline):print(row,sline)line=first.readline()sline=second.readline()row+=1
'''

结果

发现4,5,6,7,8,9都不一样,如果对比更多就是满屏幕了,于是放弃这个想法

那么我们可以通过读png的编码来了解他们的不同,于是去找png的规范写了个读取png格式的python程序

import binascii
import sys
first = open("1.png", "rb")#以二进制byte数据读取png文件
#读取文件类型
head=first.read(8)#读取文件的开头八个字节
print("格式",head[0:8],head[1:4])#0-7,8 输出检查文件类型
'''
#直接读文件的头
head=first.read(32)
print("head",head)
print("格式",head[0:8],head[1:4])#0-7,8
print("Chunk长度",head[8:12],int.from_bytes(head[8:12], byteorder='big'))#8-11,4
print("数据块类型码",head[12:16])#12-15,4
print("数据块数据",head[16:16+int.from_bytes(head[8:12], byteorder='big')])#16-28,13
print("  宽",head[16:16+4],int.from_bytes(head[16:20], byteorder='big'))#16-19,4
print("  高",head[20:20+4],int.from_bytes(head[20:24], byteorder='big'))#20-23,4
print("  图像深度",head[24],head[24])#24,1
print("  颜色类型",head[25],head[25])#25,1
print("  压缩方法",head[26],head[26])#26,1
print("  滤波器方法",head[27],head[27])#27,1
print("  扫描方法",head[28],head[28])#28,1
print("CRC校验码",head[29:33],int.from_bytes(head[29:33], byteorder='big'),)#29-32,4
print()
'''
#用作重定向标准输出流
class stroutput:def __init__(self,blend=1):#默认绑定输出流self.buff=''self.__console__=sys.stdoutif blend==1:self.blend()def blend(self):#绑定方法sys.stdout=selfdef write(self, output_stream):#构造write方法self.buff+=output_streamdef to_console(self):#输出到控制台方法sys.stdout=self.__console__print(self.buff)sys.stdout=selfdef to_file(self, file_path):#输出到文件方法f=open(file_path,'w')sys.stdout=fprint(self.buff)f.close()def flush(self):#构造flush方法self.buff=''def close(self):#关闭还原输出流方法sys.buff=""sys.stdout=self.__console__def getString(self):#返回当前流内容return self.buffdef getSize(self):#返回当前流大小return len(self.buff)def printSize(self):#打印流大小sys.stdout=self.__console__print(len(self.buff))sys.stdout=selfdef print(self,string):#提供print方法sys.stdout=sys.__stdout__print(string)sys.stdout=self#chunk解码
def Chunkdecode(Chunktype,Chunklength,showtype,showdata,showdatadetail,readonly):showtypekey="".join(showtype)#将所有要输出的类型合并成字符串if readonly==0:print(end="")#保留调试位置if Chunktype==b"IHDR":#如果是IHDR块文件头数据,按照语法读data=first.read(Chunklength)#按照长度byte的数值读取数据if showtypekey.find("IHDR")!=-1:#如果是IHDR数据解析IHDR数据if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  宽",data[0:4],int.from_bytes(data[0:4], byteorder='big'))print("  高",data[4:8],int.from_bytes(data[4:8], byteorder='big'))print("  图像深度",data[8],data[8])print("  颜色类型",data[9],data[9])print("  压缩方法",data[10],data[10])print("  滤波器方法",data[11],data[11])print("  扫描方法",data[12],data[12])return data#返回数据供生成校验字符if Chunktype==b"IDAT":#图像数据块data=first.read(Chunklength)if showtypekey.find("IDAT")!=-1: if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"IEND":#图像结束数据data=first.read(Chunklength)if showtypekey.find("IEND")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"sBIT":#样本有效位数据块data=first.read(Chunklength)if showtypekey.find("sBIT")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"cHRM":#基色和白色点数据块data=first.read(Chunklength)if showtypekey.find("cHRM")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"gAMA":#图像Y数据块data=first.read(Chunklength)if showtypekey.find("gAMA")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"PLTE":#调色板数据块data=first.read(Chunklength)if showtypekey.find("PLTE")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"bKGD":#背景颜色数据块data=first.read(Chunklength)if showtypekey.find("bKGD")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"hIST":#图像直方图数据块data=first.read(Chunklength)if showtypekey.find("hIST")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"tRNS":#图像透明数据块data=first.read(Chunklength)if showtypekey.find("tRNS")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"oFFs":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("oFFs")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"pHYs":#物理像素尺寸数据块data=first.read(Chunklength)if showtypekey.find("pHYs")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"sCAL":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("sCAL")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"tIME":#	图像最后修改时间数据块data=first.read(Chunklength)if showtypekey.find("tIME")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"tEXt":#文本信息数据块data=first.read(Chunklength)if showtypekey.find("tEXt")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"zTXt":#压缩文本数据块data=first.read(Chunklength)if showtypekey.find("zTXt")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"fRAc":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("fRAc")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"gIFg":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("gIFg")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"gIFt":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("gIFt")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return dataif Chunktype==b"gIFx":#(专用公共数据块)data=first.read(Chunklength)if showtypekey.find("gIFx")!=-1:if showdata!=0: print("数据块数据",data[0:0+Chunklength])if showdatadetail!=0:print("  大小",len(data))return data#读块函数,默认不输出任何东西,只有参数不为0的输出
def readChunk(readonly=0,showtype=["IHDR","IDAT","IEND","sBIT"],showChuncktype=0,showdata=0,Changeline=0,showdatadetail=0,checkCRC=0,showlength=0):output=stroutput()Chunklengthbyte=first.read(4)#读取长度比特Chunklength=int.from_bytes(Chunklengthbyte, byteorder='big')#转化长度比特为int型if Chunklength==0:#检查Chunk块是不是读取完毕print("png文件读取完了")return False#结尾if showlength!=0:print("Chunk长度",Chunklengthbyte,Chunklength)#如果带有showlength参数,且不为0则打印Chunktype=first.read(4)#从文件读取4byte的数据作为Chunk类型if ((showChuncktype!=0) and ("".join(showtype).find(str(Chunktype,"utf-8"))!=-1)):print("数据块类型码",str(Chunktype))#判断是否是要输出的chunk块CRCdata=Chunktype+Chunkdecode(Chunktype,Chunklength,showtype,showdata,showdatadetail,readonly)#执行分类解码函数,并与chunk类型合成数据块字符串CRC=first.read(4)#读取CRC32的校验码if checkCRC!=0:#如果需要检查校验码CRCcalc=binascii.crc32(CRCdata)#通过数据块字符串算出CRC数值CRCint=int.from_bytes(CRC, byteorder='big')#转化数据中的CRCprint("CRC校验码",CRC,CRCint,CRCint==CRCcalc)#进行对比if Changeline!=0 and readonly==0 and output.getSize()!=0: output.to_console()#是否换行,如果输出流里没输出那么抛弃output.close()return True#执行成功返回def beginRead(showline=0,begin=1,end=0,**kwargs):#启动函数(是否显示行号,开始位置,结束位置)**kwargs是用来打通嵌套函数的,可以映射子函数的参数到主函数key=True#因为python不支持,或者我不知道的空while,暂时用这个来代替line=1#行数统计if showline>0:#空间换时间,把while里的if提出来进行分支,是否显示行号while(key):#如果还有数据块那么接着读取if line>=begin and (end==0 or line<=end):#判断是否启用区间参数,并检查是不是在输出区间begin和end之间output=stroutput()#劫持输出流,目的是做一个拦截器,拦截多余的空行,而不是用全局变量print(str(line)+".",end="")#打印行号,不换行key=readChunk(**kwargs)#传递参数if output.getSize()!=(len(str(line))+1):output.to_console()output.close()else:key=readChunk(readonly=1)#仅仅按格式空读取文件,什么也不输出,readonly是用来进行函数的区分和调试的line+=1else:#不显示行号while(key):if line>=begin and (end==0 or line<=end):#判断是否启用区间参数,并检查是不是在输出区间begin和end之间key=readChunk(**kwargs)#传递参数else:key=readChunk(readonly=1)#仅仅按格式空读取文件,什么也不输出,readonly是用来进行函数的区分和调试的line+=1beginRead(showline=1,begin=1,end=50,Changeline=1,showChuncktype=1,showdatadetail=1,showtype=["IHDR","sBIT"],showdata=1)#启动读取,showChuncktype等参数定义在readChunk子函数中
#显示行号,从1行开始,读50行,块与块之间空行,显示块类型,显示块细节,显示IHDR和sBIT类型

进行对比,程序我就不说了,我都有详细注释,使用参数

beginRead(showline=1,begin=1,end=2,Changeline=1,showChuncktype=1,showdatadetail=1),结果是

通过对比,知道了两个文件间有什么不同,编码的数据块不一样。具体的png数据块介绍给个传送门点这里,说的挺详细的了。就是8字节的头表示格式,也就是上图的第一行,左边是8字节的byte数据,右边是抽取出来的文件类型。然后就是文件头数据块(数据块有很多种,统称chunk数据块),这个肯定要在前面的,它包括了图片的一些信息,具体的解码已将在上图了,标注b的是直接从文件里读出来的byte数据,没有b标注的是已经转换成int型的数据。

通过对比发现这两个图确实是不一样的,但为什么他会失真呢,其实按道理来说带有alpha通道的除非经过处理,不然是不会发生改变的。那么为什么它的透明通道会丢失呢,原因肯定在腾讯对图片进行了压缩处理。我们通过qq点击发送图片的图片右键另存为

发现他已经严重变形了,甚至成了jpg

但是1.png为什么能保持原来的样子,那肯定是因为有缓存,我们去找找

我进入我的qq文档下使用windows的查找什么都没找到,不知道什么问题,还好装了git,使用git bash

$ find . -name "*.png" -size +1M -size -5M

查找当前文件夹下的png后缀的在1M到3M之间的文件

找到了一个可惜不是它,好的野蛮搜索

$ find /c -name "*.png" -size +1M -size -5M

找到了

删除掉temp后打开群聊找到那张图才会真正下载,出现在D:\My Documents\Tencent Files\1806620741\Image\Group下就能找到图片了,将其用其他照片替换掉我们在qq双击打开的就是我们替换的图片,到此确定这个缓存

用对比程序运行看看

first = open("1.png", "rb")#byte读取数据
second = open("LTKU7DO6N8WM8)Q1~S36{]D (2).png", "rb")line=first.read(10)#读10byte
sline=second.read(10)
#方案二byte对齐
row=1
while(line):if line!=sline:#如果不一样print(row)#显示行号print(line)#显示1.png的byte数据print(sline)#显示2.png的byte数据,换行是为了好看,方便对齐line=first.read(10)#读下一行sline=second.read(10)row+=1#行号标记+1
while(sline):print(row,sline)line=first.read(10)sline=second.read(10)row+=1

其实我已经完全能确定就是一张图了,使用md5验证也是可以的,只是md5不能找出不同,结果很正确

没有任何不同

我们用2.png替换他,看看透明通道改变么,注意1.png和2.png块编码方式是不一样的,双击打开,如上图所示,已经被替换了

透明通道完好,说明无关块编码方式,而是在腾讯服务器有1.png的原图,而我上传的2.png一直在被压缩,电脑传不了原图,我们再拿手机试试,原图发送

2.png完全没问题,至此水落石出,只有手机qq才能传高清大图,电脑qq要传就要传文件或者压缩包

细心的朋友可能就知道平时qq群里的图片点开又是另一张图,改动很大,就是因为这个压缩简略图的机制,上传的png会被压缩成jpg丢失透明通道,所以简略图.jpg一看过去是一张图片但是打开后是带透明通道的.png,很多信息就被隐藏起来了,所以才有两个不同的图像叠在一幅图上。

本文标签: 探索qq发送png透明通道丢失的问题