当前位置: 首页 > 工具软件 > libexif > 使用案例 >

基于libexif写入/读取自定义Exif 信息(曝光模式,感光度,GPS等信息)

徐峰
2023-12-01

libexif 简介

libexif 是一个用来读取数码相机照片中包含的 EXIF 信息的 C 语言库,支持多种平台。可以使用libexif 库对jpeg图片进行exif信息的写入,读取,修改等操作

libexif官网
libexif-github地址

使用libexif 给jepg图片添加exif信息

/* Get an existing tag, or create one if it doesn't exist */
static ExifEntry *init_tag(ExifData *exif, ExifIfd ifd, ExifTag tag)
{
	ExifEntry *entry;
	/* Return an existing tag if one exists */
	if (!((entry = exif_content_get_entry (exif->ifd[ifd], tag)))) {
	    /* Allocate a new entry */
	    entry = exif_entry_new ();
	    assert(entry != NULL); /* catch an out of memory condition */
	    entry->tag = tag; /* tag must be set before calling
				 exif_content_add_entry */

	    /* Attach the ExifEntry to an IFD */
	    exif_content_add_entry (exif->ifd[ifd], entry);

	    /* Allocate memory for the entry and fill with default data */
	    exif_entry_initialize (entry, tag);

	    /* Ownership of the ExifEntry has now been passed to the IFD.
	     * One must be very careful in accessing a structure after
	     * unref'ing it; in this case, we know "entry" won't be freed
	     * because the reference count was bumped when it was added to
	     * the IFD.
	     */
	    exif_entry_unref(entry);
	}
	return entry;
}

/* Create a brand-new tag with a data field of the given length, in the
 * given IFD. This is needed when exif_entry_initialize() isn't able to create
 * this type of tag itself, or the default data length it creates isn't the
 * correct length.
 */
static ExifEntry *create_tag(ExifData *exif, ExifIfd ifd, ExifTag tag, size_t len, ExifFormat format)
{
	void *buf;
	ExifEntry *entry;
	
	/* Create a memory allocator to manage this ExifEntry */
	ExifMem *mem = exif_mem_new_default();
	assert(mem != NULL); /* catch an out of memory condition */

	/* Create a new ExifEntry using our allocator */
	entry = exif_entry_new_mem (mem);
	assert(entry != NULL);

	/* Allocate memory to use for holding the tag data */
	buf = exif_mem_alloc(mem, len);
	assert(buf != NULL);

	/* Fill in the entry */
	entry->data = buf;
	entry->size = len;
	entry->tag = tag;
	entry->components = len;
	entry->format = format;

	/* Attach the ExifEntry to an IFD */
	exif_content_add_entry (exif->ifd[ifd], entry);

	/* The ExifMem and ExifEntry are now owned elsewhere */
	exif_mem_unref(mem);
	exif_entry_unref(entry);

	return entry;
}


long filesize( FILE *fp )
{
    long int save_pos;
    long size_of_file;

    /* Save the current position. */
    save_pos = ftell( fp );
    
    /* Jump to the end of the file. */
    fseek( fp, 0L, SEEK_END );
    
    /* Get the end position. */
    size_of_file = ftell( fp );
    
    /* Jump back to the original position. */
    fseek( fp, save_pos, SEEK_SET );

    return( size_of_file );
}

