[JS真好玩] 图片文件没后缀,怎么判断图片类型?

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情

这是我的专栏《JS真好玩》,将教你用JS实现一些有趣的东西。JS可以直接在浏览器运行,也可以在Node中运行,你可以跟我学习用JS做好玩儿的事情。感谢大家关注~文章求赞噢!祝大家早日变成一名「前端极客」!

欢迎阅读:本专栏最受欢迎文章《用JS找到哪个小坏蛋给我连点2次赞》

背景

最近我在处理图片,遇到个问题:从网上下载的图片,都没有后缀标明文件类型。我希望写代码识别他们的后缀,并自动改名字。

关于图片存储

文件用后缀标明文件类型,是给计算机的「文件管理器」和人类看的。「文件管理器」可以直接读取文件名,知道用什么软件打开文件,「人类」也可以直观的从文件名中知道类型。

但文件名后缀不是决定文件类型的(所以人类可以任意修改后缀,没有限制)。文件中,通常有 Magic Number 标识文件类型。这个 Magic Number 通常位于文件的头部。一个文件其实就是一个长的二进制序列,头部就是这些二进制序列的前几个字节。

以下来自维基百科对 Magic Number 的一些案例:

  • GIF image files have the ASCII code for "GIF89a" (47 49 46 38 39 61) or "GIF87a" (47 49 46 38 37 61)
  • JPEG image files begin with FF D8 and end with FF D9. JPEG/JFIF files contain the ASCII code for "JFIF" (4A 46 49 46) as a null terminated string. JPEG/Exif files contain the ASCII code for "Exif" (45 78 69 66) also as a null terminated string, followed by more metadata about the file.
  • PNG image files begin with an 8-byte signature which identifies the file as a PNG file and allows detection of common file transfer problems: \211 P N G \r \n \032 \n (89 50 4E 47 0D 0A 1A 0A). That signature contains various newline characters to permit detecting unwarranted automated newline conversions, such as transferring the file using FTP with the ASCII transfer mode instead of the binary mode.

上面提到的"GIF89a" (47 49 46 38 39 61),引号里是ASCII,括号里是16进制,你可以去我写的工具里轻松转换:tool.hullqin.cn/byte-parser… ,工具介绍文章在《手写解析uin8数组的工具:解析二进制字节,太快太方便了!》

方案一:imageinfo

安装

npm i imageinfo
复制代码

在NodeJS中使用

const imageinfo = require('imageinfo');
const img = fs.readFileSync('图片文件路径');
const info = imageinfo(img);
console.log(info);
console.log(info.format);
console.log("Data is type:", info.mimeType);
console.log("  Size:", img.length, "bytes");
console.log("  Dimensions:", info.width, "x", info.height);
复制代码

得到info后,使用info.format.toLowerCase()就能获得文件后缀。

这个库比较简洁,只能识别png jpg gif swf。如果文件未识别到图片类型,info就是false了,要注意特殊处理这种情况。

源码

imageinfo源码非常简单:

module.exports = function imageInfo(buffer, path) {
 var pngSig = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
 var jpgSig = [0xff, 0xd8, 0xff];
 var gifSig = [0x47, 0x49, 0x46, 0x38, [0x37, 0x39], 0x61];
 var swfSig = [[0x46, 0x43], 0x57, 0x53];

 if (checkSig(buffer, 0, pngSig)) return imageInfoPng(buffer);
 if (checkSig(buffer, 0, jpgSig)) return imageInfoJpg(buffer);
 if (checkSig(buffer, 0, gifSig)) return imageInfoGif(buffer);
 if (checkSig(buffer, 0, swfSig)) return imageInfoSwf(buffer);

 return false;
};
复制代码

可以看到它定义了一些头部标识,然后通过checkSig去匹配。我们再看看checkSig

function checkSig(buffer, offset, sig) {
 var len = sig.length;
 for (var i = 0; i < len; i++) {
  var b = buffer[i+offset],
   s = sig[i],
   m = false;

  if ('number' == typeof s) {
   m = s === b;
  }
  else {
   for (var k in s) {
    var o = s[k];
    if (o === b) {
     m = true;
    }
   }
  }

  if (!m) {
   return false;
  }
 }

 return true;
}
复制代码

也很简单,就是拿文件头部几个字节和之前定义的头部标识进行逐个匹配,全匹配成功,返回true,否则就返回false。

方案二:file-type

这个库不像imagetype走简洁路线,file-type功能更强大,能识别的类型更多。同时支持NodeJS环境和浏览器环境。

官方介绍如下:

The file type is detected by checking the magic number of the buffer.

This package is for detecting binary-based file formats, not text-based formats like .txt.csv.svg, etc.

意思是,这个库通过检查文件二进制序列的 magic number 来判断文件类型。不支持 txt csv svg这些文本类型文件的判断。

安装

npm i file-type
复制代码

使用

NodeJS使用:

import {fileTypeFromFile} from 'file-type';

console.log(await fileTypeFromFile('Unicorn.png'));
//=> {ext: 'png', mime: 'image/png'}
复制代码

浏览器使用:

import {fileTypeFromStream} from 'file-type';

const url = 'https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg';

const response = await fetch(url);
const fileType = await fileTypeFromStream(response.body);

console.log(fileType);
//=> {ext: 'jpg', mime: 'image/jpeg'}
复制代码

更多API,可阅读官方文档: file-type

写在最后

我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,联系我,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:《教你做小游戏》《极致用户体验》

猜你喜欢

转载自juejin.im/post/7131019859227312165