Exif 格式介绍和操作

  • Exif 是什么
  • Exif 格式
  • 如何修改Exif数据
    • 1. 修改单个或者多个数据
    • 2. 复制整个Exif数据
  • 参考:

Exif 是什么

Exif是一种图像文件格式,可以记录数码照片的属性信息和拍摄数据;
实际上Exif格式就是在JPEG格式头部插入了数码照片的信息,包括拍摄时的相机品牌、型号、光圈、焦距、白平衡等相机硬件信息和图片参数信息。

主要包括以下几类信息:

  1. 拍摄日期
  2. 拍摄器材(机身、镜头、闪光灯等)
  3. 拍摄参数(快门速度、光圈F值、ISO速度、焦距、测光模式等)
  4. 图像处理参数(锐化、对比度、饱和度、白平衡等)
  5. 图像描述及版权信息
  6. GPS定位数据
  7. 缩略图

例如:

项目 信息
制造厂商 Canon
相机型号 Canon EOS-1Ds Mark III
曝光时间 0.00800 (1/125) sec
光圈值 F22
闪光灯 关闭

Exif 格式

Exif信息以0xFFE1作为开头标记,后两个字节表示Exif信息的长度。所以Exif信息最大为64 kB.

具体格式如下所示:
JPEG 数据格式

JPEG 数据格式
--------------------------------------------------------------------------------------------------------------------------
| SOI 标记 | Exif 的大小=SSSS          | 标记 YY 的大小=TTTT          | SOS 标记 的大小=UUUU   | 图像数据流     | EOI 标记
--------------------------------------------------------------------------------------------------------------------------
| FFD8    | FFE1 SSSS    DDDD......   | FFYY   TTTT    DDDD......  | FFDA UUUU DDDD....   | I I I I....   | FFD9
--------------------------------------------------------------------------------------------------------------------------

如何修改Exif数据

1. 修改单个或者多个数据

Android 提供了操作Exif信息的类 ExifInterface;
可以通过该类的相关方法来获取以及修改JPEG图片中的Exif信息。

获取Exif信息

try {//oldPath:图片地址ExifInterface exifInterface = new ExifInterface(oldPath);String dateData = exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
} catch (IOException e) {e.printStackTrace();
}

修改Exif信息

try {//newPath:图片地址ExifInterface exifInterface = new ExifInterface(newPath);exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,"6");exifInterface.saveAttributes();
} catch (IOException e) {e.printStackTrace();
}

其中ExifInterface有三种构造函数,可以根据实际情况创建适当的对象

public ExifInterface(String filename);
public ExifInterface(InputStream inputStream);
public ExifInterface(FileDescriptor fileDescriptor);

2. 复制整个Exif数据

在Camera开发过程中,在生成最终的保存图片之前,往往会因为特效等操作需要对图片添加滤镜等特效,在这个过程中会将原始的JPEG数据转换为BMP格式以方便操作,但是在BMP格式中Exif数据会丢失,导致最终将BMP转换为所保存的JPEG格式数据中无Exif信息。

这种情况,可以在将JPEG转换为BMP格式之前,将Exif数据读取,然后再插入到最终要生成的JPEG图片中,以达到Exif数据完整的目的。

具体如何读取可以根据Exif格式的来操作

JPEG 数据格式
--------------------------------------------------------------------------------------------------------------------------
| SOI 标记 | Exif 的大小=SSSS          | 标记 YY 的大小=TTTT          | SOS 标记 的大小=UUUU   | 图像数据流     | EOI 标记
--------------------------------------------------------------------------------------------------------------------------
| FFD8    | FFE1 SSSS    DDDD......   | FFYY   TTTT    DDDD......  | FFDA UUUU DDDD....   | I I I I....   | FFD9
--------------------------------------------------------------------------------------------------------------------------

从JPEG数据格式可知,JPEG文件开始于一个二进制的值0xFFD8,结束于二进制值0xFFD9.

其中SOI(Start of image)表示图像开始,EOI(End of image)表示图像结束。

从Exif格式我们可以得知,Exif信息是以FFE1开头,并在之后的两个字节中记录了Exif信息的长度(该长度=2+Exif数据的长度;即自己所占的两个字节也囊括其中);那么在有标记信息和信息长度的情况下可以很方便的读取到Exif的全部数据。

那么代码实现如下:
其中源图片包括Exif信息,目标图片中无Exif信息。

