当前位置: 首页 > news >正文

为pngme拓展对gif格式支持

为pngme拓展对gif格式支持

  • 引言
  • GIF格式简读
  • GIF结构
  • 原始数据到结构数据
    • 读屏幕逻辑数据
    • 读图像数据
    • 读取扩展块
    • 读取子块链
  • 将文本写入应用扩展块
  • 结构数据到原始数据

引言

前序文章为pngme拓展加密功能与jpg格式支持-CSDN博客,其中所提到的东西,本文不会重复提及,除了我的演示项目地址Smart-Space/pngkey: 将信息文本或密文写入PNG、JPG或GIF文件。

GIF格式文件也是一种常见的图片,所以我决定为pngkey添加对gif格式的支持,这样,对于三大图片格式(png, jpg, gif)都能用了。本文使用的gif格式为89a


GIF格式简读

中文文章参考GIF图解与压缩详解-CSDN博客,细节部分本文不会提及。

不同于PNG与JPG的通用块结构,GIF包括:

  • 文件头
  • 逻辑屏幕标识符
  • 全局颜色列表
  • 图像块若干
  • 拓展块若干
  • 文件结尾

每个数据块的数据解释不尽相同,因此,需要构建多个块结构,同时实现各个块类型从原始数据到结构数据,以及其逆变换的方法。

另外,我们能写入的块为Comment ExtensionApplication Extension,但是仔细想想,注释信息不好说会不会影响其他程序解析gif,而且注释块根本就没有给我们填写块标识符的地方,或者说,其存储逻辑本身没有标识符的位置。而且,使用应用扩展块,可以在Application Identifier位置写入" pngkey "用来表示这个块被我们使用,同时将Application Authentication Code作为块识别码。因此,我们选择应用扩展块来进行数据存储。

GIF结构

/// GIF结构pubstructGif{chunks:Vec<Chunk>,}pubenumChunk{Header([u8;6]),LogicalScreenDescriptor(LogicalScreenDescriptor),GlobalColorTable(Vec<u8>),Image(ImageChunk),Extension(ExtensionChunk),Trailer,}pubstructLogicalScreenDescriptor{pubwidth:u16,pubheight:u16,pubpacked_fields:u8,pubbackground_color_index:u8,pubpixel_aspect_ratio:u8,}pubstructImageChunk{pubdescriptor:ImageDescriptor,publocal_color_table:Option<Vec<u8>>,pubimage_data:Vec<u8>,// 包含LZW压缩数据的子块链}pubstructImageDescriptor{publeft:u16,pubtop:u16,pubwidth:u16,pubheight:u16,pubpacked_fields:u8,}pubstructExtensionChunk{pubextension_type:u8,pubdata:Vec<u8>,// 包括块头长度与子块链}

原始数据到结构数据

开始前说一句,rust的std::io::Cursor好好玩😋。

其实大体上就是按照gif标准格式从头到尾解析一遍,注意是小端序列就行:

