设计一款照片一键加水印的小工具

举报
DS小龙哥 发表于 2022/02/16 11:41:13 2022/02/16
【摘要】 这篇文章介绍使用QT设计一个小工具,读取JPG图片的EXIF信息,得到照片的拍摄时间,再绘制到照片上,另存为新图片,代码里使用多线程处理,可以一次性选择多张照片,一键添加时间水印后另存到指定目录下。给照片添加时间水印后有很多方便的地方。比如:以后去打印店打印照片就能将时间打印出来,可以通过时间了解到这个照片的拍摄场景时间线,帮助回忆这个时间线发生的一些美好往事。

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

image-20220216113350464

image-20220216113415357

这是添加水印后的效果:

image.png

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许可证下发布的,几乎可以在任何地方使用。你的项目只需要加入两个文件就可以使用,不依赖于任何构建系统或外部库。

image-20220216112831991

2.2 来至官网的示例代码

  #include "exif.h"

  EXIFInfo result;
  result.parseFrom(JPEGFileBuffer, BufferSize);

  printf("Camera make       : %s\n", result.Make.c_str());
  printf("Camera model      : %s\n", result.Model.c_str());
  printf("Software          : %s\n", result.Software.c_str());
  printf("Bits per sample   : %d\n", result.BitsPerSample);
  printf("Image width       : %d\n", result.ImageWidth);
  printf("Image height      : %d\n", result.ImageHeight);
  printf("Image description : %s\n", result.ImageDescription.c_str());
  printf("Image orientation : %d\n", result.Orientation);
  printf("Image copyright   : %s\n", result.Copyright.c_str());
  printf("Image date/time   : %s\n", result.DateTime.c_str());
  printf("Original date/time: %s\n", result.DateTimeOriginal.c_str());
  printf("Digitize date/time: %s\n", result.DateTimeDigitized.c_str());
  printf("Subsecond time    : %s\n", result.SubSecTimeOriginal.c_str());
  printf("Exposure time     : 1/%d s\n", (unsigned) (1.0/result.ExposureTime));
  printf("F-stop            : f/%.1f\n", result.FNumber);
  printf("ISO speed         : %d\n", result.ISOSpeedRatings);
  printf("Subject distance  : %f m\n", result.SubjectDistance);
  printf("Exposure bias     : %f EV\n", result.ExposureBiasValue);
  printf("Flash used?       : %d\n", result.Flash);
  printf("Metering mode     : %d\n", result.MeteringMode);
  printf("Lens focal length : %f mm\n", result.FocalLength);
  printf("35mm focal length : %u mm\n", result.FocalLengthIn35mm);
  printf("GPS Latitude      : %f deg\n", result.GeoLocation.Latitude);
  printf("GPS Longitude     : %f deg\n", result.GeoLocation.Longitude);
  printf("GPS Altitude      : %f m\n", result.GeoLocation.Altitude);

3. 小工具实现源码

图片处理采用多线程方式处理,不卡主UI界面,处理结果会通过信号槽方式传递给UI界面进行显示。

下面贴出实现的核心代码:

#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("已选:%1\n").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 %d\n", code);
       return -3;
     }

     // Dump EXIF information
//     qDebug("Camera make          : %s\n", result.Make.c_str());
//     qDebug("Camera model         : %s\n", result.Model.c_str());
//     qDebug("Software             : %s\n", result.Software.c_str());
//     qDebug("Bits per sample      : %d\n", result.BitsPerSample);
//     qDebug("Image width          : %d\n", result.ImageWidth);
//     qDebug("Image height         : %d\n", result.ImageHeight);
//     qDebug("Image description    : %s\n", result.ImageDescription.c_str());
//     qDebug("Image orientation    : %d\n", result.Orientation);
//     qDebug("Image copyright      : %s\n", result.Copyright.c_str());
//     qDebug("Image date/time      : %s\n", result.DateTime.c_str());
//     qDebug("Original date/time   : %s\n", result.DateTimeOriginal.c_str());
//     qDebug("Digitize date/time   : %s\n", result.DateTimeDigitized.c_str());
//     qDebug("Subsecond time       : %s\n", result.SubSecTimeOriginal.c_str());
//     qDebug("Exposure time        : 1/%d s\n",
//            (unsigned)(1.0 / result.ExposureTime));
//     qDebug("F-stop               : f/%.1f\n", result.FNumber);
//     qDebug("Exposure program     : %d\n", result.ExposureProgram);
//     qDebug("ISO speed            : %d\n", result.ISOSpeedRatings);
//     qDebug("Subject distance     : %f m\n", result.SubjectDistance);
//     qDebug("Exposure bias        : %f EV\n", result.ExposureBiasValue);
//     qDebug("Flash used?          : %d\n", result.Flash);
//     qDebug("Flash returned light : %d\n", result.FlashReturnedLight);
//     qDebug("Flash mode           : %d\n", result.FlashMode);
//     qDebug("Metering mode        : %d\n", result.MeteringMode);
//     qDebug("Lens focal length    : %f mm\n", result.FocalLength);
//     qDebug("35mm focal length    : %u mm\n", 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 m\n", result.GeoLocation.Altitude);
//     qDebug("GPS Precision (DOP)  : %f\n", result.GeoLocation.DOP);
//     qDebug("Lens min focal length: %f mm\n", result.LensInfo.FocalLengthMin);
//     qDebug("Lens max focal length: %f mm\n", result.LensInfo.FocalLengthMax);
//     qDebug("Lens f-stop min      : f/%.1f\n", result.LensInfo.FStopMin);
//     qDebug("Lens f-stop max      : f/%.1f\n", result.LensInfo.FStopMax);
//     qDebug("Lens make            : %s\n", result.LensInfo.Make.c_str());
//     qDebug("Lens model           : %s\n", result.LensInfo.Model.c_str());
//     qDebug("Focal plane XRes     : %f\n", result.LensInfo.FocalPlaneXResolution);
//     qDebug("Focal plane YRes     : %f\n", 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"));
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。