如何将EXIF标签存入TIFF文件?解决libtiff未知标签存储难题
问题描述
需要将JPG/PNG/WEBP中的EXIF标签转移到TIFF文件,但TIFF不允许直接存储完整的EXIF二进制块。核心痛点:
- 无法存储libtiff未识别的标签
- 标签编号来自EXIF,编译时无法预知,无法提前定义自定义标签
- 已知每个标签的Tag/Field/Long2/Value信息,但
TIFFSetField只能处理libtiff识别的标签,无法直接写入所有字段
当前实现代码
以下代码仅能复制libtiff识别的标签:
int AddExifFields(TIFF *tiff, const unsigned char *profile_data, size_t profile_length, MagickBool logging) { const TIFFField *fip; // 省略部分代码... uint32_t IFDpos = 4; uint32_t FieldCount = 0; uint32_t Value, Tag, Long2; uint16_t EntryNum, Field; do { Value = LD_UINT32(profile_data + IFDpos); if(Value <= IFDpos || Value < 8) return FieldCount; // 0表示停止;IFDPOS必须递增 IFDpos = Value; EntryNum = LD_UINT16(profile_data + IFDpos); IFDpos += 2; while(EntryNum > 0) { Tag = LD_UINT16(profile_data + IFDpos); Field = LD_UINT16(profile_data + IFDpos + 2); Long2 = LD_UINT32(profile_data + IFDpos + 4); Value = LD_UINT32(profile_data + IFDpos + 8); fip = TIFFFindField(tiff, Tag, TIFF_ANY); if(logging) (void)LogMagickEvent(CoderEvent,GetMagickModule(),"从EXIF提取标签 %xh, 类型 %d, 长度 %d, 值 %d %s", Tag, Field, Long2, Value, FipFieldName(fip)); if(fip != NULL) /* libtiff无法识别的标签会被跳过 */ { const TIFFDataType FDT = TIFFFieldDataType(fip); switch(Field) { case TIFF_ASCII: if(FDT != TIFF_ASCII) break; /* 类型不兼容 */ if(Value >= profile_length - 1) break; /* 字符串超出EXIF边界 */ if(TIFFSetField(tiff, Tag, profile_data + Value)) FieldCount++; break; case TIFF_BYTE: case TIFF_SHORT: case TIFF_LONG: if(FDT != TIFF_BYTE && FDT != TIFF_SHORT && FDT != TIFF_LONG) break; if(TIFFSetField(tiff, Tag, Value)) FieldCount++; break; //case TIFF_SRATIONAL: 待处理 case TIFF_RATIONAL: if(FDT == TIFF_RATIONAL) { double d = Value / (double)Long2; if(TIFFSetField(tiff, Tag, d)) FieldCount++; } break; } } IFDpos += 12; // 跳转到下一个目录条目 if(IFDpos + 12 >= profile_length) return FieldCount; EntryNum--; } } while(IFDpos + 4 < profile_length); return FieldCount; } // 调用代码 uint64_t dir_offset = 0; TIFFCreateEXIFDirectory(tiff); AddExifFields(tiff, profile_data, profile_length, logging); TIFFWriteCustomDirectory(tiff, &dir_offset); TIFFSetDirectory(tiff, 0); TIFFSetField(tiff, TIFFTAG_EXIFIFD, dir_offset);
绕过限制的实现方法
方法1:动态注册未知EXIF标签
libtiff允许运行时动态注册TIFFField,哪怕标签编号编译时未知。步骤如下:
- 遍历EXIF标签时,对每个libtiff未识别的标签,调用
TIFFDefineField动态注册 - 注册后即可用
TIFFSetField写入值
修改AddExifFields中的逻辑,在fip == NULL的分支添加注册代码:
if(fip == NULL) { // 动态注册未知标签 TIFFFieldInfo fieldInfo; fieldInfo.tag = Tag; fieldInfo.name = "UnknownEXIFTag"; // 自定义名称,不影响实际存储 fieldInfo.count = 1; // 根据EXIF标签的实际计数调整 fieldInfo.type = (TIFFDataType)Field; fieldInfo.bit = 0; fieldInfo.flags = TIFF_FIELD_CUSTOM | TIFF_FIELD_EXIF; // 标记为EXIF字段 // 注册字段到当前EXIF目录 if(TIFFDefineField(tiff, &fieldInfo) != 0) { if(logging) (void)LogMagickEvent(CoderEvent,GetMagickModule(),"成功注册未知标签 %xh", Tag); fip = TIFFFindField(tiff, Tag, TIFF_ANY); } } // 继续原来的写入逻辑 if(fip != NULL) { // ... 原有的switch写入代码 ... }
注意:
- 必须确保注册的
type和count与EXIF标签的实际类型完全匹配 - 多值标签(比如数组类型)需要调整
count参数,并传入对应的数据指针而非单个值
方法2:直接写入原始TIFF目录条目
如果动态注册仍有局限,可以直接操作TIFF目录结构,绕过libtiff的字段检查:
- 获取EXIF目录句柄
- 构造符合TIFF规范的原始目录条目(12字节结构:2字节Tag + 2字节Type + 4字节Count + 4字节Value/偏移)
- 调用
TIFFWriteDirectoryEntry直接写入条目
示例代码片段:
// 处理未知标签时 TIFFDirEntry entry; entry.tag = Tag; entry.type = (TIFFDataType)Field; entry.count = Long2; // 这里Long2对应EXIF中的计数,需确认对应关系 // 根据数据大小决定直接存值还是存偏移 if(TIFFDataSize(entry.type) * entry.count <= 4) { entry.value = Value; } else { // 分配TIFF内存存储数据,获取偏移地址 entry.value = TIFFWriteBuffer(tiff, profile_data + Value, TIFFDataSize(entry.type)*entry.count); } // 写入到EXIF目录 if(TIFFWriteDirectoryEntry(tiff, &entry) != 0) { FieldCount++; if(logging) (void)LogMagickEvent(CoderEvent,GetMagickModule(),"直接写入未知标签 %xh 成功", Tag); }
注意:
- 必须严格遵循TIFF规范的目录条目结构
- 大尺寸数据需要用
TIFFWriteBuffer写入TIFF文件的空闲区域,避免覆盖已有数据 - 操作完成后需调用
TIFFRewriteDirectory确保目录结构被正确写入
内容的提问来源于stack exchange,提问作者Jára
相关产品推荐
相关产品推荐