implTryFrom<&[u8]>forGif{typeError=Error;fntry_from(bytes:&[u8])->Result<Gif>{letmutcursor=std::io::Cursor::new(bytes);letmutchunks=Vec::new();letmutheader=[0u8;6];cursor.read_exact(&mutheader)?;chunks.push(Chunk::Header(header));// pngkey会事先判断gif头,因此这里不判断了letlsd=Self::read_logical_screen_descriptor(&mutcursor)?;lethas_gct=(lsd.packed_fields&0x80)!=0;letgct_size_factor=lsd.packed_fields&0x07;chunks.push(Chunk::LogicalScreenDescriptor(lsd));ifhas_gct{letsize=2u16.pow((gct_size_factor+1)asu32)asusize;letmutgct=vec![0u8;size*3];// RGB / 1 bytecursor.read_exact(&mutgct)?;chunks.push(Chunk::GlobalColorTable(gct));}// 读取所有数据直到Trailerloop{letmutblock_type=[0u8;1];cursor.read_exact(&mutblock_type)?;matchblock_type[0]{0x2c=>{// ','分割图像letimage=Self::read_image_chunk(&mutcursor)?;chunks.push(Chunk::Image(image));}0x21=>{// '!'拓展块letextension=Self::read_extension_chunk(&mutcursor)?;chunks.push(Chunk::Extension(extension));}0x3b=>{// 结尾chunks.push(Chunk::Trailer);break;}_=>{returnErr("Invalid GIF block type".into());}}}Ok(Gif{chunks})}}

读屏幕逻辑数据

fnread_logical_screen_descriptor<R:Read>(reader:&mutR)->Result<LogicalScreenDescriptor>{letmutbuf=[0u8;7];reader.read_exact(&mutbuf)?;Ok(LogicalScreenDescriptor{width:u16::from_le_bytes([buf[0],buf[1]]),height:u16::from_le_bytes([buf[2],buf[3]]),packed_fields:buf[4],background_color_index:buf[5],pixel_aspect_ratio:buf[6],})}

这没什么好说的,七个字节直接读就完事了,当packed_fields的最高位置位时表示有全局颜色列表GlobalColorTable

读图像数据

fnread_image_chunk<R:Read>(reader:&mutR)->Result<ImageChunk>{letmutbuf=[0u8;9];reader.read_exact(&mutbuf)?;letdescriptor=ImageDescriptor{left:u16::from_le_bytes([buf[0],buf[1]]),top:u16::from_le_bytes([buf[2],buf[3]]),width:u16::from_le_bytes([buf[4],buf[5]]),height:u16::from_le_bytes([buf[6],buf[7]]),packed_fields:buf[8],};// 检查局部调色板letlocal_color_table=if(descriptor.packed_fields&0x80)!=0{letsize=2u16.pow(((descriptor.packed_fields&0x07)+1)asu32)asusize;letmutlct=vec![0u8;size*3];reader.read_exact(&mutlct)?;Some(lct)}else{None};// 读取图像数据(LZW压缩数据的子块链)letmutlzw_min=[0u8;1];reader.read_exact(&mutlzw_min)?;letmutimage_data=vec![lzw_min[0]];letsub=Self::read_sub_blocks(reader)?;image_data.extend_from_slice(&sub);Ok(ImageChunk{descriptor,local_color_table,image_data,})}

前半部分跟全局颜色数据的解析基本一致。后半部分要注意,LZW编码长度本身占用一个字节,之后才是读子块链。

读取扩展块

fnread_extension_chunk<R:Read>(reader:&mutR)->Result<ExtensionChunk>{letmutext_type=[0u8;1];reader.read_exact(&mutext_type)?;letmutdata=Vec::new();letmutsub=Self::read_sub_blocks(reader)?;data.append(&mutsub);Ok(ExtensionChunk{extension_type:ext_type[0],data,})}

扩展块要说明的是,标识头长度(固定为11)、标识头、子块链都在data成员里。

读取子块链

子块链在gif标准格式里才是通用的,一字节长度信息+数据信息作为一个单元。

fnread_sub_blocks<R:Read>(reader:&mutR)->Result<Vec<u8>>{letmutdata=Vec::new();loop{letmutsize_byte=[0u8;1];reader.read_exact(&mutsize_byte)?;data.push(size_byte[0]);letsize=size_byte[0]asusize;ifsize==0{break;// 子块链结束}letmutblock_data=vec![0u8;size];reader.read_exact(&mutblock_data)?;data.extend_from_slice(&block_data);}Ok(data)}

将文本写入应用扩展块

由于每个子块最多255字节数据 + 1字节长度,因此,对于要写入的信息,需要截断填入:

// ...letmutremaining_data=data;while!remaining_data.is_empty(){letchunk_size=std::cmp::min(remaining_data.len(),255);ext_data.push(chunk_sizeasu8);ext_data.extend_from_slice(&remaining_data[..chunk_size]);remaining_data=&remaining_data[chunk_size..];}// ...

结构数据到原始数据

对于扩展块,先前的读取步骤将块头长度与数据和子块链全部读入了data成员,因此内部数据原样输出即可,外加上扩展标识符就行(图片的0x2c,应用的0x21)。

pubfnas_bytes(&self)->Result<Vec<u8>>{letmutbytes:Vec<u8>=Vec::new();forchunkin&self.chunks{matchchunk{Chunk::Header(header)=>{bytes.write_all(header)?;}Chunk::LogicalScreenDescriptor(lsd)=>{bytes.write_all(&lsd.width.to_le_bytes())?;bytes.write_all(&lsd.height.to_le_bytes())?;bytes.write_all(&[lsd.packed_fields])?;bytes.write_all(&[lsd.background_color_index])?;bytes.write_all(&[lsd.pixel_aspect_ratio])?;}Chunk::GlobalColorTable(gct)=>{bytes.write_all(gct)?;}Chunk::Image(image)=>{bytes.write_all(&[0x2c])?;bytes.write_all(&image.descriptor.left.to_le_bytes())?;bytes.write_all(&image.descriptor.top.to_le_bytes())?;bytes.write_all(&image.descriptor.width.to_le_bytes())?;bytes.write_all(&image.descriptor.height.to_le_bytes())?;bytes.write_all(&[image.descriptor.packed_fields])?;ifletSome(lct)=&image.local_color_table{bytes.write_all(lct)?;}bytes.write_all(&image.image_data)?;}Chunk::Extension(extension)=>{bytes.write_all(&[0x21])?;bytes.write_all(&[extension.extension_type])?;bytes.write_all(&extension.data)?;}Chunk::Trailer=>{bytes.write_all(&[0x3b])?;}}}Ok(bytes)}
http://www.jsqmd.com/news/312927/

相关文章:

  • 哪个品牌的呼叫中心更好用 功能与口碑双优排行
  • 长沙英语雅思培训机构推荐.2026权威测评出国雅思辅导机构口碑榜
  • 2026年北京全屋定制品牌推荐:五大标杆品牌综合实力排名揭晓
  • 浏阳焰火燃放生产厂家哪家好,性价比和口碑综合对比
  • 江西靠谱的消防考证服务,顶九消防的课程费用多少钱?
  • 天津英语雅思培训机构推荐。2026权威测评出国雅思辅导机构口碑榜
  • 探寻都得利PE给水管评价,都得利给水管公司服务怎么样
  • 盘点2026年杭州可靠的音乐剧艺考机构,艺升艺考榜上有名
  • 全屋定制品牌怎么对比选择?2026年北京品牌推荐与排名,解决服务与性价比核心痛点
  • 分析双螺杆挤出机资深厂商,有哪些好用且口碑好的品牌
  • 【多目标优化】基于遗传算法的高斯过程回归GPR的多目标优化(MOOP)附Matlab代码
  • 天津英语雅思培训机构推荐、2026权威测评出国雅思辅导机构口碑榜
  • Python 端口扫描器(新手)
  • 天津英语雅思培训机构推荐.2026权威测评出国雅思辅导机构口碑榜
  • 2026年智能床OEM代工厂家推荐:智造升级趋势评测,涵盖研发与量产场景核心技术痛点
  • 天津英语雅思培训机构推荐,2026权威测评出国雅思辅导机构口碑榜
  • 2026年北京全屋定制品牌推荐:智能高定趋势横向评测,涵盖平层与别墅场景整合痛点
  • 【计算机毕业设计案例】基于微信小程序的书院预约系统基于SpringBoot+微信小程序的书院预约系统的设计与实现(程序+文档+讲解+定制)
  • 小瓶盖大杠杆:再互动解码元气森林“扫码赢红包”的精准运营逻辑
  • 【计算机毕业设计案例】基于ssm+Android的学籍异动管理平台基于ssm的Android的学籍异动管理平台系统小程序app(程序+文档+讲解+定制)
  • 大航海时代ol台服找Call记(二) 跟随Call
  • 2026年1月最新权威发布:中国电动床OEM代工厂家综合实力TOP5全景解析(基于2025年度市场数据)
  • 2026年电动床OEM代工厂家推荐:权威评测与排名,解决供应链匹配与品控痛点
  • 社交机器人全球落地加速!医疗/教育/客服全面渗透,接受度达74.5%,机遇与挑战并存
  • 数据智能服务产业发展研究报告:技术+数据双驱动,四大模式+全产业链落地,2035年MaaS市场达3672亿美元
  • ​安永AI银行白皮书:智能体驱动,五阶成熟度+中国实践,2027年生成式AI市场达35亿元
  • ​2030年企业永续创新指南:AI+量子双驱动,五大预测重塑商业范式
  • 2026无线低延迟游戏耳机推荐:罗技G PRO X 2凭什么成为职业选手首选?
  • 优阅达再度获得 HubSpot 亚太区合作伙伴表彰,荣膺 2025 最佳增长合作伙伴