闭俗情侣:在平凡烟火中,点亮爱的永恒星光

核心内容摘要

瑜伽妈妈的绣感完整:身心灵的和谐绽放
扣逼扣到高潮-高清影视一网打尽

【眼球冲击】当“翻白眼流口水咬铁球”的梗,解锁了人类最原始的本能

为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 Extension和Application Extension但是仔细想想注释信息不好说会不会影响其他程序解析gif而且注释块根本就没有给我们填写块标识符的地方或者说其存储逻辑本身没有标识符的位置。

而且使用应用扩展块可以在Application Identifier位置写入 pngkey 用来表示这个块被我们使用同时将Application Authentication Code作为块识别码。

因此我们选择应用扩展块来进行数据存储。

GIF结构/// GIF结构pubstructGif{chunks:VecChunk,}pubenumChunk{Header([u8;6]),LogicalScreenDescriptor(LogicalScreenDescriptor),GlobalColorTable(Vecu

,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:OptionVecu8,pubimage_data:Vecu8,// 包含LZW压缩数据的子块链}pubstructImageDescriptor{publeft:u16,pubtop:u16,pubwidth:u16,pubheight:u16,pubpacked_fields:u8,}pubstructExtensionChunk{pubextension_type:u8,pubdata:Vecu8,// 包括块头长度与子块链}原始数据到结构数据开始前说一句rust的std::io::Cursor好好玩。

其实大体上就是按照gif标准格式从头到尾解析一遍注意是小端序列就行implTryFrom[u8]forGif{typeErrorError;fntry_from(bytes:[u8])-ResultGif{letmutcursorstd::io::Cursor::new(bytes);letmutchunksVec::new();letmutheader[0u8;6];cursor.read_exact(mutheader)?;chunks.push(Chunk::Header(header));// pngkey会事先判断gif头因此这里不判断了letlsdSelf::read_logical_screen_descriptor(mutcursor)?;lethas_gct(lsd.packed_fields0x

!0;letgct_size_factorlsd.packed_fields0x07;chunks.push(Chunk::LogicalScreenDescriptor(lsd));ifhas_gct{letsize2u

pow((gct_size_factor

asu

asusize;letmutgctvec![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{// ,分割图像letimageSelf::read_image_chunk(mutcursor)?;chunks.push(Chunk::Image(image));}0x21{// !拓展块letextensionSelf::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_descriptorR:Read(reader:mutR)-ResultLogicalScreenDescriptor{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_chunkR:Read(reader:mutR)-ResultImageChunk{letmutbuf[0u8;9];reader.read_exact(mutbuf)?;letdescriptorImageDescriptor{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_tableif(descriptor.packed_fields0x

!0{letsize2u

pow(((descriptor.packed_fields0x

07)

asu

asusize;letmutlctvec![0u8;size*3];reader.read_exact(mutlct)?;Some(lct)}else{None};// 读取图像数据LZW压缩数据的子块链letmutlzw_min[0u8;1];reader.read_exact(mutlzw_min)?;letmutimage_datavec![lzw_min[0]];letsubSelf::read_sub_blocks(reader)?;image_data.extend_from_slice(sub);Ok(ImageChunk{descriptor,local_color_table,image_data,})}前半部分跟全局颜色数据的解析基本一致。

后半部分要注意LZW编码长度本身占用一个字节之后才是读子块链。

读取扩展块fnread_extension_chunkR:Read(reader:mutR)-ResultExtensionChunk{letmutext_type[0u8;1];reader.read_exact(mutext_type)?;letmutdataVec::new();letmutsubSelf::read_sub_blocks(reader)?;data.append(mutsub);Ok(ExtensionChunk{extension_type:ext_type[0],data,})}扩展块要说明的是标识头长度固定为

标识头、子块链都在data成员里。

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

fnread_sub_blocksR:Read(reader:mutR)-ResultVecu8{letmutdataVec::new();loop{letmutsize_byte[0u8;1];reader.read_exact(mutsize_byte)?;data.push(size_byte[0]);letsizesize_byte[0]asusize;ifsize0{break;// 子块链结束}letmutblock_datavec![0u8;size];reader.read_exact(mutblock_data)?;data.extend_from_slice(block_data);}Ok(data)}将文本写入应用扩展块由于每个子块最多255字节数据 1字节长度因此对于要写入的信息需要截断填入// ...letmutremaining_datadata;while!remaining_data.is_empty(){letchunk_sizestd::cmp::min(remaining_data.len(),

;ext_data.push(chunk_sizeasu

;ext_data.extend_from_slice(remaining_data[..chunk_size]);remaining_dataremaining_data[chunk_size..];}// ...结构数据到原始数据对于扩展块先前的读取步骤将块头长度与数据和子块链全部读入了data成员因此内部数据原样输出即可外加上扩展标识符就行图片的0x2c应用的0x21。

pubfnas_bytes(self)-ResultVecu8{letmutbytes:Vecu8Vec::new();forchunkinself.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)}

两峰高耸玉门开,一杆金枪捣黄龙-两峰高耸玉门开,一杆金枪捣黄龙应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123