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

Posted DS小龙哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计一款照片一键加水印的小工具相关的知识,希望对你有一定的参考价值。

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 来至官网的示例代码

  #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"));

以上是关于设计一款照片一键加水印的小工具的主要内容,如果未能解决你的问题,请参考以下文章

在自己的照片上加水印 logo用啥软件?

nodejs编程实战之图片水印生成

nodejs编程实战之图片水印生成

nodejs编程实战之图片水印生成

nodejs编程实战之图片水印生成

在自己的照片上加水印 logo用啥软件?