/*
	给jpeg图片添加 exif信息
	input_file_fath 输入jpeg图片
	output_file_path 加exif信息的jpeg图片保存路径
	
*/
int add_customer_exif_info_jpeg(char *input_file_fath, char *output_file_path)
{
	int rc = 1;
	unsigned char *exif_data = NULL;
	unsigned int exif_data_len;
	ExifEntry *entry = NULL;
	ExifData *exif = NULL;
	FILE *fp_src_file = NULL;
	FILE *fp_dst_file = NULL;
	
	unsigned long src_file_length;
	unsigned char *ptr_src_file_buf = NULL;

	char camera_maker[32];   // 相机制造商
	char camera_model[32];   // 实际型号
	unsigned short iso_val = 60;	//iso
	ExifRational exposure_time;   //曝光时间
	ExifRational f_number;    //光圈值
	ExifSRational exposure_bias_value;
	ExifRational focal_length;    //光圈值
	ExifRational max_aperture_value;    //光圈值
	ExifShort metering_mode;          //测光模式
	ExifRational subject_distance;    //光圈值
	ExifShort flash_mode;              //闪光灯模式	
	ExifRational flash_energy;    //闪光灯能量
	ExifRational gps_lat;         //gps 经度
	ExifRational gps_long;         //gps 维度 
	char gps_lat_ref[2] = "W";
	char gps_long_ref[2] = "N";
	
	
	
	exif = exif_data_new();
	if (!exif) {
		fprintf(stderr, "Out of memory\n");
		return rc;
	}
	
	/* Set the image options */
	exif_data_set_option(exif, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION);
	exif_data_set_data_type(exif, EXIF_DATA_TYPE_COMPRESSED);
	exif_data_set_byte_order(exif, FILE_BYTE_ORDER);

	/* Create the mandatory EXIF fields with default data */
	//exif_data_fix(exif);

	/************EXIF信息添加**************************/
	
	//相机制造商
	sprintf(camera_maker, "%s", "Sony");
	entry = create_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_MAKE, strlen(camera_maker) + 1, EXIF_FORMAT_ASCII);
	memcpy(entry->data, camera_maker, strlen(camera_maker) + 1);

	//相机型号
	sprintf(camera_model, "%s", "imx274");
	entry = create_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_MODEL, strlen(camera_model) + 1, EXIF_FORMAT_ASCII);
	memcpy(entry->data, camera_model, strlen(camera_model) + 1);
	
	
	//曝光时间设置 uint:s
	exposure_time.numerator = 1;
	exposure_time.denominator = 18;
	entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_TIME);	
	exif_set_rational(entry->data, FILE_BYTE_ORDER, exposure_time);
	
	//ISO 大小
	entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_ISO_SPEED_RATINGS);
	exif_set_short(entry->data, FILE_BYTE_ORDER, iso_val);

	//曝光补偿 unit EV
	exposure_bias_value.numerator = 0;
	exposure_bias_value.denominator = 1;
	entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_BIAS_VALUE);
	exif_set_srational(entry->data, FILE_BYTE_ORDER, exposure_bias_value);

	//焦距 numerator/denominator,  uint mm 
	focal_length.numerator = 35;
	focal_length.denominator = 1;
	entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH);
	exif_set_rational(entry->data, FILE_BYTE_ORDER, focal_length);

	/*
	https://baike.baidu.com/item/%E5%85%89%E5%9C%88%E5%80%BC/10310445?fr=aladdin
	最大光圈 AV f/(max_aperture_value.numerator/max_aperture_value.denominator)
	光圈值(AV) 0  1    2  3   4  5   6 7  8  9 10
	光圈数值(F)1  1.4  2  2.8 4  5.6 8 11 16 2232
	*/
	//光圈值 最大光值
	f_number.numerator = 4;
	f_number.denominator = 1;
	entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_FNUMBER);
	exif_set_rational(entry->data, FILE_BYTE_ORDER, f_number);
	max_aperture_value.numerator = 4;
	max_aperture_value.denominator = 1;
	entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_MAX_APERTURE_VALUE);
	exif_set_rational(entry->data, FILE_BYTE_ORDER, max_aperture_value);

	/*
		测光模式 1~6
		1 平均测光
		2 中央重点测光
		3 点测光
		4 多点测光
		5 矩阵测光
		6 部分测光
	*/
	metering_mode = 5;
	entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_METERING_MODE);
	exif_set_short(entry->data, FILE_BYTE_ORDER, metering_mode);

	//目标距离
	subject_distance.numerator = 0;
	subject_distance.denominator = 1;
	entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_SUBJECT_DISTANCE);
	exif_set_rational(entry->data, FILE_BYTE_ORDER, subject_distance);
	
	/*
		闪光灯模式
		0 - 关闭
		1 - 开启
	*/
	flash_mode = 1;
	entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_FLASH);
	exif_set_short(entry->data, FILE_BYTE_ORDER, flash_mode);

	//闪光灯能量 这里根据flash mode简单设置下 Unit:bcps
	flash_energy.numerator = flash_mode > 0 ? 0xffff : 0;
	flash_energy.denominator = 1;
	entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_FLASH_ENERGY);
	exif_set_rational(entry->data, FILE_BYTE_ORDER, flash_energy);

	//gps 经度参考
	entry = create_tag(exif, EXIF_IFD_GPS, EXIF_TAG_GPS_LATITUDE_REF, 2, EXIF_FORMAT_ASCII);
	memcpy(entry->data, gps_lat_ref, 2);
	
	//gps 经度
	gps_lat.numerator = 114;
	gps_lat.denominator = 3;
	entry = create_tag(exif, EXIF_IFD_GPS, EXIF_TAG_GPS_LATITUDE, sizeof(gps_lat), EXIF_FORMAT_RATIONAL);
	exif_set_rational(entry->data, FILE_BYTE_ORDER, gps_lat);

	
	//gps 纬度参考

	entry = create_tag(exif, EXIF_IFD_GPS, EXIF_TAG_GPS_LONGITUDE_REF,2, EXIF_FORMAT_ASCII);
	memcpy(entry->data, gps_long_ref, 2);
		
	//gps 纬度
	gps_long.numerator = 18;
	gps_long.denominator = 2;
	entry = create_tag(exif, EXIF_IFD_GPS, EXIF_TAG_GPS_LONGITUDE, sizeof(gps_lat), EXIF_FORMAT_RATIONAL);
	exif_set_rational(entry->data, FILE_BYTE_ORDER, gps_long);

	
	/* Get a pointer to the EXIF data block we just created */
	exif_data_save_data(exif, &exif_data, &exif_data_len);
	assert(exif_data != NULL);

	
	fp_src_file = fopen(input_file_fath, "rb");
	if (!fp_src_file) {
		fprintf(stderr, "Error creating file %s\n", input_file_fath);
		exif_data_unref(exif);
		return rc;
	}

	/*read src jpeg file to buff*/
	src_file_length = filesize(fp_src_file);
	
	ptr_src_file_buf = (unsigned char *)malloc(src_file_length);
	if (!ptr_src_file_buf) {
		printf("Allocate buf for %s failed.\n", input_file_fath);
		goto errout;
	}
	
	if(fread(ptr_src_file_buf, src_file_length, 1, fp_src_file) != 1) {
		printf("Read %s error\n", input_file_fath);
		goto errout;
	}
	fclose(fp_src_file);
	
	if (!(ptr_src_file_buf[0] == 0xFF && ptr_src_file_buf[1] == 0xD8)) {
		printf("%s is not jpeg file,won't add exif for it!\n", input_file_fath);
		goto errout;
	}

	if ((ptr_src_file_buf[6] == 'E' && ptr_src_file_buf[7] == 'x' && ptr_src_file_buf[8] == 'i' && ptr_src_file_buf[9] == 'f')) {
		printf("%s adready add exif,won't overwrite exif info!\n", input_file_fath);
		goto errout;
	}
	
	fp_dst_file = fopen(output_file_path, "wb");
	if (!fp_dst_file) {
		fprintf(stderr, "Error creating file %s\n", output_file_path);
		exif_data_unref(exif);
		return rc;
	}
	
	/* Write EXIF header */
	if (fwrite(exif_header, exif_header_len, 1, fp_dst_file) != 1) {
		fprintf(stderr, "Error writing to file %s\n", output_file_path);
		goto errout;
	}
	/* Write EXIF block length in big-endian order */
	if (fputc((exif_data_len+2) >> 8, fp_dst_file) < 0) {
		fprintf(stderr, "Error writing to file %s\n", output_file_path);
		goto errout;
	}
	if (fputc((exif_data_len+2) & 0xff, fp_dst_file) < 0) {
		fprintf(stderr, "Error writing to file %s\n", output_file_path);
		goto errout;
	}
	/* Write EXIF data block */
	if (fwrite(exif_data, exif_data_len, 1, fp_dst_file) != 1) {
		fprintf(stderr, "Error writing to file %s\n", output_file_path);
		goto errout;
	}
	/* Write JPEG image data, skipping the non-EXIF header */
	if (fwrite(ptr_src_file_buf +image_data_offset, src_file_length-image_data_offset, 1, fp_dst_file) != 1) {
		fprintf(stderr, "Error writing to file %s\n", output_file_path);
		goto errout;
	}
		
	rc = 0;

errout:

	if (ptr_src_file_buf) {
		free(ptr_src_file_buf);
	}
	
	/* The allocator we're using for ExifData is the standard one, so use
	 * it directly to free this pointer.
	 */
	free(exif_data);
	exif_data_unref(exif);

	return rc;
}

int add_customer_exif_info_jpeg(char *input_file_fath, char *output_file_path)
这个函数就是对input_file_fath 图片添加自定义的exif信息,然后输出到output_file_path
从这个函数可以看到libexif添加exif信息的步骤也是相当简单,添加流程如下:

entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_METERING_MODE);
exif_set_short(entry->data, FILE_BYTE_ORDER, metering_mode);
或者
//gps 纬度
gps_long.numerator = 18;
gps_long.denominator = 2;
entry = create_tag(exif, EXIF_IFD_GPS, EXIF_TAG_GPS_LONGITUDE, sizeof(gps_lat), EXIF_FORMAT_RATIONAL);
exif_set_rational(entry->data, FILE_BYTE_ORDER, gps_long);

init_tag()create_tag() 有点差异:
init_tag() 提供了常见的IFD及tag的初始化操作
create_tag() 如果init_tag() 没有默认支持你要操作IFD及tag,就用这个接口初始化一个entry

插入exif步骤:

  1. 创建一个entry入口
  2. 设置entery->data就可以了

使用libexif 读取jepg的exif信息

void read_exif_entry(ExifEntry *ee, void* ifd)
{
	char v[1024];
//	strncpy(t, exif_tag_get_title_in_ifd(ee->tag, exif_entry_get_ifd(ee)), sizeof(t));
//	strncpy(t, exif_tag_get_title_in_ifd(ee->tag, *((ExifIfd*)ifd)), sizeof(t));
	//trim t
	printf("%s: %s\n"
//			, exif_tag_get_name_in_ifd(ee->tag, *((ExifIfd*)ifd))
			, exif_tag_get_title_in_ifd(ee->tag, *((ExifIfd*)ifd))
//			, exif_tag_get_description_in_ifd(ee->tag, *((ExifIfd*)ifd))
			, exif_entry_get_value(ee, v, sizeof(v)));
}
 
void read_exif_content(ExifContent *ec, void *user_data)
{
	ExifIfd ifd = exif_content_get_ifd(ec);
	if (ifd == EXIF_IFD_COUNT)
		fprintf(stderr, "exif_content_get_ifd error");
	printf("======IFD: %d %s======\n", ifd, exif_ifd_get_name(ifd));
	exif_content_foreach_entry(ec, read_exif_entry, &ifd);
}
 
int main(int argc, char** argv)
{
	if (argc < 2) {
		printf("Usage %s jpeg_list\n", argv[0]);
	}

	for (int i = 1; i<argc; i++) {
		printf("-------------->ExifInfo:%s Start<--------------\n", argv[i]);
		ExifData* ed = exif_data_new_from_file(argv[1]);
		if (!ed) {
			fprintf(stderr, "An error occur\n");
			return 1;
		}
	
		//exif_data_set_option(ed, 
		exif_data_foreach_content(ed, read_exif_content, NULL);
	
		exif_data_unref(ed);
		
		printf("-------------->ExifInfo:%s End<--------------\n\n", argv[i]);
	}
	
 
	return 0;
}
···

读取exif信息过程也比较简单就是依次遍历各个entry,然后打印出entry相关的信息

exif_data_foreach_content
	read_exif_content(ExifContent *ec, void *user_data)
		exif_content_foreach_entry(ec, read_exif_entry, &ifd);
			 read_exif_entry(ExifEntry *ee, void* ifd) //这个函数最终会打印出各个Entery的内容

代码资源地址及使用方法

csdn资源地址
代码使用SourceInsight 或者notepad++ 打开,否则可能出现中文乱码
资源详细的使用见里面的MyReadme.txt,这里简单的介绍下资源的使用:


 1.编译库
    运行libexif-mater 文件夹的mybuild_ubuntu.sh
    运行后当前目录build的文件夹会有编译文件
    ls build/
    include  lib  share

2. 编译示例demo
 cd contrib/examples/
 ./compile_exampale.sh
    gcc -Wall -I ../../build/include/ --static -o photographer photographer.c -L ../../build/lib/ -lexif -lm
    gcc -Wall -I ../../build/include/ --static -o thumbnail thumbnail.c -L ../../build/lib/ -lexif -lm
    gcc -Wall -I ../../build/include/ --static -o write-exif write-exif.c -L ../../build/lib/ -lexif -lm
    gcc -Wall -I ../../build/include/ --static -o test_jpeg_exif_new test_jpeg_exif_new.c -L ../../build/lib/ -lexif -lm
    gcc -Wall -I ../../build/include/ --static -o read_jpeg_exif read_jpeg_exif.c -L ../../build/lib/ -lexif -lm
    All done!!!

test_jpeg_exif_new  写自定义exif 信息
read_jpeg_exif 读取写入的exif信息

写exif 信息使用:
用windows自带画图软件随便产生一个jpg文件或者使用自带的test.jpg文件
./test_jpeg_exif_new test.jpg test_exif.jps
Add customer exif info for test.jpg ok! #正常会有这个输出

读取写入的exif信息:
 ./read_jpeg_exif test_exif.jps
-------------->ExifInfo:test_exif.jps Start<--------------
======IFD: 0 0======
X-Resolution: 72
Y-Resolution: 72
Resolution Unit: Inch
======IFD: 1 1======
======IFD: 2 EXIF======
Exposure Time: 1/18 sec.
F-Number: f/4.0
ISO Speed Ratings: 60
Exposure Bias: 0.00 EV
Maximum Aperture Value: 4.00 EV (f/4.0)
Subject Distance: 0.0 m
Metering Mode: Pattern
Flash: Flash fired
Focal Length: 35.0 mm
Flash Energy: 65535
Exif Version: Exif Version 2.1
FlashPixVersion: FlashPix Version 1.0
Color Space: Uncalibrated
======IFD: 3 GPS======
North or South Latitude: W
Latitude: 38.0, 132897/0, 0/0, 0/0, 0/0, 0/0, 0/0, 0/0
East or West Longitude: N
Longitude: 9.0, 132833/0, 0/0, 0/0, 0/0, 0/0, 0/0, 0/0
======IFD: 4 Interoperability======
-------------->ExifInfo:test_exif.jps End<--------------
 类似资料: