前端JavaScript获取图片文件的真实格式
常见方式判断图片格式
当我们进行前端开发,需要处理图片上传功能,针对图片格式做判断时,常规的方法都是使用文件后缀名来判断,如下代码所示:
input.addEventListener('change', (e) => {const file = e.target.files[0]const format = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase()
}, false)
以上代码,监听上传控件的事件,得到要上传的文件信息,获取文件名称,然后通过获取文件名称截取文件后缀名,以后缀名作为图片文件的格式。这段代码,大部分人都比较熟悉,也有很多场景下是这样来判断图片格式的,但如果我们强行修改了文件的后缀名,则此方法就失效了。我们知道gif格式图片的位深度是8,如果我们强制把位深度为32的png格式的图片后缀名改成gif,这个图片文件依然可以正常使用:
上图所示,就是将png格式文件后缀名改成了gif,图片系统信息显示格式为gif,但是位深度还是32,图像本质上还是png格式的。
这个时候,单纯的通过后缀名来判断图片的格式,就不再准确了,我们需要另外的方式来获取图片文件的真实格式。而这种方式就需要使用到前端二进制相关的知识,见前文介绍深入理解前端字节二进制知识以及相关API。
修改后缀名的方式
几种位图格式之间,是可以相互修改后缀名,图片仍能正常使用> > gif动图后缀名改成其他位图格式,则动效会失效,变成静态图> > 位图格式的后缀名如果改成矢量图svg,则图片失效,将无法使用> > svg图片文件后缀名改成位图格式,图片也将无法使用
图像数据简单说明
不同格式的图像所存储的数据是不一样的,都有自己特殊的数据结构。依据各个格式图像不同的数据结构,我们通过类型数组中的图像数据,就能判断出图片的真实格式。如jpg格式,它的图像数据结构中,最前面2个字节是一个固定取值 0xFFD8
,第三个字节一般也是固定 0xFF
。如png格式,它的图像数据结构中,最前面8个字节就是PNG文件署名域,可以很好的标识出当前图像的格式就是PNG。如bmp格式,它的图像数据结构中,最前面14个字节存储的是文件头信息,而最前面2个字节存储的就是文件类型:BM
。如webp格式,需要从最前面移动8个字节以后,取接下来的4个字节的信息,代表文件类型:WEBP
针对不同位图的的数据判断,可以使用下面表格列出的方式:
格式 | 标识的字节数 | 对应的十进制值 | 偏移量 |
---|---|---|---|
jpg | 3 | 255 216 255 | 0 |
png | 8 | 137 80 78 71 13 10 26 10 | 0 |
gif | 3 | 71, 73, 70 | 0 |
webp | 4 | 87, 69, 66, 80 | 8 |
ico | 4 | 0, 0, 1, 0 | 0 |
bmp | 2 | 66 77 | 0 |
其中,偏移量为0,表示取最前面几个字节的数据;webp的偏移量为8,表示从最前面移动8个字节后,再取4个字节的标识符。上面的表格,已经列出了当前浏览器支持的位图图像,字节判断标识,通过读取相应的数据做对比就得到了真实的格式。
以上几种格式中,bmp、gif、webp取到的数据,都能对应各自特有的署名标识,前面有提到 BM
和 WEBP
,gif格式的则是 GIF
。可以运用字符编码方面的知识,如使用 String.fromCharCode
方法对数值进行转换,具体的前端字符编码知识见前文前端开发中需要搞懂的字符编码
// bmp
String.fromCharCode(66) // B
String.fromCharCode(77) // M// gif
String.fromCharCode(71) // G
String.fromCharCode(73) // I
String.fromCharCode(70) // F// webp
String.fromCharCode(87) // W
String.fromCharCode(69) // E
String.fromCharCode(66) // B
String.fromCharCode(80) // P
gif格式的署名标识是和版本号一起处理的,一般最前面6个字节标识:
'G'、'I'、'F'、'8'、'7(9)'、'a'
。第5个字节可取值7或者9,代表两个不同的版本,即1987年的版本和1989年的版本。
JS读取图片真实格式
当我们了解了前端二进制相关的知识后,就应该知道图片文件也是能通过WebAPI对象,读取到对应的数据:
const reader = new FileReader()
reader.onload = () => {const imgArrayBuffer = reader.resultconst imgUint8Array = new Uint8Array(imgArrayBuffer)
}
reader.readAsArrayBuffer(file)
以上代码,就是通过 FileReader
对象读取文件的数据,这里是作为 ArrayBuffer
来读取的,然后就可以转换成类型数组进行处理了。
读取到图片文件的 Uint8Array
类型数组数据后,根据上文表格中提到的格式字节数据标识,我们以jpg、bmp和webp为例:
imgUint8Array[0] === 66 && imgUint8Array[1] === 77 // bmp 格式
imgUint8Array[0] === 255 && imgUint8Array[1] === 216 && imgUint8Array[3] === 255 // jpg 格式
imgUint8Array[8] === 87 && imgUint8Array[9] === 69 && imgUint8Array[10] === 66 && imgUint8Array[10] === 80 // webp 格式
到此,就可以使用这种方式来读取到图片的真实格式,部分判断代码如下:
// 各格式对应图像数据的标识数值
const IMAGEFORMATS = [{ ext: 'png', data: [137, 80, 78, 71, 13, 10, 26, 10] },{ ext: 'jpg', data: [255, 216, 255] },{ ext: 'gif', data: [71, 73, 70] },{ ext: 'ico', data: [0, 0, 1, 0] },{ ext: 'bmp', data: [66, 77] },{ ext: 'webp', data: [87, 69, 66, 80], offset: 8 }
]// 循环判断文件是否符合某个格式对应的标识数值
for (let i = 0; i < IMAGEFORMATS.length; i++) {const { data, offset, ext } = IMAGEFORMATS[i]if (isEqualFormatPrefix(imgUint8Array, data, offset)) {return ext}
}
不过以上的方式主要是针对位图,如果是svg的图片,则会稍微复杂一些,需要另行处理。
svg格式的判断
svg格式图片是矢量图,对应的数据一般使用 xml
标记语言进行描述,所以我们读取到图像数据后,需要对应的标识署名是 <svg
,如果对应的图像数据中拥有该标识,则大致可以判定为svg格式的图片。<svg
标识有4个符号和字母,对应的数值:60, 115, 118, 103
,接下来我就需要判断图像文件是否有同样的数据了。
imgUint8Array[0] === 60 && imgUint8Array[1] === 115 && imgUint8Array[3] === 118 && imgUint8Array[3] === 103 // svg 格式
以上代码就是简单的判断svg格式了。但是,我们一般的svg图片,图像数据最开始是包含有xml标记语言的 <?xm
标签,这个时候我们根据格式再判断:
if (isEqualFormatPrefix(fileUint8Array, [60, 63, 120, 109], offset)) { // 判断是否以 <?xm 开头if (isHasSignCodes(fileUint8Array, [60, 115, 118, 103])) { // 判断是否包含 <svg 标签return'svg'}
}
注意:以上针对svg格式矢量图的这种判断方式,是以 xml
标记语言的标签符号进行判断的,只能处理通过更改后缀名的方式伪造的图片文件。当我们伪造一个假的文件,包含有 <svg
标签标识时,则可以逃避这种判断。
总结
浏览器支持的图片格式中,除了svg以外,其他几种位图格式,都可以较好的通过读取图像二进制数据的方式判断出图片文件的真实格式,能够防止文件伪造绕开判断,造成不必要的异常等问题。
最后
最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。
有需要的小伙伴,可以点击下方卡片领取,无偿分享
相关文章:

前端JavaScript获取图片文件的真实格式
常见方式判断图片格式 当我们进行前端开发,需要处理图片上传功能,针对图片格式做判断时,常规的方法都是使用文件后缀名来判断,如下代码所示: input.addEventListener(change, (e) > {const file e.target.files[…...

今天面了一个来华为要求月薪25K,明显感觉他背了很多面试题...
最近有朋友去华为面试,面试前后进行了20天左右,包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说,80%的人都会栽在第一轮面试,要不是他面试前做足准备,估计都坚持不完后面几轮面试。 其实&…...

11 Advanced CNN
文章目录GoogLeNetInception Module1x1 Conv计算效果代码实现总结ResNet (残差网络)问题引入梯度消失与传统神经网络的比较代码实现课程来源: 链接对于前篇中所提到问题,设计出的是一种类似于LeNet5的线性结构,而对于大多数问题,简…...

亿级高并发电商项目---万达商城项目搭建(二)
👏作者简介:大家好,我是小童,Java开发工程师,CSDN博客博主,Java领域新星创作者 📕系列专栏:前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶 Ǵ…...

UML术语标准和分类
一、UML术语标准 1.中文UML术语标准 中国软件行业协会(CSIA)与日本UML建模推进协会(UMTP)共同在中国推动的UML专家认证,两个协会共同颁发认证证书、两国互认,CSIA与UMTP共同推出了UML中文术语…...
LeetCode 刷题系列 -- 151. 反转字符串中的单词
给你一个字符串 s ,请你反转字符串中 单词 的顺序。单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。注意:输入字符串 s中可能会存在前导空格、尾随空格或…...

二十二、Gtk4-ListView
GTK 4添加了新的列表对象GtkListView、GtkGridView和GtkColumnView。这个新特性在Gtk API参考—列表小构件概述中有描述。 GTK 4还有其他实现列表的方法。它们是GtkListBox和GtkTreeView,它们是从GTK 3接管的。在Gtk开发博客中有一篇关于Matthias Clasen所写的列表…...

ASP.NET Core3.1实战教程---基于Jquery单文件上传
这个必须记录一下费劲啊!废了我2天的时间,昔日的net快速已经没落....就文件上传都这么费劲。 先说下要求(在线apk文件上传实现手机端整包更新): 1、为了简化需求文件上传和数据提交分开执行 2、选完文件后按钮变成上…...

10 卷积神经网络CNN(基础篇)
文章目录全连接CNN过程卷积过程下采样过程全连接层卷积原理单通道卷积多通道卷积改进多通道总结以及课程代码卷积改进PaddingStride下采样过程大池化层(Max Pooling)简单卷积神经网络的实现课程代码本篇课程来源: 链接部分文本来源参考&#…...

Windows下LuaBridge2.8的环境配置及简单应用
Windows下LuaBridge2.8的环境配置及简单应用 LuaBridge2.8下载链接: https://github.com/vinniefalco/LuaBridge/tags 关于Lua的环境配置可参考以下链接(这里不做简述): https://ufgnix0802.blog.csdn.net/article/details/125341…...

每天10个前端小知识 【Day 10】
前端面试基础知识题 1. es5 中的类和es6中的class有什么区别? 在es5中主要是通过构造函数方式和原型方式来定义一个类,在es6中我们可以通过class来定义类。 class类必须new调用,不能直接执行。 class类执行的话会报错,而es5中…...

【LeetCode】1223. 掷骰子模拟
1223. 掷骰子模拟 题目描述 有一个骰子模拟器会每次投掷的时候生成一个 1 到 6 的随机数。 不过我们在使用它时有个约束,就是使得投掷骰子时,连续 掷出数字 i 的次数不能超过 rollMax[i](i 从 1 开始编号)。 现在,…...

SPSS数据分析软件的安装与介绍(附网盘链接)
🤵♂️ 个人主页:艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞Ǵ…...

2022年38女神节大促美妆、珠宝、母婴、保健电商数据回顾
近期,我们陆续接收到了品牌商家朋友们对于2022年女神节大促期间部分品类的数据需求,希望能对今年的大促活动有一个更宏观的认知、更精准的预测,从而拿到更好的数据效果。 为此,在距离大促开启一个月的备货阶段,鲸参谋决…...
Java笔记-线程同步
目录线程的同步---以三个窗口售票100张为例方式一:同步代码块方式二:同步方法使用同步机制的作用:线程的同步—以三个窗口售票100张为例 (1)问题:卖票的过程出现重票和错票 (2)原因…...

通过python 调用OpenAI api_key提交问题解答
通过python 调用OpenAI api_key提交问题解答✨可以通过网页版的jupyter notebook调用,也可以通过spyder窗口等IDE窗口. 🌼通过python 调用OpenAI api_key接口,可以避免国内网页不能访问的问题。前提是需要自己已经注册了OpenAI帐号ÿ…...

图表控件LightningChart .NET再破世界纪录,支持实时可视化 1 万亿个数据点
LightningChart.NET SDK 是一款高性能数据可视化插件工具,由数据可视化软件组件和工具类组成,可支持基于 Windows 的用户界面框架(Windows Presentation Foundation)、Windows 通用应用平台(Universal Windows Platfor…...

什么是响应性?
响应性: 这个术语在今天的各种编程讨论中经常出现,但人们说它的时候究竟是想表达什么意思呢?本质上,响应性是一种可以使我们声明式地处理变化的编程范式。一个经常被拿来当作典型例子的用例即是 Excel 表格: 这里单元…...

黑马】后台管理176-183
一、新建订单管理的分支二、创建一个订单管理的vue文件进行组件页面的路由配置import Order from ../components/order/Order.vue{path:/orders,component:Order},注意上面的components不要忘记少加一个s!三,获取后台数据面包屑导航粘贴过来文本输入框&a…...
Typescript - 类型守卫(typeof / in / instanceof / 自定义类型保护的类型谓词)通俗易懂详细教程
前言 类型守卫用于获取变量类型信息,通常使用在条件块语句中。类型守卫是返回布尔值的常规函数,接受一个类型并告诉 TypeScript 是否可以缩小到更具体的类型。类型守卫具有唯一的属性,可以确保测试的值返回的是布尔值类型。 TypeScript 使用了…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...

Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

C++实现分布式网络通信框架RPC(2)——rpc发布端
有了上篇文章的项目的基本知识的了解,现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...