判断GIF类型并使用ImageDecoder解析GIF图

2024-05-31 14:12

本文主要是介绍判断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功能实现的要点,就是获取具体某一帧的资源。

  1. 使用ImageDecoder API解析GIF每一帧的图像资源;
  2. 将该图像资源绘制在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图的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1018148

相关文章

MySQL中优化CPU使用的详细指南

《MySQL中优化CPU使用的详细指南》优化MySQL的CPU使用可以显著提高数据库的性能和响应时间,本文为大家整理了一些优化CPU使用的方法,大家可以根据需要进行选择... 目录一、优化查询和索引1.1 优化查询语句1.2 创建和优化索引1.3 避免全表扫描二、调整mysql配置参数2.1 调整线程数2.

C#中SortedSet的具体使用

《C#中SortedSet的具体使用》SortedSet是.NETFramework4.0引入的一个泛型集合类,它实现了一个自动排序的集合,内部使用红黑树数据结构来维护元素的有序性,下面就来介绍一下如... 目录基础概念主要特性创建和初始化基本创建方式自定义比较器基本操作添加和删除元素查询操作范围查询集合运

C# Opacity 不透明度的具体使用

《C#Opacity不透明度的具体使用》本文主要介绍了C#Opacity不透明度的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录WinFormsOpacity以下是一些使用Opacity属性的示例:设置窗体的透明度:设置按钮的透

99%的人都选错了! 路由器WiFi双频合一还是分开好的专业解析与适用场景探讨

《99%的人都选错了!路由器WiFi双频合一还是分开好的专业解析与适用场景探讨》关于双频路由器的“双频合一”与“分开使用”两种模式,用户往往存在诸多疑问,本文将从多个维度深入探讨这两种模式的优缺点,... 在如今“没有WiFi就等于与世隔绝”的时代,越来越多家庭、办公室都开始配置双频无线路由器。但你有没有注

Python中的sort()和sorted()用法示例解析

《Python中的sort()和sorted()用法示例解析》本文给大家介绍Python中list.sort()和sorted()的使用区别,详细介绍其参数功能及Timsort排序算法特性,涵盖自适应... 目录一、list.sort()参数说明常用内置函数基本用法示例自定义函数示例lambda表达式示例o

Go语言使用net/http构建一个RESTful API的示例代码

《Go语言使用net/http构建一个RESTfulAPI的示例代码》Go的标准库net/http提供了构建Web服务所需的强大功能,虽然众多第三方框架(如Gin、Echo)已经封装了很多功能,但... 目录引言一、什么是 RESTful API?二、实战目标:用户信息管理 API三、代码实现1. 用户数据

在ASP.NET项目中如何使用C#生成二维码

《在ASP.NET项目中如何使用C#生成二维码》二维码(QRCode)已广泛应用于网址分享,支付链接等场景,本文将以ASP.NET为示例,演示如何实现输入文本/URL,生成二维码,在线显示与下载的完整... 目录创建前端页面(Index.cshtml)后端二维码生成逻辑(Index.cshtml.cs)总结

Spring Integration Redis 使用示例详解

《SpringIntegrationRedis使用示例详解》本文给大家介绍SpringIntegrationRedis的配置与使用,涵盖依赖添加、Redis连接设置、分布式锁实现、消息通道配置及... 目录一、依赖配置1.1 Maven 依赖1.2 Gradle 依赖二、Redis 连接配置2.1 配置 R

Python WSGI HTTP服务器Gunicorn使用详解

《PythonWSGIHTTP服务器Gunicorn使用详解》Gunicorn是Python的WSGI服务器,用于部署Flask/Django应用,性能高且稳定,支持多Worker类型与配置,可处... 目录一、什么是 Gunicorn?二、为什么需要Gunicorn?三、安装Gunicorn四、基本使用启

MySQL 临时表创建与使用详细说明

《MySQL临时表创建与使用详细说明》MySQL临时表是存储在内存或磁盘的临时数据表,会话结束时自动销毁,适合存储中间计算结果或临时数据集,其名称以#开头(如#TempTable),本文给大家介绍M... 目录mysql 临时表详细说明1.定义2.核心特性3.创建与使用4.典型应用场景5.生命周期管理6.注