import android.util.Log;import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;/**** JPEG 数据格式* --------------------------------------------------------------------------------------------------------------------------* | SOI 标记 | Exif 的大小=SSSS          | 标记 YY 的大小=TTTT          | SOS 标记 的大小=UUUU   | 图像数据流     | EOI 标记* --------------------------------------------------------------------------------------------------------------------------* | FFD8    | FFE1 SSSS    DDDD......   | FFYY   TTTT    DDDD......  | FFDA UUUU DDDD....   | I I I I....   | FFD9* --------------------------------------------------------------------------------------------------------------------------*/public class ImageHeaderParser {private static final String TAG = "CAMap_ImageHeaderParser";private static final int EXIF_MAGIC_NUMBER = 0xFFD8;private static final int SEGMENT_SOS = 0xDA;private static final int MARKER_EOI = 0xD9;private static final int SEGMENT_START_ID = 0xFF;private static final int EXIF_SEGMENT_TYPE = 0xE1;private byte[] mExifOfJpeg;private final StreamReader streamReader;public ImageHeaderParser(byte[] data) {this(new ByteArrayInputStream(data));}public ImageHeaderParser(InputStream is) {streamReader = new StreamReader(is);parserExif();}public static byte[] cloneExif(byte[] srcData, byte[] destData) {if (srcData == null || destData == null || srcData.length == 0 || destData.length == 0) {return null;}ImageHeaderParser srcImageHeaderParser = new ImageHeaderParser(srcData);byte[] srcExif = srcImageHeaderParser.getExifOfJpeg();int srcExifLength = srcExif.length;if (srcExif == null || srcExifLength <= 4) {return null;}ImageHeaderParser destImageHeaderParser = new ImageHeaderParser(destData);byte[] destExif = destImageHeaderParser.getExifOfJpeg();if (destExif == null || destExif.length == 0) {byte[] newDestData = new byte[srcExifLength + destData.length];//copy FFD8System.arraycopy(destData, 0, newDestData, 0, 2);//copy exifSystem.arraycopy(srcExif, 0, newDestData, 2, srcExifLength);//copy destData info except FFD8System.arraycopy(destData, 2, newDestData, 2 + srcExifLength, destData.length - 2);return newDestData;}return null;}public byte[] getExifOfJpeg() {return mExifOfJpeg;}private void parserExif() {try {final int magicNumber = streamReader.getUInt16();if (magicNumber == EXIF_MAGIC_NUMBER) {mExifOfJpeg = getExifSegment();}} catch (IOException e) {e.printStackTrace();}}private byte[] getExifSegment() throws IOException {short segmentId, segmentType;int segmentLength;while (true) {segmentId = streamReader.getUInt8();if (segmentId != SEGMENT_START_ID) {Log.d(TAG, "[getExifSegment]: Unknown segmentId=" + segmentId);return null;}segmentType = streamReader.getUInt8();if (segmentType == SEGMENT_SOS) {return null;} else if (segmentType == MARKER_EOI) {return null;}// Segment length includes bytes for segment length.segmentLength = streamReader.getUInt16() - 2;if (segmentType != EXIF_SEGMENT_TYPE) {long skipped = streamReader.skip(segmentLength);if (skipped != segmentLength) {Log.d(TAG, "[getExifSegment]: Unable to skip enough data"+ ", type: " + segmentType+ ", wanted to skip: " + segmentLength+ ", but actually skipped: " + skipped);return null;}} else {byte[] segmentData = new byte[segmentLength];int read = streamReader.read(segmentData);if (read != segmentLength) {Log.d(TAG, "[getExifSegment]: Unable to read segment data"+ ", type: " + segmentType+ ", length: " + segmentLength+ ", actually read: " + read);return null;} else {byte[] block = new byte[2 + 2 + segmentLength];block[0] = (byte) SEGMENT_START_ID;block[1] = (byte) EXIF_SEGMENT_TYPE;int length = segmentLength + 2;block[2] = (byte) ((length >> 8) & 0xFF);block[3] = (byte) (length & 0xFF);System.arraycopy(segmentData, 0, block, 4, segmentLength);return block;}}}}private static class StreamReader {private final InputStream is;//motorola / big endian byte orderpublic StreamReader(InputStream is) {this.is = is;}public int getUInt16() throws IOException {return  (is.read() << 8 & 0xFF00) | (is.read() & 0xFF);}public short getUInt8() throws IOException {return (short) (is.read() & 0xFF);}public long skip(long total) throws IOException {if (total < 0) {return 0;}long toSkip = total;while (toSkip > 0) {long skipped = is.skip(toSkip);if (skipped > 0) {toSkip -= skipped;} else {// Skip has no specific contract as to what happens when you reach the end of// the stream. To differentiate between temporarily not having more data and// having finished the stream, we read a single byte when we fail to skip any// amount of data.int testEofByte = is.read();if (testEofByte == -1) {break;} else {toSkip--;}}}return total - toSkip;}public int read(byte[] buffer) throws IOException {int toRead = buffer.length;int read;while (toRead > 0 && ((read = is.read(buffer, buffer.length - toRead, toRead)) != -1)) {toRead -= read;}return buffer.length - toRead;}public int getByte() throws IOException {return is.read();}}
}

参考:

  • JPEG图片压缩后保留Exif信息(java实现)

Exif 格式介绍和操作相关推荐

  1. MySQL Binlog三种格式介绍及分析

    一.Mysql Binlog格式介绍 Mysql binlog日志有三种格式,分别为Statement,MiXED,以及ROW! 1.Statement:每一条会修改数据的sql都会记录在binlog ...

  2. 图像bayer格式介绍以及bayer插值原理CFA

    1 图像bayer格式介绍 bayer格式图片是伊士曼·柯达公司科学家Bryce Bayer发明的,Bryce Bayer所发明的拜耳阵列被广泛运用数字图像. 对于彩色图像,需要采集多种最基本的颜色, ...

  3. macbook视频格式转换_mac视频格式转换怎么操作?如何将视频转换成mac能播放的格式?...

    mac 视频格式转换怎么操作?如何将视频转换成 mac 能播放的格式? 别以为 mac 只是系统和咱们普通的电脑不一样,实际上,在操作中,它还有很多的不一样. 比如说咱们 Windows 系统中常见的 ...

  4. FME的ESRI Geodatabase (MDB)格式介绍(一)

    原文发布时间:2010-10-13 作者:毛毛虫 来源: ESRI Geodatabase (MDB) 是一种FME 格式,它的FME格式关键字是GEODATABASE_MDB. 1.创建类: FME ...

  5. 地震数据SEGY格式介绍及其查看分析(附示例地震数据)

    SEGY简介 segy指的是"segy格式地震数据". 地震数据一般以地震道(trace)为单位进行组织,采用SEG-Y文件格式存储.SEG-Y格式是由SEG (Society o ...

  6. 文件格式和压缩格式介绍

    文件格式和压缩格式介绍 一.常用的文件存储格式 TEXTFILE textfile为默认格式,存储方式为行式存储,在检索时磁盘开销大 数据解析开销大,而对压缩的text文件 hive无法进行合并和拆分 ...

  7. udf iso9660 java_UDF和ISO9660格式介绍

    UDF和ISO9660格式介绍 (2017-04-07 08:51:03) 标签: it ·ISO-9660: 这是国际标准化组织(ISO)于1985年颁布的通用光盘文件系统.目前它是得到最广泛支持的 ...

  8. mysql查看表描述_MySQL表记录操作介绍(重点介绍查询操作)

    MySQL表记录操作指的是对数据库表中数据进行CRUD增删改查操作,一下将一一给大家介绍,重点介绍查询操作. 一.插入数据(INSERT) 二.删除数据(DELETE) 三.修改数据(UPDATE) ...

  9. CIF、QCIF、HD1、D1格式介绍

    CIF.QCIF.HD1.D1格式介绍 CIF简介 CIF是常用的标准化图像格式(Common Intermediate Format).在H.323协议簇中,规定了视频采集设备的标准采集分辨率.CI ...

  10. 操作选项_Win 10系统中的电源选项及任务栏图标等功能介绍和操作

    大家好,我是波仔,很高兴又来跟大家一起分享Windows 10系统的知识,希望大家可以多多了解. 今天我们来分享一下Windows 10系统中的电源选项和一些程序图标的介绍和操作. Windows 1 ...

最新文章

  1. ES6与canvas实现鼠标小球跟随效果
  2. 练习:WinForm (PictureBox和Timer)
  3. MySQL密码设置和重置,以及远程登入数据库
  4. java 文件读写--转载
  5. RegExp:正则表达式对象 || Global对象
  6. 7.2.2 - 并发多线程 开启进程的两种方式
  7. 密码技术--证书及go语言生成自签证书
  8. html页面获取关闭页面事件,html页面关闭事件
  9. CityEngine下如何更好的实现影像与地形叠加
  10. CentOS7 (64位) 下QT5.5 连接MySQL数据库(driver not loaded)
  11. C语言(CED)判断一个数是否是2的整数幂的简便方法!
  12. Shiro框架--将Shrio的session改成HTTPSession数据
  13. DRILLNET 2.0------第十八章 起下钻水力参数计算模型
  14. 用QEMU构建嵌入式LINUX系统
  15. CTFshow web15
  16. 《从零开始学Swift》学习笔记(Day 45)——重写方法
  17. matlab 坐标轴根号,在matlab图例中如何打数学符号---根号?
  18. mipi和isp处理_什么是ISP,他的工作原理是怎样的?
  19. dpdk 内核模块 Unknown symbol in module 问题
  20. 分享云安全实践,透视2022亚马逊云科技re:Inforce全球安全大会

热门文章

  1. 13.0、veu-路由嵌套
  2. 数字孪生|数字孪生装备-概念与内涵
  3. urllib.request.urlopen()出现的程序超时假死问题
  4. 2021 App上架到 各应用商店(应用宝,华为,vivo,小米,AppStore)
  5. Win7缺失dll文件如何修复?Win7计算机丢失dll文件怎么办
  6. 使用VMware10虚拟机安装Linux系统(能力工场)
  7. python中ones zeros 的用法
  8. Python小白基础--集合set
  9. ps3 自制系统的C 语言,老树发新芽:PS3自制系统的使用与研究
  10. Android实现身份证号码验证