You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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,哪怕标签编号编译时未知。步骤如下:

  1. 遍历EXIF标签时,对每个libtiff未识别的标签,调用TIFFDefineField动态注册
  2. 注册后即可用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的字段检查:

  1. 获取EXIF目录句柄
  2. 构造符合TIFF规范的原始目录条目(12字节结构:2字节Tag + 2字节Type + 4字节Count + 4字节Value/偏移)
  3. 调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 16:44:50