使HTML5 FileReader与heic文件兼容

15
当使用FileReader API从输入元素读取文件时,它能够正常工作。但是我的安卓设备允许发送文件,即使是heic格式的文件,而这会导致控制台中没有任何错误信息的空白图像。当直接从相机获取图像时,方向也不正确。我刚刚发现实现这些功能需要使用庞大的库,因此我正在寻找更为智能的解决方案。
function previewFile( e ) {
    var preview = document.getElementById('usersProfilePicture');
    var file    = e.files[0];
    var reader  = new FileReader();

    reader.onloadend = function () {
        preview.src = reader.result;
    }
    if (file) {
        reader.readAsDataURL(file);
    } else {
        preview.src = "";
    }
}

HTML5

<form>
    <label>
        <input id="uploadProfilePicture" name=file type=file accept="image/jpg, image/jpeg, image/png, image/gif, image/bmp">
    </label>
</form>

没有任何错误信息。无论我设置什么接受属性,桌面和安卓上的Firefox、Chrome都允许.heic文件。

最糟糕的解决方案:拒绝接受.heic文件。 最好的解决方案:使fileReader能够处理.heic文件。 介于两者之间的解决方案:客户端检测heic并将其转换为jpeg。


1
我发现当前稳定版的Android Chrome应用程序可以选择不可见的.heic文件,并且不遵守accept属性,但是新的金丝雀版本会隐藏.heic文件。因此,这个问题将在未来得到自动修复。但是,heic格式越来越流行,我必须要支持它。所以我仍然需要一个适当的解决方案。 - Sascha Grindau
3
把这个帖子顶上去,我也需要一个heic的解决方案。 - douglasrcjames
3个回答

10
上面的答案已经解释得非常好了,但是如果有人需要另一个例子,我稍微修改了来自Heic2Any的例子 (https://alexcorvi.github.io/heic2any/)
<input type="file" id="heic" onchange="convertHEIC(event)">

async function convertHEIC (event){
    let output = document.getElementById('output');

    //if HEIC file
    if(event.target.files[0] && event.target.files[0].name.includes(".HEIC")){
        // get image as blob url
        let blobURL = URL.createObjectURL(event.target.files[0]);
        
        // convert "fetch" the new blob url
        let blobRes = await fetch(blobURL)

        // convert response to blob
        let blob = await blobRes.blob()

        // convert to PNG - response is blob
        let conversionResult = await heic2any({ blob })

        // convert to blob url
        var url = URL.createObjectURL(conversionResult);
        document.getElementById("target").innerHTML = `<a target="_blank" href="${url}"><img src="${url}"></a>`;
    
    }       
};

5

目前我有一个解决此问题的方法,通过使用名为heic2any的库。

(https://github.com/alexcorvi/heic2any)

检查输入的文件是否是.heic格式,然后像这样使用库:

heic2any({
        // required: the HEIF blob file
        blob: file,
        // (optional) MIME type of the target file
        // it can be "image/jpeg", "image/png" or "image/gif"
        // defaults to "image/png"
        toType: "image/jpeg",
        // conversion quality
        // a number ranging from 0 to 1
        quality: 0.5
    })

我会封装这个调用成一个 Promise,然后将结果传递给文件读取器:
// uploadHEIC is a wrapper for heic2any
uploadHEIC(heicFile).then(function (heicToJpgResult) {
    var reader = new Filereader();
    reader.onload = function () {
    // Do what you want to file
    }
    reader.readAsArrayBuffer(heicToJpgResult);
}

2

有几件事需要注意才能使其正常工作。

首先,在Windows中,heic和heif的分配mime类型为空白。不确定何时会修复此错误,但现在您不能依赖脚本或输入标记中的mime类型。我需要在我的输入标记的accept参数中添加heic和heif文件扩展名:

<input type="file" accept="image/*,.heic,.heif" />

在我的脚本中,我创建了一个函数来检查文件扩展名是否为 heic 和 heif,如果 mime 类型为空的话。
function isHEIC(file) { // check file extension since windows returns blank mime for heic
    let x = file.type ? file.type.split('image/').pop() : file.name.split('.').pop().toLowerCase();
    return x == 'heic' || x == 'heif';
}

此外,heic2any 很大(即使已经进行了缩小和压缩)。我决定仅在需要时动态加载它。
function loadScript(url, callback) {
    var script = document.querySelectorAll('script');
    for (var i = 0; i < script.length; i++) {
        if (script[i].src === url) {
            script = script[i];
            if (!script.readyState && !script.onload) {
                callback();
            } else { // script not loaded so wait up to 10 seconds
                var secs = 0, thisInterval = setInterval(function() {
                    secs++;
                    if (!script.readyState && !script.onload) {
                        clearInterval(thisInterval);
                        callback();
                    } else if (secs == 10) {
                        clearInterval(thisInterval);
                        console.log('could not load ' + url);
                    }
                }, 1000);
            }
            return;
        }
    }
    script = document.createElement('script');
    script.type = 'text/javascript';
    document.getElementsByTagName('head')[0].appendChild(script);
    if (script.readyState) {
        script.onreadystatechange = function() {
            if (script.readyState === 'loaded' || script.readyState === 'complete') {
                script.onreadystatechange = null;
                callback();
            }
        }
    } else {
        script.onload = function() {
            script.onload = null;
            callback();
        }
    }
    script.src = url;
}

在我的使用案例中,我正在利用heic2any来准备上传的图像。如果图像是heic格式,我会将其转换为png(blob),然后将结果传递给另一个实用程序(image blob reduce)进行调整大小和锐化,然后再转换为jpg以准备上传。但是,为了简单起见,下面的示例将使用heic2any在一步中将其转换为jpg,然后再上传。
function convertHEIC(file) {
    return new Promise(function(resolve) {
        if (!isHEIC(file)) return resolve(file);
        loadScript('https://cdn.jsdelivr.net/npm/heic2any@0.0.3/dist/heic2any.min.js', function() {
            heic2any({
                blob: file,
                toType: "image/jpg"
            }).then(function (convertedFile) {
                convertedFile.name = file.name.substring(0, file.name.lastIndexOf('.')) + '.jpeg';
                resolve(convertedFile);
            });
        });
    });
}

// convert any heic (and do any other prep) before uploading the file
convertHEIC(file).then(function(file) {
    // code to upload (or do something else with file)
    .
    .
    .
}

有几个问题... heic2any似乎不支持"image/jpg", 需要改成"image/jpeg" (否则将默认为"image/png")。此外,script.readyState和script.onreadystatechange好像没有生效,所以loadScript每次都会加载脚本。 - devlop
谢谢反馈。我已经更正了JPEG的拼写错误。至于动态脚本加载,它难道不是在你调用函数时执行应该执行的操作吗?我在这里找到了代码:https://dev59.com/A18d5IYBdhLWcg3w41gp#71209306,对我来说它很好用。您能否提供更多关于问题的明确信息? - Tom Davenport
关于使用onreadystatechange与脚本,请参见此处:https://dev59.com/RmUq5IYBdhLWcg3waPtC#31374433(请注意,有很多赞同并链接到进一步讨论,因此我认为可以放心使用),但是如果您遇到问题,我很想知道可能出了什么问题。 - Tom Davenport
调试代码时,readyStateonreadystatechange都不是脚本(HtmlScriptElement)的属性,与默认为null的onload属性相比。通过向代码添加日志记录,我发现readyState条件总是失败,else块总是运行。我并不是规范的权威(完全不是),但我认为脚本元素不支持这些属性(而且您引用的其他帖子实际上只是使用了onloadonreadystatechange未被使用)-至少从我的测试来看。 - devlop
这对我来说非常有效,脚本的动态加载只在需要时进行,完美无缺,不会给应用程序增加额外的负担,做得很好。 - OG Sean
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接