文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android实现JPEG图片压缩后同时保留图片的EXIF信息

2024-11-29 20:56

关注

在实际开发中,对于图片数据不论是缓存在本地磁盘还是上传到后端,都需要先对图片进行压缩处理。在图片压缩的过程中,为了减小文件大小,一些不重要的元数据(包括EXIF信息)可能会被移除或修改。如果图片经过压缩处理,其原始的EXIF信息可能会丢失或不完整。

EXIF信息附加于JPEG、TIFF、RIFF等文件之中,可以记录数码照片的属性信息和拍摄数据。比如记录以下信息:

项目

资讯(举例)

制造厂商

Canon

相机型号

Canon EOS-1Ds Mark III

影像方向

正常(upper-left)

影像解析度X

300

影像解析度Y

300

解析度单位

dpi

软件

Adobe Photoshop CS Macintosh

最后异动时间

2005:10:06 12:53:19

YCbCrPositioning

2

曝光时间

0.00800 (1/125) sec

光圈值

F22

拍摄模式

光圈优先

ISO感光值

100

Exif资讯版本

30,32,32,31

影像拍摄时间

2005:09:25 15:00:18

影像存入时间

2005:09:25 15:00:18

曝光补偿(EV+-)

0

测光模式

点测光(Spot)

闪光灯

关闭

镜头实体焦长

12 mm

Flashpix版本

30,31,30,30

影像色域空间

sRGB

影像尺寸X

5616 pixel

影像尺寸Y

3744 pixel

有一些压缩工具或软件提供了保留EXIF信息的选项。在使用这些工具进行压缩时,可以选择保留EXIF信息,以确保压缩后的图片仍然包含完整的元数据。在实际开发中我们如何进行保留EXIF信息的同时进行图片压缩呢?

使用ExifInterface方案

ExifInterface是Android系统中用于描述多媒体文件(如JPG格式图片)附加信息的一个类。它主要涵盖了拍摄时的光圈、快门、白平衡、ISO、焦距、日期时间等各种拍摄条件,以及相机品牌、型号、色彩编码、拍摄时录制的声音以及全球定位系统(GPS)和缩略图等信息。简单来说,ExifInterface就是JPEG图像文件+拍摄参数的结合。

ExifInterface类主要提供了读取、写入和缩略图处理这三个方面的功能。通过ExifInterface,可以获取到图片的多种属性,如方向(orientation)、拍摄时间(dateTime)、设备制造商(make)、设备型号(model)等。

ExifInterface类只提供了 getXXX() 和 setAttributes(String tag, String value) 这种操作单个属性的方法,如果想将原图片文件中的所有EXIF信息完整复制到另一个图片中会非常繁琐。因此有人通过反射,对所有属性名进行遍历,从而实现了批量操作。也算是一种解决方案,具体如下:

public static void saveExif(String oldFilePath, String newFilePath) throws Exception {
    ExifInterface oldExif = new ExifInterface(oldFilePath);
    ExifInterface newExif = new ExifInterface(newFilePath);
    Class cls = ExifInterface.class;
    Field[] fields = cls.getFields();
    for (int i = 0; i < fields.length; i++) {
        String fieldName = fields[i].getName();
        if (!TextUtils.isEmpty(fieldName) && fieldName.startsWith("TAG")) {
            String fieldValue = fields[i].get(cls).toString();
            String attribute = oldExif.getAttribute(fieldValue);
            if (attribute != null) {
                newExif.setAttribute(fieldValue, attribute);
            }
        }
    }
    //将内存中的修改写入磁盘(IO操作)
    newExif.saveAttributes();
 }

以上方案弊端也很明显,就是需要对文件进行多次IO操作。观察上面方法中的两个参数都是文件路径,比如我们通过拍照进行图片压缩上传,那么拍完照通过 onPictureTaken(byte[] data, Camera camera) 回调方法拿到图片的 byte[] data 数据后处理是这样的:

能否只在内存中操作?发现有 ExifInterface (String filename) 和 ExifInterface (InputStream inputStream) 两种构造方法, 进行如下改造:

public static void saveExif(byte[] srcData, String destFilePath) throws Exception {
    ExifInterface oldExif = new ExifInterface(new ByteArrayInputStream(srcData));
    ExifInterface newExif = new ExifInterface(destFilePath);
    Class cls = ExifInterface.class;
    Field[] fields = cls.getFields();
    for (int i = 0; i < fields.length; i++) {
        String fieldName = fields[i].getName();
        if (!TextUtils.isEmpty(fieldName) && fieldName.startsWith("TAG")) {
            String fieldValue = fields[i].get(cls).toString();
            String attribute = oldExif.getAttribute(fieldValue);
            if (attribute != null) {
                newExif.setAttribute(fieldValue, attribute);
            }
        }
    }
    //将内存中的修改写入磁盘(IO操作)
    newExif.saveAttributes();
 }

