识别heic格式图片
- 客户反馈图片上传卡住了,排查了一天,发现图片格式是heic的,第一次遇到heic格式的图片
- 科普一番,大概了解了heic格式的图片是苹果新出的一个图片算法,占用更小的空间,同时保持高质量的画面
- 查阅了mdn上的mime-type,目前标准的媒体类型,还没包含heic格式的图片,也找不到相关buffer文件头
- 多说一句,通过后缀名或者mimeType判断文件格式,不准确
- 详细链接:www.jianshu.com/p/b016d10a0…
- 先来趴一下heic图片的hex,en.webhex.net/upload

- 由此可以看出,heic格式标识是从第9位开始(16进制的buffer)
- 但是通常通用格式的图片取的前8位,这个heic判断头文件的buffer比较特殊,干脆直接提取前16位判断
- 其他图片格式的文件头信息可以在网上查到
客户端拿到heic文件头信息
- 通过第一步,我们知道了heic格式的图片的头部信息,并且网上查到了其他图片格式的头部,因此,判断 00 00 00 开头,和 00 00 00结尾的第一组buffer就可以确定不是通用图片格式
- 至于不是通用格式图片,是因为,heic格式图片的另一种表现是heif(上面链接里可以找到),所以第一组里的二进制有两个可能的值
- 客户端得借助FileReader.readAsArrayBuffer和Uint8Array获取buffer(8位无符号整型数组
developer.mozilla.org/zh-CN/docs/…)
- 上代码:
const fileReaderBuffer = new FileReader()
fileReaderBuffer.onload = function() {
const type = getFileType(fileReaderBuffer)
}
fileReaderBuffer.readAsArrayBuffer(e.target.files[0])
function getFileType(reader) {
const bufferInt = new Uint8Array(reader.result)
const arr = bufferInt.slice(0, 4)
const headerArr = bufferInt.slice(0, 16)
let header = ''
let allHeader = ''
let realMimeType
for (let i = 0
header += arr[i].toString(16)
}
for (let i = 0
allHeader += headerArr[i].toString(16)
}
// magic numbers: http://www.garykessler.net/library/file_sigs.html
switch (header) {
case '89504e47':
realMimeType = 'image/png'
break
case '47494638':
realMimeType = 'image/gif'
break
case 'ffd8ffDB':
case 'ffd8ffe0':
case 'ffd8ffe1':
case 'ffd8ffe2':
case 'ffd8ffe3':
case 'ffd8ffe8':
realMimeType = 'image/jpeg'
break
case '00020': // heic开头前4位可能是00020也可能是00018,其实这里应该是判断头尾000的,可以自己改下
case '00018':
(allHeader.lastIndexOf('68656963') === 13 || allHeader.lastIndexOf('68656966') === 13) ? (realMimeType = 'image/heic') : (realMimeType = 'unknown')
break
default:
realMimeType = 'unknown'
break
}
return realMimeType
}
转换图片为通用格式
import heic2any from 'heic2any'
const fileReader = new FileReader()
const fileReaderBuffer = new FileReader()
const file // 图片文件
// 加载图片
function loadImg(img) {
return new Promise((resolve, reject) => {
img.onload = (e) => {
resolve(e.target.result)
}
img.onerror = (e) => {
reject(e)
}
})
}
// 压缩图片
fileReader.onload = async (e) => {
const base64 = e.target.result
const img = new Image()
let imgData = null
img.src = base64
img.filename = file.name
try {
await loadImg(img)
imgData = await compressImg(img)
resolve(imgData)
} catch (error) {
console.error(error)
resolve({
file: null,
url: ''
})
}
}
// 读取是否是heic格式图片
fileReaderBuffer.onload = async () => {
const type = getFileType(fileReaderBuffer)
if (type === 'unknown') {
console.error('unknown image type')
resolve({
file: null,
url: ''
})
return
}
if (type.includes('/heic')) {
heic2any({ blob: file, toType: 'image/jpeg' }).then((blob) => {
fileReader.readAsDataURL(blob)
}).catch(() => {
resolve({
file: null,
url: ''
})
})
return
}
fileReader.readAsDataURL(file)
}
fileReaderBuffer.readAsArrayBuffer(file)