js处理heic(heif)格式的图片

11,587 阅读2分钟

识别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);  // heic格式图片
    let header = '';
    let allHeader = '';
    let realMimeType;
    
    for (let i = 0; i < arr.length; i++) {
        header += arr[i].toString(16); // 转成16进制的buffer
    }
    
    for (let i = 0; i < headerArr.length; i++) {
        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;
}

转换图片为通用格式

  • 网上找了一个js包,可以把heic转成png,jpg,gif格式的工具:github.com/alexcorvi/h…
  • 上一步我们拿到了此图片的真实类型,那么就可以根据真实类型来做下一步操作了,我这里是压缩
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);