使用自定义方案

不管是图片还是其他文件,本质都是格式化的数据,都有专用的数据结构。研究下JPG的数据结构,找到 EXIF 数据块的起始索引,然后从源文件byte[]中复制插入到目标文件byte[]对应位置中就实现了。

图片

JPEG文件的内容都开始于一个二进制的值 '0xFFD8', 并结束于二进制值'0xFFD9'. 在JPEG的数据中有好几种类似于二进制 0xFFXX 的数据都统称作 "标记", 代表了一段JPEG的信息数据。

0xFFD8 的意思是 SOI图像起始(Start of image) ,是Jpeg文件的魔数(Magic Number)。每种格式的文件都有固定的Magic Number,比如.class 字节码文件的Magic Number是 “0xCAFEBABE”。0xFFD9 则表示 EOI图像结束 (End of image)。

0xFF+标记号(1个字节)+数据大小描述符(2个字节)+数据内容(n个字节)

对于EXIF数据,使用的是APP1标记,前两个字节固定为 0xFFE1,后面紧跟着两个字节记录的是EXIF数据内容的 length + 2,假设这两个字节的值是 24,那么EXIF数据内容的长度就是22字节。所以只要找到EXIF在数组中的起始索引,抠出来插入到新数组中去就完成了。

图片

public static byte[] cloneExif(byte[] srcData, byte[] destData) {
    if (srcData == null || srcData.length == 0 || destData == null || destData.length == 0) return null;

    ImageHeaderParser srcImageHeaderParser = new ImageHeaderParser(srcData);
    byte[] srcExifBlock = srcImageHeaderParser.getExifBlock();
    if (srcExifBlock == null || srcExifBlock.length <= 4) return null;

    LOG.d(TAG, "pictureData src: %1$s KB; dest: %2$s KB", srcData.length / 1024, destData.length / 1024);
    LOG.d(TAG, "srcExif: %s B", srcExifBlock.length);
    ImageHeaderParser destImageHeaderParser = new ImageHeaderParser(destData);
    byte[] destExifBlock = destImageHeaderParser.getExifBlock();
    if (destExifBlock != null && destExifBlock.length > 0) {
        LOG.d(TAG, "destExif: %s B", destExifBlock.length);
        //目标图片中已有exif信息, 需要先删除
        int exifStartIndex = destImageHeaderParser.getExifStartIndex();
        //构建新数组
        byte[] newDestData = new byte[srcExifBlock.length + destData.length - destExifBlock.length];
        //copy 1st block
        System.arraycopy(destData, 0, newDestData, 0, exifStartIndex);
        //copy 2rd block (exif)
        System.arraycopy(srcExifBlock, 0, newDestData, exifStartIndex, srcExifBlock.length);
        //copy 3th block
        int srcPos = exifStartIndex + destExifBlock.length;
        int destPos = exifStartIndex + srcExifBlock.length;
        System.arraycopy(destData, srcPos, newDestData, destPos, destData.length - srcPos);
        LOG.d(TAG, "output image Data with exif: %s KB", newDestData.length / 1024);
        return newDestData;
    } else {
        LOG.d(TAG, "destExif: %s B", 0);
        //目标图片中没有exif信息
        byte[] newDestData = new byte[srcExifBlock.length + destData.length];
        //copy 1st block (前两个字节)
        System.arraycopy(destData, 0, newDestData, 0, 2);
        //copy 2rd block (exif)
        System.arraycopy(srcExifBlock, 0, newDestData, 2, srcExifBlock.length);
        //copy 3th block
        int srcPos = 2;
        int destPos = 2 + srcExifBlock.length;
        System.arraycopy(destData, srcPos, newDestData, destPos, destData.length - srcPos);
        LOG.d(TAG, "output image Data with exif: %s KB", newDestData.length / 1024);
        return newDestData;
    }

}

将原图的数据流和压缩处理后的数据流传入,调用cloneExif方法,返回附加了EXIF信息的数据流,将返回的数据流存储即得到一张带有原EXIF信息的压缩图片。

「注意」上述方法只针对JPEG格式图片,其他格式文件数据结构不同,方法可能无效。

来源:沐雨花飞蝶内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