1. 前言
现在手机相机拍摄的照片都是JPG/JPEG格式,JPEG格式的照片可以附加EXIF信息,这个EXIF信息是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据,也就相当于图片的身份信息。它可以记录,拍摄的时间、拍摄的地点、相机型号、曝光参数等很多信息。
这篇文章介绍使用QT设计一个小工具,读取JPG图片的EXIF信息,得到照片的拍摄时间,再绘制到照片上,另存为新图片,代码里使用多线程处理,可以一次性选择多张照片,一键添加时间水印后另存到指定目录下。给照片添加时间水印后有很多方便的地方。比如:以后去打印店打印照片就能将时间打印出来,可以通过时间了解到这个照片的拍摄场景时间线,帮助回忆这个时间线发生的一些美好往事。
QT本身图片处理接口不支持读取EXIF信息,需要采用第三方库来完成,目前GitHub上有很多开源的库可以实现JPG图片的EXIF信息读取,比如:easyexif ,exiv2 等等。easyexif 使用比较简单,如果只是想要读取信息,使用easyexif 库非常方便,easyexif 是一个很精简的代码,整个项目只包含了2个文件: exif.h和exif.c
。
easyexif 库的GitHub地址:https://github.com/mayanklahiri/easyexif
exiv2 库的GitHub地址:https://github.com/Exiv2/exiv2
2. easyexif使用介绍
2.1 easyexif简介
来至官网的介绍:
这是一个小型的符合ISO规范的C ExIF解析库。
EasyExIF是一个小型、轻量级的C 库,它可以从JPEG文件中解析基本信息。它只使用了std::string库,纯C 编写。使用时,将JPEG文件的二进制内容传递给它,它会解析出几个最重要的EXIF字段。
为什么要用EasyExIF这个库?它包括一个.h和一个.c文件。
有时,我们只需要快速从JPEG文件的EXIF头中提取基本信息:拍摄图像的时间(不是文件时间戳、相机的内部时间)、F-stop或曝光时间、嵌入EXIF文件的GPS信息、相机的品牌和型号等。问题是,现在市面上很多的EXIF库都不是很轻量级,也不容易集成到更大的程序中。EasyEXIF旨在解决这个问题,它是在一个非常自由的BSD许可证下发布的,几乎可以在任何地方使用。你的项目只需要加入两个文件就可以使用,不依赖于任何构建系统或外部库。
2.2 来至官网的示例代码
代码语言:javascript复制 #include "exif.h"
EXIFInfo result;
result.parseFrom(JPEGFileBuffer, BufferSize);
printf("Camera make : %sn", result.Make.c_str());
printf("Camera model : %sn", result.Model.c_str());
printf("Software : %sn", result.Software.c_str());
printf("Bits per sample : %dn", result.BitsPerSample);
printf("Image width : %dn", result.ImageWidth);
printf("Image height : %dn", result.ImageHeight);
printf("Image description : %sn", result.ImageDescription.c_str());
printf("Image orientation : %dn", result.Orientation);
printf("Image copyright : %sn", result.Copyright.c_str());
printf("Image date/time : %sn", result.DateTime.c_str());
printf("Original date/time: %sn", result.DateTimeOriginal.c_str());
printf("Digitize date/time: %sn", result.DateTimeDigitized.c_str());
printf("Subsecond time : %sn", result.SubSecTimeOriginal.c_str());
printf("Exposure time : 1/%d sn", (unsigned) (1.0/result.ExposureTime));
printf("F-stop : f/%.1fn", result.FNumber);
printf("ISO speed : %dn", result.ISOSpeedRatings);
printf("Subject distance : %f mn", result.SubjectDistance);
printf("Exposure bias : %f EVn", result.ExposureBiasValue);
printf("Flash used? : %dn", result.Flash);
printf("Metering mode : %dn", result.MeteringMode);
printf("Lens focal length : %f mmn", result.FocalLength);
printf("35mm focal length : %u mmn", result.FocalLengthIn35mm);
printf("GPS Latitude : %f degn", result.GeoLocation.Latitude);
printf("GPS Longitude : %f degn", result.GeoLocation.Longitude);
printf("GPS Altitude : %f mn", result.GeoLocation.Altitude);
3. 小工具实现源码
图片处理采用多线程方式处理,不卡主UI界面,处理结果会通过信号槽方式传递给UI界面进行显示。
下面贴出实现的核心代码:
代码语言:javascript复制#include "widget.h"
#include "ui_widget.h"
class IMAGE_CONFIG image_config;
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle("照片自动加水印");
//关联图片转换线程
connect(&scale_out_image,SIGNAL(LogSend(QString)),this,SLOT(Image_Log_Display(QString)));
//获取系统图片目录的路径
QStringList dir_list=QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
if(dir_list.size()>0)
{
ui->lineEdit->setText(dir_list.at(0) "/HandlePicture");
}
}
Widget::~Widget()
{
delete ui;
}
//选择照片
void Widget::on_pushButton_select_clicked()
{
QStringList filenamelist=QFileDialog::getOpenFileNames(this,"选择照片",ui->lineEdit->text(),tr("*.jpg *.jpeg"));
image_config.filenamelist=filenamelist;
for(int i=0;i<filenamelist.count();i )
{
Image_Log_Display(QString("已选:%1n").arg(filenamelist.at(i)));
// qDebug()<<filenamelist.at(i); //循环取出列表中的文件名称
}
}
//关闭线程
void ScaleOutImage::close()
{
image_config.run_flag=0;
this->quit();
this->wait();
}
//处理图片
int ScaleOutImage::HandleImage(QString file)
{
// Read the JPEG file into a buffer
FILE *fp = fopen(file.toUtf8().data(), "rb");
if (!fp)
{
qDebug("Can't open file.n");
return -1;
}
fseek(fp, 0, SEEK_END);
unsigned long fsize = ftell(fp);
rewind(fp);
unsigned char *buf = new unsigned char[fsize];
if (fread(buf, 1, fsize, fp) != fsize) {
qDebug("Can't read file.n");
delete[] buf;
return -2;
}
fclose(fp);
// Parse EXIF
easyexif::EXIFInfo result;
int code = result.parseFrom(buf, fsize);
delete[] buf;
if (code)
{
qDebug("Error parsing EXIF: code %dn", code);
return -3;
}
// Dump EXIF information
// qDebug("Camera make : %sn", result.Make.c_str());
// qDebug("Camera model : %sn", result.Model.c_str());
// qDebug("Software : %sn", result.Software.c_str());
// qDebug("Bits per sample : %dn", result.BitsPerSample);
// qDebug("Image width : %dn", result.ImageWidth);
// qDebug("Image height : %dn", result.ImageHeight);
// qDebug("Image description : %sn", result.ImageDescription.c_str());
// qDebug("Image orientation : %dn", result.Orientation);
// qDebug("Image copyright : %sn", result.Copyright.c_str());
// qDebug("Image date/time : %sn", result.DateTime.c_str());
// qDebug("Original date/time : %sn", result.DateTimeOriginal.c_str());
// qDebug("Digitize date/time : %sn", result.DateTimeDigitized.c_str());
// qDebug("Subsecond time : %sn", result.SubSecTimeOriginal.c_str());
// qDebug("Exposure time : 1/%d sn",
// (unsigned)(1.0 / result.ExposureTime));
// qDebug("F-stop : f/%.1fn", result.FNumber);
// qDebug("Exposure program : %dn", result.ExposureProgram);
// qDebug("ISO speed : %dn", result.ISOSpeedRatings);
// qDebug("Subject distance : %f mn", result.SubjectDistance);
// qDebug("Exposure bias : %f EVn", result.ExposureBiasValue);
// qDebug("Flash used? : %dn", result.Flash);
// qDebug("Flash returned light : %dn", result.FlashReturnedLight);
// qDebug("Flash mode : %dn", result.FlashMode);
// qDebug("Metering mode : %dn", result.MeteringMode);
// qDebug("Lens focal length : %f mmn", result.FocalLength);
// qDebug("35mm focal length : %u mmn", result.FocalLengthIn35mm);
// qDebug("GPS Latitude : %f deg (%f deg, %f min, %f sec %c)n",
// result.GeoLocation.Latitude, result.GeoLocation.LatComponents.degrees,
// result.GeoLocation.LatComponents.minutes,
// result.GeoLocation.LatComponents.seconds,
// result.GeoLocation.LatComponents.direction);
// qDebug("GPS Longitude : %f deg (%f deg, %f min, %f sec %c)n",
// result.GeoLocation.Longitude, result.GeoLocation.LonComponents.degrees,
// result.GeoLocation.LonComponents.minutes,
// result.GeoLocation.LonComponents.seconds,
// result.GeoLocation.LonComponents.direction);
// qDebug("GPS Altitude : %f mn", result.GeoLocation.Altitude);
// qDebug("GPS Precision (DOP) : %fn", result.GeoLocation.DOP);
// qDebug("Lens min focal length: %f mmn", result.LensInfo.FocalLengthMin);
// qDebug("Lens max focal length: %f mmn", result.LensInfo.FocalLengthMax);
// qDebug("Lens f-stop min : f/%.1fn", result.LensInfo.FStopMin);
// qDebug("Lens f-stop max : f/%.1fn", result.LensInfo.FStopMax);
// qDebug("Lens make : %sn", result.LensInfo.Make.c_str());
// qDebug("Lens model : %sn", result.LensInfo.Model.c_str());
// qDebug("Focal plane XRes : %fn", result.LensInfo.FocalPlaneXResolution);
// qDebug("Focal plane YRes : %fn", result.LensInfo.FocalPlaneYResolution);
QImage image(file);//加载图片
QPainter painter(&image);//构建 QImage 绘图对象
painter.setPen(Qt::white);
int font_size=image_config.font_size;
painter.setFont(QFont("宋体", font_size));
QString text="";
QRect rect;
rect.setX(0);
rect.setY(image.height()-font_size*2);
rect.setWidth(image.width());
rect.setHeight(font_size*2);
qDebug()<<"照片的尺寸:"<<image.rect();
qDebug()<<"水印尺寸位置:"<<rect;
if(image_config.camera_type)
{
text =result.Make.c_str();
text =" ";
}
if(image_config.camera_time)
{
QString csv=QString::fromStdString(result.DateTime);
QString date=csv.section(' ',0, 0); //日期
QString time=csv.section(' ',1, 1); //时间
date=date.replace(':',"/");
text =date " " time;
}
if(text.size()>0)
{
painter.drawText(rect,Qt::AlignHCenter,text);
qDebug()<<"绘制的水印:"<<text;
}
QString out=image_config.lineEdit_out_addr "/" QFileInfo(file).baseName() ".jpg";
//如果文件已经存在就先删除
if(QFileInfo(out).exists())
{
QFile::remove(out);
}
if(image.save(out))return 0;
return -6;
}
//线程执行函数
void ScaleOutImage::run()
{
QString out_name;
int run=0;
for(int i=0;i<image_config.filenamelist.count();i )
{
run=HandleImage(image_config.filenamelist.at(i));
if(run==0)
{
LogSend(QString("处理:%1成功.n").arg(QFileInfo(image_config.filenamelist.at(i)).fileName()));
}
else
{
LogSend(QString("处理:%1失败.n").arg(QFileInfo(image_config.filenamelist.at(i)).fileName()));
}
if(image_config.run_flag==0)
{
break;
}
}
}
void Widget::Image_Log_Display(QString text)
{
Log_Text_Display(ui->plainTextEdit_log,text);
}
/*日志显示*/
void Widget::Log_Text_Display(QPlainTextEdit *plainTextEdit_log,QString text)
{
//设置光标到文本末尾
plainTextEdit_log->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);
//当文本数量超出一定范围就清除
if(plainTextEdit_log->toPlainText().size()>4096)
{
plainTextEdit_log->clear();
}
plainTextEdit_log->insertPlainText(text);
//移动滚动条到底部
QScrollBar *scrollbar = plainTextEdit_log->verticalScrollBar();
if(scrollbar)
{
scrollbar->setSliderPosition(scrollbar->maximum());
}
}
//开始
void Widget::on_pushButton_start_clicked()
{
//创建目录
QDir dir_image(ui->lineEdit->text());
if(!dir_image.exists())
{
if(dir_image.mkdir(ui->lineEdit->text()))
{
Image_Log_Display("输出目录创建成功.n");
}
else
{
Image_Log_Display("输出目录创建失败.n");
return;
}
}
image_config.camera_time=ui->checkBox_camera_time->isChecked();
image_config.camera_type=ui->checkBox_camera_type->isChecked();
image_config.font_size=ui->spinBox->value();
image_config.lineEdit_out_addr=ui->lineEdit->text();
image_config.run_flag=1;
//开始转换
scale_out_image.start();
}
//停止
void Widget::on_pushButton_stop_clicked()
{
scale_out_image.close();
}
//帮助
void Widget::on_pushButton_about_clicked()
{
QMessageBox::about(this,"功能介绍",tr("获取JPG照片属性里的拍摄时间,绘制在照片上重新保存.n"));
}