前言中有一张GIF图片,我们想获取它的总帧数,如果图片超过一定的帧数,则不允许用户上传,服务端有很多现成的库可以使用,这个方法不是很友好。前端需要先将gif上传到服务器,服务器解析后返回结果,大大降低了用户体验。那么如何通过上传前的js总帧数来判断呢?本文将分享一个解决方案给大家,并打包成插件发布到npm仓库。欢迎有兴趣的开发者阅读本文。写在前面这个插件已经发布到npm。原生JS编写,支持任何前端框架。如果你对其实现原理不感兴趣,只想用它来解决你的实际问题,可以直接通过npm/yarn安装。命令如下:#yarninstallyarnaddgif-parser-web#npminstallnpminstallgif-parser-web--保存文件地址,请移步:README.md思想分析我们都知道,不管是什么文件在计算机中,它是以流的形式存储的,所以我们可以通过读取文件流来获取它的所有信息。Gif类型的文件也是如此,我们只要知道它的文件流结构,就可以按照它的规则进行解析和读取。什么是GifGif的全称是GraphicsInterchangeFormat,是一种以8位颜色再现真彩色图像的位图。使用LZW压缩算法进行编码,可以有效减少图像文件在互联网上的传输时间。我们在网站上看到的动人表情基本上都是Gif格式的。组成结构上面说了,我们要解析gif,首先要知道它的文件流结构。在What'sInAGIF网站中,我们知道它是由很多不同类型的块组成的,如下:,局部颜色表(LocalColorTable)。控制块:图形控制扩展。图形渲染块:纯文本扩展(PlainTextExtension)、图像描述符(ImageDescriptor)。特殊用途块:应用程序扩展(ApplicationExtension)、注释扩展(CommentExtension)、数据流结束标记(Trailer)。图像数据块:图像数据(ImageData)。分析原理了解了gif的组成结构之后,我们来看下如何获取它的数据流,如下:读取Gif图片文件(从url读取或上传本地文件类型数据);read将取出的数据转换成arrayBuffer;将arrayBuffer放入DataView;使用DataView底层的相关API读取十六进制码;对十六进制码进行解码,得到图像的信息。其解码过程如下图所示:沿着箭头从Header开始读取,直到PlainTextExtension完成第一帧的读取,其中GlobalColorTable、ApplicationExtension、CommentExtension、LocalColorTable、PlainTextExtension不一定存在。接下来,重复GraphicControlExtension、ImageDescriptor、ImageData读取剩余的帧图像数据。直到Trailer标志被读取,整个Gif的读取就完成了。GIF文件流图注意:在读取过程中,每个chunk都有自己特殊的编码标志。数据块分析了解了gif的构成之后,我们来看一下具体每个数据块的编码信息。HeaderBlock这个数据块用来标记数据流的开始。它位于文件头数据流的上下文中。它包含gif的签名和版本信息。它必须存在,而且只有一个。该块在数据流中占6个字节,其中签名和版本信息各占3个字节,即:数据流中0-2位置的元素必须代表签名信息数据中3-5位置的元素streamofgif必须指明gif的版本信息。我们以89a格式的gif为例。它的Header信息如下:Signature的十六进制值为47、49、46,将其转换成Unicode编码字符后,将是:“G”、“I”、“F”版本的十六进制值分别是38、39、61,转换成Unicode编码后的字符分别是:“8”、“9”、“a”;GIFheaderblocklayoutus让我们看看如何用代码读取它。//假设我们已经获取到dataViewconstsignature=dataView.getUint16(0);//使用getUint16方法从0位置开始连续获取2个字节的值,并转换成Unicode:GIconstversion=dataView.getUint16(2);//使用getUint16方法连续获取从第2个位置开始的2个字节的值,转换成Unicode编码:F8LogicalScreenDescriptor该数据块定义了设备需要的图像显示参数,位于Header数据块后面,它必须存在且只有一个,其值的坐标是相对于虚拟屏幕的左上角计算的。该块在数据流中占用7个字节,包含的信息如下:CanvasWidth图像的宽度(以像素为单位),占用2个字节的空间。画布高度图像的高度(以像素为单位),占用2个字节。PackedFields压缩字段,占用1个字节空间,其中包含4个值GlobalColorTableFlag全局颜色标志,用于标识全局颜色表。如果值为0,表示没有全局色块;如果值为1,则表示全局色块跟随此块。ColorResolution颜色分辨率,即颜色的位数,有1位、8位、16位、32位等。在gif格式的图像定义中,其颜色不能超过256位,其深度不能超过8位。SortFlag排序标志,0不设置,1按重要性降序排序,最重要的颜色在前。SizeofGlobalColorTable全局色表的大小,如果值为1,则该字段的值用于计算全局色表包含的字节数。BackgroundColorIndex背景颜色索引,描述全局颜色表的索引,背景颜色用于屏幕上未被图像覆盖的像素点的颜色。如果全局颜色标志设置为0,则该字段将被忽略。像素纵横比用于计算原始图像中像素纵横比近似值的系数。如果该值不为0,则近似值计算为:(N+15)/64,其中N为像素纵横比,其值为像素宽度与其高度的商。GIF逻辑屏幕描述符块布局我们使用代码来获取它的宽度和高度。//假设我们有dataViewconstwidth=this.dataView.getUint16(6,true);constheight=this.dataView.getUint16(8,true);全局颜色表这个数据块包含一个颜色表,由红-绿-蓝三元组组成的字节序列。如前所述,它不一定存在,如果存在,它将位于LogicalScreenDescriptor块的后面。占用的字节数为3*2^(N+1),N为全局色表的大小+1。数据流中只有一个数据块,如下图所示。GIF全局色表块布局我们来看看代码的实现。让pos=0;constPaletteColorsRGB=[];constgifInfo={}//解析全局调色板constunpackedField=getBitArray(dataView.getUint8(10));if(unpackedField[0]){constglobalPaletteSize=getPaletteSize(unpackedField);gifInfo.globalPalette=true;//计算全局调色板的大小gifInfo.globalPaletteSize=globalPaletteSize/3;//调整指针位置pos+=globalPaletteSize;//遍历得到该区域的所有颜色并保存for(leti=0;i
