判断GIF类型并使用ImageDecoder解析GIF图
一、判断是否为GIF图片类型
在JavaScript中,判断用户上传的文件是否为GIF文件类型时,通常可以通过检查文件的type属性或文件的拓展名来判断,但是由于文件拓展名可以轻易被用户修改,type属性是由浏览器根据文件拓展名猜测得出的,因此这种判断方式并不总是准确的。
为了更准确的判断文件类型,需要读取文件的头部字节,并检查这些字节是否符合GIF文件的规范。
要检查文件头部字节以确定文件是否为GIF格式,可以使用JavaScript的FileReader API来读取文件的前几个字节,并与GIF文件的魔数(Magic Number)进行比较。GIF文件的魔数是GIF87a或GIF89a,它们位于文件的前几个字节中。
// 文件输入元素
const fileInput = document.getElementById('file-input')// 监听文件变化事件
fileInput.addEventListener('change', async fucntion(e) {// 获取用户选择的文件const file = e.target.files[0]if(file) {const isGif = await checkGifFileType(file)if(isGif) {// 是gif} else {// 不是gif}}
})
/*** 检查文件类型是否是gif* * @param {*} file * @returns */
export function checkGifFileType(file) {// 创建一个FileReader对象const reader = new FileReader();// 读取文件的前几个字节reader.readAsArrayBuffer(file.slice(0, 6)); // GIF的魔数只需要6个字节return new Promise((resolve, reject) => {reader.onload = function (e) {// 将ArrayBuffer转换为Uint8Array以便读取字节const arrayBuffer = e.target.result;const uint8Array = new Uint8Array(arrayBuffer);// GIF文件的魔数可以是'GIF87a'或'GIF89a',转换为ASCII码分别为[71, 73, 70, 56, 55, 97]或[71, 73, 70, 56, 57, 97]// 检查文件的前6个字节是否匹配GIF的魔数const gif87a = [71, 73, 70, 56, 55, 97];const gif89a = [71, 73, 70, 56, 57, 97];if ((uint8Array[0] === gif87a[0] &&uint8Array[1] === gif87a[1] &&uint8Array[2] === gif87a[2] &&uint8Array[3] === gif87a[3] &&uint8Array[4] === gif87a[4] &&uint8Array[5] === gif87a[5]) ||(uint8Array[0] === gif89a[0] &&uint8Array[1] === gif89a[1] &&uint8Array[2] === gif89a[2] &&uint8Array[3] === gif89a[3] &&uint8Array[4] === gif89a[4] &&uint8Array[5] === gif89a[5])) {resolve(true); // 是GIF文件} else {resolve(false); // 不是GIF文件}};reader.onerror = function (error) {reject(error);};});
}
二、解析GIF,并在canvas上播放
GIF本质上和视频一样,都是一帧一帧的图片拼合而成,所以,在canvas上播放GIF功能实现的要点,就是获取具体某一帧的资源。
- 使用ImageDecoder API解析GIF每一帧的图像资源;
- 将该图像资源绘制在canvas画布上;
注意:目前ImageDecoder仅Chrome浏览器支持,如果考虑兼容性,可以使用libgif.js等第三方库去解析GIF。
1、资源获取
使用fetch方法获取GIF图片资源,注意跨域问题。
fetch("xxx.gif").then((response) => {// response.body 就是图像资源数据
});
2、使用ImageDecoder解析
imageDecoder对象就包含了一系列的属性和方法,用来对解析好的图像数据进行各种各样的处理。
const imageDecoder = new ImageDecoder({ data: response.body, type: "image/gif"
});
获取GIF第一帧的图形数据,则可以:
imageDecoder.decode({ frameIndex: 0
}).then((result) => {// result 对象就是解析后的结果
});
result 对象包括下面这些属性,其中result.image 的返回值是一个 VideoFrame 对象,包含很多属性和方法,例如,帧图像的编码尺寸,显示尺寸,时间戳,时间间隔等,可以作为 ImageSource 绘制在 canvas 画布上。
{// 解码的图像image: VideoFrame,// 如果为true,则表示该图像包含最终的完整细节输出。complete: boolean
}
result.image.timestamp:当前帧出现的时间戳,单位为微分秒,即万分之一秒
result.image.duration:当前帧持续的时长,单位为微分秒,即万分之一秒
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");context.drawImage(result.image, 0, 0);
3、简单封装
export function giftDecoder(url) {return new Promise((resolve, reject) => {fetch(url).then(async (response) => {// response.body 就是图像资源数据const imageDecoder = new ImageDecoder({ data: response.body, type: "image/gif" });let _d = await imageDecoder.decode({ frameIndex: 0 })console.log(imageDecoder, _d)const track = imageDecoder.tracks.selectedTrack;console.log(track)let arr = []let totalTime = 0for(let i = 0; i < track.frameCount; i++) {let result = await imageDecoder.decode({ frameIndex: i})if(result) {// result 对象就是解析后的结果// 1000000// timestamp:当前帧出现的时间戳,单位为微分秒// duration:当前帧持续的时长,单位为微分秒arr.push(result)if(i == track.frameCount - 1) {totalTime = result.image.timestamp + result.image.duration}} else {reject()break}}console.error('gif数组', arr)resolve({totalTime,list: arr})});})
}
// gif图特殊处理
const gifData = await giftDecoder(gifUrl)console.error('gif图特殊处理', gifData)
三、使用 gifuct-js 库解析gif
gifuct-js是由Matt Way开发的一个轻量级JavaScript库,专门用于处理和创建GIF动图。它提供了强大的API,让开发者能够轻松地解码、编码、操作及优化GIF文件,适用于网页应用或任何需要在前端处理GIF的场景。
项目地址:https://gitcode.com/matt-way/gifuct-js
技术分析
- 解码与编码能力
- Gifuct-js的核心是其高效的GIF解码器和编码器。通过原生JavaScript实现,它能够快速解析GIF文件的逻辑屏幕描述、图像描述、图形控制扩展等信息,同时也能生成符合标准的新GIF文件。
- 图像操作接口
- 该项目提供了丰富的API,支持对帧进行添加、删除、重排、调整透明度等操作。例如,你可以通过addFrame方法将新的帧添加到GIF中,或者通过removeFrame方法移除特定帧,以实现动态效果的定制。
- 性能优化
- 考虑到前端性能,Gifuct-js设计时就注重了内存管理和运行效率。它采用流式处理,减少了不必要的数据复制,从而降低内存占用并提升处理速度。
- 兼容性
- Gifuct-js基于WebAssembly和JavaScript,使其在现代浏览器中具有良好的兼容性。对于不支持WebAssembly的老版本浏览器,项目还提供了一个纯JavaScript的回退方案。
1、安装
npm install gifuct-js
2、解码
此解码器使用**js-binary-schema-parser**解析 GIF 文件。这意味着要解码 GIF 文件,首先需要将其转换为 Uint8Array 缓冲区。
fetch
import { parseGIF, decompressFrames } from 'gifuct-js';var promisedGif = fetch(gifURL).then(resp => resp.arrayBuffer()).then(buff => {var gif = parseGIF(buff);var frames = decompressFrames(gif, true);return gif;});
XMLHttpRequest
import { parseGIF, decompressFrames } from 'gifuct-js';var oReq = new XMLHttpRequest();
oReq.open("GET", gifURL, true);
oReq.responseType = "arraybuffer";oReq.onload = function (oEvent) {var arrayBuffer = oReq.response; // 注意:不是 oReq.responseTextif (arrayBuffer) {var gif = parseGIF(arrayBuffer);var frames = decompressFrames(gif, true);// 处理帧数据}
};oReq.send(null);
结果
decompressFrames(gif, buildPatch) 函数返回一个包含所有 GIF 图像帧及其元数据的数组。
{// 每个像素的调色板索引pixels: [...],// GIF 帧的尺寸(见处置方法)dims: {top: 0,left: 10,width: 100,height: 50},// 此帧应显示的毫秒数delay: 50,// 处置方法(见下文)disposalType: 1,// 指向像素数据的颜色数组colorTable: [...],// 可选的代表透明度的色彩索引(见下文)transparentIndex: 33,// 绘制准备好的 Uint8ClampedArray 颜色补丁信息patch: [...]
}
自动补丁生成:
如果 dcompressFrames() 函数的 buildPatch 参数为 true,解析器不仅会返回解析和解压缩的 GIF 帧,还会为每个 GIF 帧图像创建可直接用于画布的 Uint8ClampedArray 数组,以便使用 ctx.putImageData() 等方式轻松绘制。这是常见需求,但由于它涉及到透明度假设,所以设为可选项。
处置方法:
像素数据是以每个像素的索引列表存储的。这些索引指向 colorTable 数组中的值,表示每个像素应绘制的颜色。GIF 的每一帧可能不是全尺寸,而是一个需要在特定位置绘制的补丁。disposalType 定义了如何在 GIF 画布上绘制该补丁。大多数情况下,该值为 1,表示 GIF 帧应简单地覆盖现有的 GIF 画布,而不改变补丁尺寸之外的任何像素。
透明性:
如果一帧定义了 transparentIndex,则意味着像素数据中与该索引匹配的任何像素不应被绘制。在使用画布绘图时,这表示将此像素的 alpha 值设置为 0。
3、封装
import { parseGIF, decompressFrames } from 'gifuct-js'export function giftDecoder(url) {return new Promise((resolve, reject) => {let arr = []let totalTime = 0if('ImageDecoder' in window) {fetch(url).then(async (response) => {// response.body 就是图像资源数据let arr = []let totalTime = 0const imageDecoder = new ImageDecoder({ data: response.body, type: "image/gif" });let _d = await imageDecoder.decode({ frameIndex: 0 })console.log(imageDecoder, _d)const track = imageDecoder.tracks.selectedTrack;console.log(track)for(let i = 0; i < track.frameCount; i++) {let result = await imageDecoder.decode({ frameIndex: i})if(result) {// result 对象就是解析后的结果// 1000000// timestamp:当前帧出现的时间戳,单位为微分秒// duration:当前帧持续的时长,单位为微分秒arr.push(result)if(i == track.frameCount - 1) {totalTime = result.image.timestamp + result.image.duration}} else {reject()break}}console.error('gif数组', arr, response)resolve({totalTime:totalTime/1000000 ,list: arr})});} else {let oReq = new XMLHttpRequest();oReq.open("GET", url, true);oReq.responseType = "arraybuffer";oReq.onload = async function (oEvent) {let arrayBuffer = oReq.response; // 注意:不是 oReq.responseTextif (arrayBuffer) {let gif = parseGIF(arrayBuffer);let frames = decompressFrames(gif, true);// 处理帧数据let accumulatedTime = 0const _canvas = document.createElement('canvas')const _ctx = _canvas.getContext('2d')_canvas.width = gif.lsd.width_canvas.height = gif.lsd.heightfor(let i = 0; i < frames.length; i++) {let frame = frames[i]// _ctx.clearRect(0, 0, _canvas.width, _canvas.height)let imageData = _ctx.createImageData(frame.dims.width, frame.dims.height)imageData.data.set(frame.patch)_ctx.putImageData(imageData, frame.dims.left, frame.dims.top)let _blob = await canvasToBlob(_canvas, 'image/png')let _url = URL.createObjectURL(_blob)const img = new Image()img.src = _urlawait imgOnload(img)totalTime += frame.delay arr.push({timestamp: accumulatedTime,duration: frame.delay ,img: img})accumulatedTime += frame.delay }resolve({totalTime: totalTime/1000,list: arr})// console.error('xxx', totalTime, arr)} else {reject()}};oReq.send(null);}})
}function canvasToBlob(canvas, type, quality) {return new Promise(resolve => {canvas.toBlob((blob) => {resolve(blob)}, type, quality)})
}function imgOnload(img) {return new Promise(resolve => {img.onload = function() {resolve()}})
}
参考文档
https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame
https://developer.mozilla.org/en-US/docs/Web/API/ImageDecoder
相关文章:
判断GIF类型并使用ImageDecoder解析GIF图
一、判断是否为GIF图片类型 在JavaScript中,判断用户上传的文件是否为GIF文件类型时,通常可以通过检查文件的type属性或文件的拓展名来判断,但是由于文件拓展名可以轻易被用户修改,type属性是由浏览器根据文件拓展名猜测得出的&a…...
数组对象数据修改后页面没有更新,无法进行编辑,校验失效问题
在 Vue 中,当你通过 Object.assign 或其他方式修改了对象中的某个属性时,Vue 并不会触发组件重新渲染,因此表单中的 input 框无法及时更新。这可能导致在修改表单数据后,页面没有更新,而且表单校验也失效的情况。这是因…...
什么是低代码?有什么特点?
低代码是一种高效的软件开发方法,它允许开发者通过图形化界面和预构建的代码块,以最小化传统手写代码的方式快速构建应用程序。这种方法旨在加速应用程序的开发周期,同时降低技术门槛和成本。以下是根据驰骋低代码设计者的观念与主张…...
Kafka 消息保留时长由 24 小时变更为 72 小时的影响分析
目录 Kafka 消息保留时长由 24 小时变更为 72 小时的影响分析Kafka 消息存储机制保留时长对生产速度的影响保留时长对消费速度的影响底层分析与优化建议附加:将 Kafka 消息保留时长从 24 小时更改为 72 小时后,CPU 使用率从 40% 上升到 70% 的现象1. 增加…...
MySQL A表的字段值更新为B表的字段值
MySQL A表的字段值更新为B表的字段值 准备数据表 create table person (id int unsigned auto_increment comment 主键 primary key,uuid varchar(32) not null comment 系统唯一标识符32个长度的字符串,mobile varchar(11) null comment 中国国内手机号,nickn…...
TCP 建链(三次握手)和断链(四次握手)
TCP 建链(三次握手)和断链(四次挥手) 背景简介建链(三次握手)断链(四次挥手)序号及标志位延伸问题为什么建立连接需要握手三次,两次行不行?三次握手可以携带数…...
SpringBoot集成JOOQ加Mybatis-plus使用@Slf4j日志
遇到个问题记录下,就是SpringBoot使用Mybatis和Mybatis-plus时可以正常打印日志,但是JOOQ的操作日志确打印不出来? 下面的解决方法就是将JOOQ的日志单独配置出来,直接给你们配置吧! 在项目的resources目录下创建日志…...
浅谈JavaScript中的对象赋值
目录 常见的对象赋值方式 直接赋值和对象扩展(浅拷贝)两种赋值方式区别 区别 联系 常见的对象赋值方式 1. 直接赋值:this.info this.deviceInfo,将一个对象的引用赋给另一个变量,它们引用同一个对象。 2. 对象扩…...
Java面试题-集合
Java面试题-集合 1、什么是集合?2、集合和数组的区别是什么?3、集合有哪些特点?4、常用的集合类有哪些?5、List, Set, Map三者的区别?6、说说集合框架底层数据结构?7、线程安全的集合…...
从当当网批量获取图书信息
爬取当当网图书数据并保存到本地,使用request、lxml的etree模块、pandas保存数据为excel到本地。 爬取网页的url为: http://search.dangdang.com/?key{}&actinput&page_index{} 其中key为搜索关键字,page_index为页码。 爬取的数据…...
python爬虫之JS逆向——网页数据解析
目录 一、正则 1 正则基础 元字符 基本使用 通配符: . 字符集: [] 重复 位置 管道符和括号 转义符 转义功能 转义元字符 2 正则进阶 元字符组合(常用) 模式修正符 re模块的方法 有名分组 compile编译 二、bs4 1 四种对象 2 导航文档树 嵌套选择 子节点、…...
VL53L4CX TOF开发(2)----修改测距范围及测量频率
VL53L4CX TOF开发.2--修改测距范围及测量频率 概述视频教学样品申请完整代码下载测距范围测量频率硬件准备技术规格系统框图应用示意图生成STM32CUBEMX选择MCU串口配置IIC配置 XSHUTGPIO1X-CUBE-TOF1app_tof.c详细解释测量频率修改修改测距范围 概述 最近在弄ST和瑞萨RA的课程…...
C++之noexcept
目录 1.概述 2.noexcept作为说明符 3.noexcept作为运算符 4.传统throw与noexcept比较 5.原理剖析 6.总结 1.概述 在C中,noexcept是一个关键字,用于指定函数不会抛出异常。如果函数保证不会抛出异常,编译器可以进行更多优化,…...
Kafka之Broker原理
1. 日志数据的存储 1.1 Partition 1. 为了实现横向扩展,把不同的数据存放在不同的 Broker 上,同时降低单台服务器的访问压力,我们把一个Topic 中的数据分隔成多个 Partition 2. 每个 Partition 中的消息是有序的,顺序写入&#x…...
RabbitMQ docker安装及使用
1. docker安装RabbitMQ docker下载及配置环境 docker pull rabbitmq:management # 创建用于挂载的目录 mkdir -p /home/docker/rabbitmq/{data,conf,log} # 创建完成之后要对所创建文件授权权限,都设置成777 否则在启动容器的时候容易失败 chmod -R 777 /home/doc…...
篇3:Mapbox Style Specification
接《篇2:Mapbox Style Specification》,继续解读Mapbox Style Specification。 目录 Spec Reference Root 附录: MapBox Terrain-RGB...
C#WPF数字大屏项目实战11--质量控制
1、区域划分 2、区域布局 3、视图模型 4、控件绑定 5、运行效果 走过路过,不要错过,欢迎点赞,收藏,转载,复制,抄袭,留言,动动你的金手指,财务自由...
第九十七节 Java面向对象设计 - Java Object.Finalize方法
Java面向对象设计 - Java Object.Finalize方法 Java提供了一种在对象即将被销毁时执行资源释放的方法。 在Java中,我们创建对象,但是我们不能销毁对象。 JVM运行一个称为垃圾收集器的低优先级特殊任务来销毁不再引用的所有对象。 垃圾回收器给我们一个…...
【scikit-learn009】异常检测系列:单类支持向量机(OC-SVM)实战总结(看这篇就够了,已更新)
1.一直以来想写下机器学习训练AI算法的系列文章,作为较火的机器学习框架,也是日常项目开发中常用的一款工具,最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下scikit-learn框架OCSVM模型相关知识体系。 3.欢迎批评指正,欢迎互三,跪谢一键三连! 4.欢迎…...
网络管理与运维
文章目录 网络管理与运维概念:传统网络管理:基于SNMP集中管理:基于iMaster NCE的网络管理:传统网络管理方式: 基于SNMP集中管理:交互方式:MIB:版本:SNMPv3配置网管平台&a…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...
