小程序与移动端图片上传那些事

2022年7月14日,深圳一场绝世晚霞突然出现,而随手拍的一张照片上传到相册,却发现了一些问题,于是回来继续研究,顺便对相册的文件上传做个踩坑总结。

图片上传在各种程序开发中是一个再常见不过的需求。PC端由于对文件管理比较专业和成熟,一个 <input file> 直接搞定,但在移动端,却变得复杂起来。主要来说:第一是格式增多,兼容性难保证,各厂家均有自己的转换思路。第二是出于隐私保护,索性抹去了 EXIF。即刻相簿在做这件事情的时候也是不断的摸索中前进,被坑过数次。

对于相册程序而言,我们需要的是“完整”的“原图”,这里有两层含义,第一是包含完整的 EXIF 信息,从而绘制出带相机型号以及拍摄参数的分享图,第二是未经格式转换和机内后期的原始图像,尽可能的保存原始数据,当然,这一点倒影响其次。

即刻相簿是一个以微信小程序为主的相册,自然首先用到的是微信官方提供的上传功能,即 wx.chooseImage() 和 wx.chooseMedia() 很不幸的是这两者均不能满足要求,均抹去了 EXIF,转换成了 JPG,视频经微信压缩,画质掉渣到不可接受。

后来采用内嵌 webview,通过 H5 页面来实现上传,这样 file 的选择权就完全交由手机系统来决定,随后的测试中发现,在 iOS 系统中,相机拍摄格式中选择了“高效”,即 HEIF 格式时,上传时依然会转换成 JPEG,并抹去 EXIF,但拍摄 ProRAW 格式在自动转换 JPEG 的时候又能保留 EXIF,拍摄格式选择“兼容性最佳”,同样也能保留 EXIF,真是迷之操作。

(iOS 似乎在自动转换的过程中存在 Bug,将方向6转换成1的过程中没有更改宽高像素,导致画面比例错误。在选择照片中选择大、中、小,会丢失方向,竖拍图直接横着显示。前者解决办法判断宽度是否小于高度,竖拍一般宽度小于高度,后者基本无解,估计没多少人会去选大小吧)

既然是 webview,也想过能否在前段通过 JavaScript 来预先获取 EXIF,于是使用了 exif-reader.js 来读取 EXIF,很不幸测试结果中和上述表现一致。很好理解,因为在选择文件那一刻,就已经转换一个临时的 JPEG,开发者侧是无法感知这一过程。

另外有趣的是,手机上将 HEIF 图片通过分享存储到“文件”中,再在上传页面选择“选取文件”找到刚才的文件,是可以上传原始的 HEIC 格式文件并且保留了完整的 EXIF。

综上所述,即刻相簿也提供了PC端上传方式,来避开这些“限制”,以上测试基于 iPhone 13 Pro 系统 iOS 15.5。看来迟早得上 native app。

struts中使用FormFile文件上传

用贯了spring mvc的注入式文件上传,回到struts中都忘了怎么写,翻了翻老项目,记录下。
struts config中,定义formBean,action中用name指定formBean。

<struts-config>
    <form-beans>
        <form-bean name="fileManagerForm" type="com.dorole.FileManagerForm" />
    </form-beans>
    <action path="..." type="..." parameter="method" name="fileManagerForm">
        <forward name="..." path="..."></forward>
    </action>
</struts-config>

FileManagerForm如下

public class FileManagerForm extends ActionForm {
    private FormFile file;
    public void setFile(FormFile file) {
        this.file = file;
    }
    public FormFile getFile() {
        return file;
    }
}

FileManagerAction如下

public ActionForward upload(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        FileManagerForm fmf = (FileManagerForm) form;
        FormFile formFile = fmf.getFile();
        if (formFile.getFileData().length != 0) {
            ...
        }
        return null;
}

jsp如下

<form action="..." method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" value="upload" />
</form>