首先介绍一下JPEG图片信息的格式:
JPEG的格式和标记
每一个JPEG文件的内容都开始于一个二进制的值'0xFFD8',结束与'0xFFD9'。在JPEG数据中有好几种类似于二进制0xFFXX的数据,它们统称为“标记”,并且它们代表了一段JPEG的信息数据。"0xFFD8"和"0xFFD9"后面不跟数据,而其它的标记后面则会附带数据,基本格式如下:
0xFF+标记号(1个字节)+数据大小描述符(2个字节)+数据内容(n个字节)
数据大小描述符是"Motorola"的字节顺序,及大端序,数据的低位被存放在高地址。请注意数据大小包括:数据大小描述符(2个字节)+数据内容(n个字节)。比如:
FF C1 00 0C
它表示这个标记(0xFFC1)的数据占0x000C(等于12)字节,这12个字节中包括了0x000C这两个字节。
Exif使用的标记
先来简单介绍一下exif(来自维基百科):
可交换图像文件格式(英语:Exchangeable image file format,官方简称Exif),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。Exif最初由日本电子工业发展协会在1996年制定,版本为1.0。1998年,升级到2.1,增加了对音频文件的支持。2002年3月,发表了2.2版。Exif可以附加于JPEG、TIFF、RIFF等文件之中,为其增加有关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息。Windows 7操作系统具备对Exif的原生支持,通过鼠标右键点击图片打开菜单,点击属性并切换到详细信息标签下即可直接查看Exif信息。Exif信息是可以被任意编辑的,因此只有参考的功能。
0xFFE0~0xFFEF之间的标记被叫做"应用标记",它们在JPEG图像的解码中不是必须存在的。它们被使用与用户的应用程序中。
Exif使用应用程序标记APP1(0xFFE1)来插入数据。
JPEG头 | APP1标记 | 其他标记 | 数据部分 | JPEG尾 |
FFD8 | FFE1 XXXX... | FFEX XXX... | FFDB XXXX... | FFD9 |
Exif数据结构
Exif的数据结构大致如下,这是"intel"字节序的情况(即小端序)。Exif数据开始于ASCII字符"Exif"和2个字节的0x0000,后面才是Exif的数据,Exif使用TIFF格式来存储数据。
FFE1 | APP1 标记 | ||||
SSSS | APP1 数据 | APP1 数据大小 | |||
45786966 0000 | Exif 头 | ||||
49492A00 08000000 | TIFF 头 | ||||
XXXX. . . . | IFD0 (主图像) | 目录 | |||
LLLLLLLL | 连接到 IFD1 | ||||
XXXX. . . . | IFD0的数据域 | ||||
XXXX. . . . | Exif 子IFD | 目录 | |||
00000000 | 连接结束 | ||||
XXXX. . . . | Exif 子IFD的数据域 | ||||
XXXX. . . . | Interoperability IFD | Directory | |||
00000000 | 连接结束 | ||||
XXXX. . . . | Interoperability IFD的数据域 | ||||
XXXX. . . . | Makernote IFD | Directory | |||
00000000 | 连接结束 | ||||
XXXX. . . . | Makernote IFD的数据域 | ||||
XXXX. . . . | IFD1(缩略图像) | 目录 | |||
00000000 | 连接结束 | ||||
XXXX. . . . | IFD1的数据域 | ||||
FFD8XXXX. . . XXXXFFD9 | 缩略图像 |
TIFF头的结构
TIFF格式中前8个字节是TIFF头:
其中前2个字节定义了字节序。其中0x4949代表"intel"字节序,即小端序,0x4d4d代表"motorola"字节序,即大端序。
随后的2个字节是一个固定值,在intel字节序下是0x002a,在motorola字节序下是0x2a00。
最后4个字节是到第一个IFD(图像文件目录/Image File Directory)的偏移量,这个偏移量是从TIFF头开始计算的。由于第一个IFD是紧挨着TIFF头出现的,所以偏移量都是固定的0x00000008。
IFD:图像文件目录
紧挨着TIFF头,就是第一个IFD:图像文件目录。它包含了图像信息的数据。在下面的表格中,前两个字节('EEEE')表示在IFD中有多少个目录项。它后面存放的就是目录项(每个项目大小为12个字节)。在最后一个目录项之后,有个4字节大小的数据(表格中'LLLL'),它意味着到下一个IFD的偏移量。如果这个值是'0x00000000',则表示它是最后一个IFD。
EEEE | 目录项的号码 | |||
TTTT | ffff | NNNNNNNN | DDDDDDDD | 项目 0 |
TTTT | ffff | NNNNNNNN | DDDDDDDD | 项目 1 |
. . . . . . . . . | . . . . . . | |||
TTTT | ffff | NNNNNNNN | DDDDDDDD | 项目 EEEE-1 |
LLLLLLLL | 到下一个IFD的偏移量 |
上表中的'TTTT'(2个字节)是一个标签号码,代表数据的种类。'ffff'(2个字节)表示数据格式。'NNNNNNNN'(4个字节)表示组件的数目。'DDDDDDDD'(4个字节)则是数据的值或者到数据值的偏移量。
数据格式 (上面表格中的'ffff') 的定义如下表示. "rational" 的意思是说明数据的 内容是一个分数, 它含有2个有符号/无符号的长整形(signed/unsigned long integer)值, 并且第一个值表示的是分子, 第二个值则是分母.
数据的值 | 1 | 2 | 3 | 4 | 5 | 6 |
格式 | unsigned byte | ascii strings | unsigned short | unsigned long | unsigned rational | signed byte |
组件的大小(字节数) | 1 | 1 | 2 | 4 | 8 | 1 |
数据的值 | 7 | 8 | 9 | 10 | 11 | 12 |
格式 | undefined | signed short | signed long | signed rational | single float | double float |
组件的大小(字节数) | 1 | 2 | 4 | 8 | 4 | 8 |
通过多个存储在'NNNNNNNN'数据区的'组件的大小(字节数)'你能得到所有数据的字节长度. 如果数据的长度小于4个字节, 则'DDDDDDDD' 就表示的是标签的值. 如果长度超过4字节, 则'DDDDDDDD' 里存放的就是所要存储数据的偏移量地址.
IFD数据结构
在Exif格式中, 第一个IFD是IFD0(主图像IFD), 然后它连接到IFD1(缩略图IFD) 并且IFD连接在此结束. 但是 IFD0/IFD1 不包含任何的数字相机的信息例如快门速度, 焦距等. IFD0 总是包含一个特殊的标签 Exif偏移量(0x8769), 它表示到 Exif子IFD 的偏移量. Exif子IFD也是一个IFD格式化的数据, 它包含了数字相机的信息。
Exif/TIFF使用的标签数
下面显示了 Exif/TIFF 使用的标签数. 如果这个标签组件数目的上限, CompoNo 一栏就代表这一数值. 如果这个数值没有, 则说明这儿没有上限值.
IFD0 (主图像)使用的标签 | |||||||||||||||||||||||||||||||
标签号 | 标签名 | 格式 | 组件数 | 描述 | |||||||||||||||||||||||||||
0x010e | ImageDescription | ascii string | 用来描述图像. 双字节的字符码不能使用, 如 中文/韩文/日文. | ||||||||||||||||||||||||||||
0x010f | Make | ascii string | 表示数字相机的制造商. 在 Exif 标准中, 这个标签是可选的, 但是在DCF中它是必需的. | ||||||||||||||||||||||||||||
0x0110 | Model | ascii string | 表示数字相机的模块代码. 在 Exif 标准中, 这个标签是可选的, 但在DCF中它也是必需的. | ||||||||||||||||||||||||||||
0x0112 | Orientation | unsigned short | 1 |
| |||||||||||||||||||||||||||
0x011a | XResolution | unsigned rational | 1 | 图像的 显示/打印 分辨率. 缺省值是 1/72英寸, 但是它没有意义因为个人PC在 显示/打印 图像的时候不使用这个值. | |||||||||||||||||||||||||||
0x011b | YResolution | unsigned rational | 1 | ||||||||||||||||||||||||||||
0x0128 | ResolutionUnit | unsigned short | 1 | XResolution(0x011a)/YResolution(0x011b)的单位. '1' 表示没有单位, '2' 意味着英寸, '3' 表示厘米. 缺省值是 '2'(英寸). | |||||||||||||||||||||||||||
0x0131 | Software | ascii string | 显示固件的版本号(数字相机的内部控制软件). | ||||||||||||||||||||||||||||
0x0132 | DateTime | ascii string | 20 | 图像最后一次被修改时的日期/时间. 日期的格式是 "YYYY:MM:DD HH:MM:SS"+0x00, 一共 20个字节. 如果没有设置时钟或者数字相机没有时钟, 则这个域是用空格来填充. 通常, 它和DateTimeOriginal(0x9003)具有相同的值 | |||||||||||||||||||||||||||
0x013e | WhitePoint | unsigned rational | 2 | 定义图像白点(white point/白点:在彩色分色、照相或摄影时作为色彩平衡测量用途的参考点) 的色度(chromaticity). 如果图像是用CIE标准照度 D65(著名的是 '光线/daylight'的国际标准), 这个值是 '3127/10000,3290/10000'. | |||||||||||||||||||||||||||
0x013f | PrimaryChromaticities | unsigned rational | 6 | 定义图像的原始色度. 如果图像使用 CCIR 推荐 709原始色度, 则这个值是 '640/1000,330/1000,300/1000,600/1000,150/1000,0/1000'. | |||||||||||||||||||||||||||
0x0211 | YCbCrCoefficients | unsigned rational | 3 | 当图像的格式是 YCbCr(JPEG的格式), 这个值表示转换成 RGB格式的一个常量. 通常, 这个值是'0.299/0.587/0.114'. | |||||||||||||||||||||||||||
0x0213 | YCbCrPositioning | unsigned short | 1 | 当图像的格式是 YCbCr 并且使用 '子采样/Subsampling'(色度数据的剪切值, 所有的数字相机都使用), 定义了subsampling 像素阵列的色度采样点. '1'表示像素阵列的中心, '2' 表示基准点. | |||||||||||||||||||||||||||
0x0214 | ReferenceBlackWhite | unsigned rational | 6 | 表示黑点(black point)/白点 的参考值. 在YCbCr 格式中,前两个值是 Y的黑点/白点, 下两个值是 Cb, 最后两个值是 Cr. 而在 RGB 格式中, 前两个表示R的黑点/白点, 下两个是 G, 最后两个是 B. | |||||||||||||||||||||||||||
0x8298 | Copyright | ascii string | 表示版权信息 | ||||||||||||||||||||||||||||
0x8769 | ExifOffset | unsigned long | 1 | Exif 子IFD的偏移量 |
Exif 子IFD使用的标签 | |||||||||||||||||||||||||||||||||||||||||||||||||||
标签号 | 标签名 | 格式 | 组件数 | 描述 | |||||||||||||||||||||||||||||||||||||||||||||||
0x829a | ExposureTime | unsigned rational | 1 | 曝光时间 (快门速度的倒数). 单位是秒. | |||||||||||||||||||||||||||||||||||||||||||||||
0x829d | FNumber | unsigned rational | 1 | 拍照时的光圈F-number(F-stop). | |||||||||||||||||||||||||||||||||||||||||||||||
0x8822 | ExposureProgram | unsigned short | 1 | 拍照时相机使用的曝光程序. '1' 表示手动曝光, '2' 表示正常程序曝光, '3' 表示光圈优先曝光, '4' 表示快门优先曝光, '5' 表示创意程序(慢速程序), '6' 表示动作程序(高速程序), '7'表示 肖像模式, '8' 表示风景模式. | |||||||||||||||||||||||||||||||||||||||||||||||
0x8827 | ISOSpeedRatings | unsigned short | 2 | CCD 的感光度, 等效于 Ag-Hr 胶片的速率. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9000 | ExifVersion | undefined | 4 | Exif 的版本号. 用4个ASCII字符来存储. 如果图片是基于Exif V2.1的, 这个值是 "0210". 因为它不是一个用NULL(0x00)来终结的字符串,所以这里的类型是 'undefined'. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9003 | DateTimeOriginal | ascii string | 20 | 照片在被拍下来的日期/时间. 使用用户的软件是不能被修改这个值的. 日期的格式是 "YYYY:MM:DD HH:MM:SS"+0x00, 一共占用20个字节. 如果数字相机没有设置时钟或者 数字相机没有时钟, 这个域使用空格来填充. 在Exif标准中, 这个标签是可选的, 但是在 DCF中是必需的. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9004 | DateTimeDigitized | ascii string | 20 | 照片被数字化时的日期/时间. 通常, 它与DateTimeOriginal(0x9003)具有相同的值. 数据格式是 "YYYY:MM:DD HH:MM:SS"+0x00, 一共占用20个字节. 如果数字相机没有设置时钟或者 数字相机没有时钟, 这个域使用空格来填充. 在Exif标准中, 这个标签是可选的, 但是在 DCF中是必需的. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9101 | ComponentsConfiguration | undefined | 表示的是像素数据的顺序. 大多数情况下RGB格式使用 '0x04,0x05,0x06,0x00' 而YCbCr 格式使用 '0x01,0x02,0x03,0x00'. 0x00:并不存在, 其他的对应关系为 0x01:Y, 0x02:Cb, 0x03:Cr, 0x04:Red, 0x05:Green, 0x06:Bllue. | ||||||||||||||||||||||||||||||||||||||||||||||||
0x9102 | CompressedBitsPerPixel | unsigned rational | 1 | JPEG (粗略的估计)的平均压缩率. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9201 | ShutterSpeedValue | signed rational | 1 | 用APEX表示出的快门速度. 为了转换成原始的 'Shutter Speed'; 则先要计算2的ShutterSpeedValue次幂, 然后求倒数. 例如, 如果 ShutterSpeedValue 是 '4', 快门速度则是1/(24)=1/16秒. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9202 | ApertureValue | unsigned rational | 1 | 拍照时镜头的光圈. 单位是 APEX. 为了转换成普通的 F-number(F-stop), 则要先计算出根号2 2 (=1.4142)的ApertureValue次幂. 例如, 如果ApertureValue 是 '5', F-number 就等于1.41425 = F5.6. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9203 | BrightnessValue | signed rational | 1 | 被拍摄对象的明度, 单位是 APEX. 为了从BrigtnessValue(Bv)计算出曝光量(Ev), 你必须加上 SensitivityValue(Sv). Ev=Bv+Sv Sv=log2(ISOSpeedRating/3.125) ISO100:Sv=5, ISO200:Sv=6, ISO400:Sv=7, ISO125:Sv=5.32. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9204 | ExposureBiasValue | signed rational | 1 | 照片拍摄时的曝光补偿. 单位是APEX(EV). | |||||||||||||||||||||||||||||||||||||||||||||||
0x9205 | MaxApertureValue | unsigned rational | 1 | 镜头的最大光圈值. 你可以通过计算根号2的MaxApertureValue次幂来转换成普通的光圈 F-number (跟ApertureValue:0x9202的处理过程一样). | |||||||||||||||||||||||||||||||||||||||||||||||
0x9206 | SubjectDistance | signed rational | 1 | 到焦点的距离, 单位是米. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9207 | MeteringMode | unsigned short | 1 | 曝光的测光方法. '0' 表示未知, '1' 为平均测光, '2' 为中央重点测光, '3' 是点测光, '4' 是多点测光, '5' 是多区域测光, '6' 部分测光, '255' 则是其他. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9208 | LightSource | unsigned short | 1 | 光源, 实际上是表示白平衡设置. '0' 意味着未知, '1'是日光, '2'是荧光灯, '3' 白炽灯(钨丝), '10' 闪光灯, '17' 标准光A, '18' 标准光B, '19' 标准光C, '20' D55, '21' D65, '22' D75, '255' 为其他. | |||||||||||||||||||||||||||||||||||||||||||||||
0x9209 | Flash | unsigned short | 1 | '0' 表示闪光灯没有闪光, '1' 表示闪光灯闪光, '5' 表示闪光但没有检测反射光, '7' 表示闪光且检测了反射光. | |||||||||||||||||||||||||||||||||||||||||||||||
0x920a | FocalLength | unsigned rational | 1 | 拍摄照片时的镜头的焦距长度. 单位是毫米. | |||||||||||||||||||||||||||||||||||||||||||||||
0x927c | MakerNote | undefined | 制造商的内部数据. 一些制造商如 Olympus/Nikon/Sanyo 等在这个区域中使用IFD 格式的数据. | ||||||||||||||||||||||||||||||||||||||||||||||||
0x9286 | UserComment | undefined | 存储用户的注释. 这个标签允许使用两字节的德字符或者 unicode. 前8 个字节描述的是字符集. 'JIS' 是日文 (著名的有 Kanji). '0x41,0x53,0x43,0x49,0x49,0x00,0x00,0x00':ASCII '0x4a,0x49,0x53,0x00,0x00,0x00,0x00,0x00':JIS '0x55,0x4e,0x49,0x43,0x4f,0x44,0x45,0x00':Unicode '0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00':Undefined | ||||||||||||||||||||||||||||||||||||||||||||||||
0x9290 | SubsecTime | ascii string | 一些数字相机每秒能拍摄 2~30 张照片, 但是DateTime/DateTimeOriginal/DateTimeDigitized 标签只能记录到秒单位的时间. SubsecTime 标签就是用来记录秒后面的数据(微秒). 例如, DateTimeOriginal = "1996:09:01 09:15:30", SubSecTimeOriginal = "130", 合并起来的原始的拍摄 时间就是 "1996:09:01 09:15:30.130" | ||||||||||||||||||||||||||||||||||||||||||||||||
0x9291 | SubsecTimeOriginal | ascii string | |||||||||||||||||||||||||||||||||||||||||||||||||
0x9292 | SubsecTimeDigitized | ascii string | |||||||||||||||||||||||||||||||||||||||||||||||||
0xa000 | FlashPixVersion | undefined | 4 | 存储FlashPix 的版本信息. 如果图像数据是基于 FlashPix formar Ver.1.0, 则这个值为 "0100". 因为它不是一个用NULL(0x00)来终结的字符串,所以这里的类型是 'undefined'. | |||||||||||||||||||||||||||||||||||||||||||||||
0xa001 | ColorSpace | unsigned short | 1 | 定义色彩空间. DCF 图像必须使用 sRGB 色彩空间因此这个值总是 '1'. 如果这个照片使用了 其他的色彩空间, 这个值是 '65535':未校准(Uncalibrated). | |||||||||||||||||||||||||||||||||||||||||||||||
0xa002 | ExifImageWidth | unsigned short/long | 1 | 主图像的尺寸大小. | |||||||||||||||||||||||||||||||||||||||||||||||
0xa003 | ExifImageHeight | unsigned short/long | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
0xa004 | RelatedSoundFile | ascii string | 如果数字相机能够纪录图像的音频数据, 则表示音频数据的名字. | ||||||||||||||||||||||||||||||||||||||||||||||||
0xa005 | ExifInteroperabilityOffset | unsigned long | 1 | 表示这是一个扩展"ExifR98", 细节未知. 这个值经常是IFD格式的数据. 当前这儿有两个 目录项, 第一个是 Tag0x0001, 值是"R98", 下一个是 Tag0x0002, 它的值为 "0100". | |||||||||||||||||||||||||||||||||||||||||||||||
0xa20e | FocalPlaneXResolution | unsigned rational | 1 | 表示CCD的像素密度. 如果你的相机是百万像素的并且是用低分辨率(如VGA模式) 来拍摄照片, 这个值可以通过照片的分辨率来重新采样. 在这种情况下, FocalPlaneResolution 就不是CCD的实际的分辨率. | |||||||||||||||||||||||||||||||||||||||||||||||
0xa20f | FocalPlaneYResolution | unsigned rational | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
0xa210 | FocalPlaneResolutionUnit | unsigned short | 1 | FocalPlaneXResoluton/FocalPlaneYResolution的单位. '1' 表示没有单位, '2'是英寸inch, '3' 表示厘米. 注意:一些Fujifilm的数码相机(如.FX2700,FX2900,Finepix4700Z/40i 等) 使用的值是 '3' 所以它的单位一定是 '厘米' , 但是它们的分辨率单位就变成'8.3mm?'(1/3in.?). 这是Fuji 的 BUG? 从Finepix4900Z 开始这个值就使用 '2' 了但仍然跟实际的值不吻合. | |||||||||||||||||||||||||||||||||||||||||||||||
0xa215 | ExposureIndex | unsigned rational | 1 | 跟ISOSpeedRatings(0x8827)一样但是数据类型是 unsigned rational. 只有Kodak的数字相机使用 这个标签来替代 ISOSpeedRating, 我不知道这是为什么(历史原因?). | |||||||||||||||||||||||||||||||||||||||||||||||
0xa217 | SensingMethod | unsigned short | 1 | 表示图像传感器单元的类型. '2' 意味着这是一个芯片颜色区域传感器, 几乎所有的数字相机都 使用这个类型. | |||||||||||||||||||||||||||||||||||||||||||||||
0xa300 | FileSource | undefined | 1 | 显示图像来源. 值 '0x03' 表示图像源是数字定格相机. | |||||||||||||||||||||||||||||||||||||||||||||||
0xa301 | SceneType | undefined | 1 | 表示拍摄场景的类型. 值 '0x01' 表示图像是通过相机直接拍摄出来的. | |||||||||||||||||||||||||||||||||||||||||||||||
0xa302 | CFAPattern | undefined | 表示色彩过滤阵列(CFA) 几何模式.
色彩过滤和CFA值之间的关系.
|
下面是一段修改JPEG拍摄时间的代码:
int main(void)
{
FILE *fp = fopen("test.jpg", "rb");
if (fp == NULL)
{
printf("打开test.jpg失败\n");
return -1;
}
fseek(fp, 0, SEEK_END);
int nFileLen = ftell(fp);
printf("文件长度: %d\n", nFileLen);
fseek(fp, 0, SEEK_SET);
int nE0Length = 0;
unsigned char *pOriginFileBuffer = (unsigned char *)malloc(nFileLen);
memset(pOriginFileBuffer, 0, nFileLen);
int ret = fread(pOriginFileBuffer, 1, nFileLen, fp);
if (ret != nFileLen)
{
printf("读取文件失败: %d\n", ret);
fclose(fp);
return -1;
}
fclose(fp);
printf("%x %x...%x %x\n", pOriginFileBuffer[0], pOriginFileBuffer[1], pOriginFileBuffer[nFileLen - 2], pOriginFileBuffer[nFileLen - 1]);
if (pOriginFileBuffer[2] == 0xFF && pOriginFileBuffer[3] == 0xE0)
{
nE0Length = pOriginFileBuffer[4] | (pOriginFileBuffer[5] << 8);
printf("nE0Length: %d\n", nE0Length);
nE0Length += 2;
}
int nOriginalHeadCopyLength = nE0Length + 2;
int nNewFileLen = nFileLen + 100;
unsigned char *pNewFileBuffer = (unsigned char *)malloc(nNewFileLen);
unsigned char *pTempPosition = pNewFileBuffer;
int nCopyLength = 0;
//拷贝JPG头包括APPE0
memcpy(pTempPosition, pOriginFileBuffer, nOriginalHeadCopyLength);
pTempPosition += nOriginalHeadCopyLength;
nCopyLength += nOriginalHeadCopyLength;
/* 构 造 APPE1 */
/* FF E1 LLLL 45 79 69 66 00 00 49 49 2A 00 08 00 00 00*/ /* 其 中 长 度 LLLL 需 要 确 定 下 来 计算长度的时候需要包含LLLL*/
/* e x i f \0 \0 I I */
unsigned char szAPPE1FixHeader[] = {
0xFF, 0xE1,
0x00, 0x48, /* APPE1 数 据 长 度 */
0x45, 0x78, 0x69, 0x66, 0x00, 0x00,/*Exif头*/
0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00/*TIFF头*/
};
int nAPPE1FixHeaderLength = sizeof(szAPPE1FixHeader) / sizeof(unsigned char);
memcpy(pTempPosition, szAPPE1FixHeader, nAPPE1FixHeaderLength);
pTempPosition += nAPPE1FixHeaderLength;
nCopyLength += nAPPE1FixHeaderLength;
unsigned char szIFD0HeaderTag[] = { 0x01, 0x00, /* 目 录 个 数 2 个*/
0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, /* Exif 子 IFD 的 偏 移 量 */
0x00, 0x00, 0x00, 0x00 }; /* 连 接 结 束 */
int nIFD0HeaderTagLength = sizeof(szIFD0HeaderTag) / sizeof(unsigned char);
memcpy(pTempPosition, szIFD0HeaderTag, nIFD0HeaderTagLength);
pTempPosition += nIFD0HeaderTagLength;
nCopyLength += nIFD0HeaderTagLength;
/* 填 充 子 IFD */
unsigned char szSubIFD0HeaderTag[] = { 0x01, 0x00, /* 目 录 个 数 1 个*/
0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, /* 拍 摄 时 间 */
0x00, 0x00, 0x00, 0x00 }; /* 连 接 结 束 */
int uiszSubIFD0HeaderTagLength = sizeof(szSubIFD0HeaderTag) / sizeof(unsigned char);
memcpy(pTempPosition, szSubIFD0HeaderTag, uiszSubIFD0HeaderTagLength);
pTempPosition = pTempPosition + uiszSubIFD0HeaderTagLength;
nCopyLength = nCopyLength + uiszSubIFD0HeaderTagLength;
//拍摄时间20字节
char szTime[20] = "2019:04:04 13:22:22";
memcpy(pTempPosition, szTime, 20);
pTempPosition += 20;
nCopyLength += 20;
/* 拷 贝 原 照 片 剩 余 大 小 */
memset(pTempPosition, 0, 8);
memcpy(pTempPosition + 8, (pOriginFileBuffer + nOriginalHeadCopyLength), (nFileLen - nOriginalHeadCopyLength));
FILE *fwp = fopen("new.jpg", "wb");
if (fwp)
{
fwrite(pNewFileBuffer, 1, nNewFileLen, fwp);
fclose(fwp);
}
free(pOriginFileBuffer);
return 0;
}
参考文章: