diff --git a/.gitignore b/.gitignore index 258bec7..7237bee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ make .swp .git +.user diff --git a/INSTALL.md b/INSTALL.md index 0265046..0c59beb 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -6,6 +6,7 @@ This project depends on the following dynamically linked libraries: * qt5 * libexif +* libheif ### OSX diff --git a/Makefile b/Makefile index d7c1d8e..7d2f324 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ all: build .PHONY: install-deps-deb install-deps-deb: - apt install qt5-qmake libexif12 qt5-default libexif-dev qt5-image-formats-plugins + apt install qt5-qmake libexif12 qt5-default libexif-dev qt5-image-formats-plugins libheif1 libheif-dev cmake check-deps-deb: dpkg -l | grep qt5-qmake @@ -15,6 +15,9 @@ check-deps-deb: dpkg -l | grep libexif-dev dpkg -l | grep qt5-default dpkg -l | grep qt5-image-formats-plugins + dpkg -l | grep libheif1 + dpkg -l | grep libheif-dev + dpkg -l | grep cmake .PHONY: clean clean: diff --git a/README.md b/README.md index f2880ea..430306d 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,11 @@ slide [-t rotation_seconds] [-o background_opacity(0..255)] [-b blur_radius] -p * qt5-image-formats-plugins * libexif +## if *.heic support wanted +* libheif +* qt-heif-image-plugin (alas is not a part of Qt jet, have to be compiled, see instuctions below) +* cmake + Ubuntu/Raspbian: ``` @@ -75,6 +80,18 @@ Install binaries ``` sudo make install ``` +Build Qt plugin for *.heic (Iphone) files + +``` +git clone https://github.com/jakar/qt-heif-image-plugin.git +cd qt-heif-image-plugin +mkdir -p build +cd build +cmake .. +make +sudo make install +``` + ### macOS diff --git a/src/exifhelper.cpp b/src/exifhelper.cpp new file mode 100644 index 0000000..b0bf8a2 --- /dev/null +++ b/src/exifhelper.cpp @@ -0,0 +1,249 @@ +#include "exifhelper.h" +#include +#include +#include +#include +#include +#include +#include + +ExifHelper::ExifHelper(): + m_exifData(NULL), + m_isHeif(false) +{ + +} +ExifHelper::~ExifHelper(){ + if(m_exifData) + exif_data_free(m_exifData); +} + +void ExifHelper::setImage(const std::string path){ + m_path=path.c_str(); + if(m_exifData){ + exif_data_free(m_exifData); + } + if(isHeif(path)){ + m_exifData=getExifDataFromHeif(path); + m_isHeif=true; + }else{ + m_exifData=exif_data_new_from_file(path.c_str()); + m_isHeif=false; + } + + if(m_exifData){ + m_byteOrder=exif_data_get_byte_order(m_exifData); + } +} + +ExifData *ExifHelper::getExifData(){ + return(m_exifData); +} + +int ExifHelper::getImageRotation() +{ + if(m_isHeif) return 0; //if not, heic image will be rotated 2 times, courtesy of libheif? + int orientation = 0; + if (m_exifData) + { + ExifEntry *exifEntry = exif_data_get_entry(m_exifData, EXIF_TAG_ORIENTATION); + + if (exifEntry) + { + orientation = exif_get_short(exifEntry->data, m_byteOrder); + // std::cout << m_path.toStdString() << " orientation " << orientation<< "\n"; + } + } + + int degrees = 0; + switch(orientation) { + case 8: + degrees = 270; + break; + case 3: + degrees = 180; + break; + case 6: + degrees = 90; + break; + } + + return degrees; +} + + + +const QString ExifHelper::getImageInfo(){ + QString txt("Path: "); + txt=txt+m_path+"\n"; + if (m_exifData) + { + + txt=txt+"XxY: "+getExifXDimension()+"x"+getExifYDimension()+"\n"; + txt=txt+"XxY resolution: "+getExifXResolution()+"x"+getExifYResolution()+"\n"; + txt=txt+"date: "+getExifDate()+"\n"; + txt=txt+"camera: "+getExifCamera()+"\n"; + txt=txt+getExifGPS(); + + }else{ + txt=txt+"No exif data found :("; + } + + return(txt); +} + +const QString ExifHelper::getExifXResolution(){ + QString txt(""); + if(m_exifData){ + ExifEntry *exifEntry = exif_data_get_entry(m_exifData, EXIF_TAG_X_RESOLUTION); + if (exifEntry) + { + txt=txt+QString::number(exif_get_long(exifEntry->data, m_byteOrder)); + } + } + return txt; +} + + +const QString ExifHelper::getExifYResolution(){ + QString txt(""); + if(m_exifData){ + ExifEntry *exifEntry = exif_data_get_entry(m_exifData, EXIF_TAG_Y_RESOLUTION); + if (exifEntry) + { + txt=txt+QString::number(exif_get_long(exifEntry->data, m_byteOrder)); + } + } + return txt; +} + +const QString ExifHelper::getExifXDimension(){ + QString txt(""); + if(m_exifData){ + ExifEntry *exifEntry = exif_data_get_entry(m_exifData, EXIF_TAG_PIXEL_X_DIMENSION); + if (exifEntry) + { + txt=txt+QString::number(exif_get_long(exifEntry->data, m_byteOrder)); + } + } + return txt; +} + +const QString ExifHelper::getExifYDimension(){ + QString txt(""); + if(m_exifData){ + ExifEntry *exifEntry = exif_data_get_entry(m_exifData, EXIF_TAG_PIXEL_Y_DIMENSION); + if (exifEntry) + { + txt=txt+QString::number(exif_get_long(exifEntry->data, m_byteOrder)); + } + } + return txt; +} + +const QString ExifHelper::getExifDate(){ + QString dateTime; + if (m_exifData) + { + ExifEntry *exifEntry = exif_data_get_entry(m_exifData, EXIF_TAG_DATE_TIME_ORIGINAL); + if (exifEntry) + { + dateTime = getExifAscii(exifEntry); + } + QString exifDateFormat = "yyyy:MM:dd hh:mm:ss"; + QDateTime exifDateTime = QDateTime::fromString(dateTime, exifDateFormat); + return QLocale::system().toString(exifDateTime); + } + return dateTime; +} + +const QString ExifHelper::getExifGPS(){ + QString txt(""); + if(m_exifData){ + ExifEntry *exifEntry = exif_content_get_entry(m_exifData->ifd[EXIF_IFD_GPS], (ExifTag)EXIF_TAG_GPS_LATITUDE_REF); + if(exifEntry) + { + //txt=txt+" "+((getExifAscii(exifEntry)[0]=='N')?"+":"-"); + txt=txt+getExifAscii(exifEntry)+" "; + } + exifEntry = exif_content_get_entry(m_exifData->ifd[EXIF_IFD_GPS], (ExifTag)EXIF_TAG_GPS_LATITUDE); + if(exifEntry){ + txt=txt+QString::number(getExifGeoCooValue(exifEntry->data,m_byteOrder),'f',4)+"°; "; + } + exifEntry = exif_content_get_entry(m_exifData->ifd[EXIF_IFD_GPS], (ExifTag)EXIF_TAG_GPS_LONGITUDE_REF); + if(exifEntry){ + //txt=txt+" "+((getExifAscii(exifEntry)[0]=='E')?"+":"-"); + txt=txt+" "+getExifAscii(exifEntry)+" "; + } + exifEntry = exif_content_get_entry(m_exifData->ifd[EXIF_IFD_GPS], (ExifTag)EXIF_TAG_GPS_LONGITUDE); + if(exifEntry){ + txt=txt+QString::number(getExifGeoCooValue(exifEntry->data,m_byteOrder),'f',4)+"°; "; + } + exifEntry = exif_content_get_entry(m_exifData->ifd[EXIF_IFD_GPS], (ExifTag)EXIF_TAG_GPS_ALTITUDE); + if(exifEntry){ + txt=txt+QString::number(getExifRationalValue(exifEntry->data,m_byteOrder),'f',2); + } + exifEntry = exif_content_get_entry(m_exifData->ifd[EXIF_IFD_GPS], (ExifTag)EXIF_TAG_GPS_ALTITUDE_REF); + if(exifEntry){ + if(*(exifEntry->data)==0) + { + txt=txt+"m ASL"; //Meters Above Sea Level + }else{ + txt=txt+"m BSL"; //Meters Below Sea Level? + } + } + } + return txt; +} + +const QString ExifHelper::getExifCamera(){ + QString txt(""); + if(m_exifData){ + ExifEntry *exifEntry = exif_data_get_entry(m_exifData, EXIF_TAG_MAKE); + if (exifEntry) + { + txt=txt+getExifAscii(exifEntry).toUpper()+", "; + } + exifEntry = exif_data_get_entry(m_exifData, EXIF_TAG_MODEL); + if (exifEntry) + { + txt=txt+getExifAscii(exifEntry); + } + } + return txt; +} + +const QString ExifHelper::getExifAscii(ExifEntry *e){ + char data[e->size]; + strncpy(data,(char*)e->data,e->size); + return QVariant(data).toString(); +} +double ExifHelper::getExifRationalValue(const unsigned char *e, ExifByteOrder o){ + ExifRational r(exif_get_rational(e,o)); + return (r.numerator/(double)r.denominator); +} +double ExifHelper::getExifGeoCooValue(const unsigned char *e, ExifByteOrder o){ + return( getExifRationalValue(e,o)+ + getExifRationalValue(e+sizeof(ExifRational),o)/60+ + getExifRationalValue(e+2*sizeof(ExifRational),o)/3600 + ); +} + +bool ExifHelper::isHeif(const std::string path){ + //TODO use libheif method heif_check_filetype(), as soon as raspi has libheif version =>1.4 + QFileInfo fileInfo(path.c_str()); + if(fileInfo.suffix().toLower()=="heic") return true; + return false; +} + +ExifData* ExifHelper::getExifDataFromHeif(const std::string path){ + heif::Context ctx; + ctx.read_from_file(path); + heif::ImageHandle handle(ctx.get_primary_image_handle()); + std::vector ids(handle.get_list_of_metadata_block_IDs("Exif")); + if(ids.empty()) return NULL; + std::vector data(handle.get_metadata(ids[0])); + const unsigned char *p(&data[4]); //first 4 bytes apparently offset to tiff data + ExifData *ptr(exif_data_new_from_data(p,data.size()-4)); + return ptr; +} diff --git a/src/exifhelper.h b/src/exifhelper.h new file mode 100644 index 0000000..83da96a --- /dev/null +++ b/src/exifhelper.h @@ -0,0 +1,40 @@ +#ifndef EXIFHELPER_H +#define EXIFHELPER_H + +#include +#include + + +class ExifHelper +{ +public: + ExifHelper(); + virtual ~ExifHelper(); + + void setImage(const std::string path); + ExifData *getExifData(); + int getImageRotation(); + const QString getImageInfo(); + const QString getExifXResolution(); + const QString getExifYResolution(); + const QString getExifXDimension(); + const QString getExifYDimension(); + const QString getExifDate(); + const QString getExifGPS(); + const QString getExifCamera(); + +private: + bool isHeif(const std::string path); + ExifData * getExifDataFromHeif(const std::string path); + const QString getExifAscii(ExifEntry *e); + double getExifRationalValue(const unsigned char *e, ExifByteOrder o); + double getExifGeoCooValue(const unsigned char *e, ExifByteOrder o); + + ExifData *m_exifData; + bool m_isHeif; + ExifByteOrder m_byteOrder; + QString m_path; + +}; + +#endif // EXIFHELPER_H diff --git a/src/imageselector.cpp b/src/imageselector.cpp index 3d082d6..43a624f 100644 --- a/src/imageselector.cpp +++ b/src/imageselector.cpp @@ -39,13 +39,13 @@ std::string RandomImageSelector::getNextImage() { std::cerr << "Error: " << err << std::endl; } - std::cout << "updating image: " << filename << std::endl; + //std::cout << "updating image: " << filename << std::endl; return filename; } unsigned int RandomImageSelector::selectRandom(const QStringList& images) const { - std::cout << "images: " << images.size() << std::endl; + //std::cout << "images: " << images.size() << std::endl; if (images.size() == 0) { throw std::string("No jpg images found in given folder"); @@ -87,7 +87,7 @@ std::string ShuffleImageSelector::getNextImage() current_image_shuffle = images.size(); return getNextImage(); } - std::cout << "updating image: " << filename << std::endl; + //std::cout << "updating image: " << filename << std::endl; current_image_shuffle = current_image_shuffle + 1; return filename; } @@ -122,10 +122,10 @@ std::string SortedImageSelector::getNextImage() images = pathTraverser->getImages(); std::sort(images.begin(), images.end()); std::cout << "read " << images.size() << " images." << std::endl; - for (int i = 0;i #include #include #include #include -#include #include #include #include @@ -18,7 +18,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), - ui(new Ui::MainWindow) + ui(new Ui::MainWindow), + m_exifHelper(new ExifHelper()) { ui->setupUi(this); setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); @@ -56,44 +57,10 @@ void MainWindow::resizeEvent(QResizeEvent* event) void MainWindow::setImage(std::string path) { currentImage = path; + m_exifHelper->setImage(path); updateImage(false); } -int MainWindow::getImageRotation() -{ - if (currentImage == "") - return 0; - - int orientation = 0; - ExifData *exifData = exif_data_new_from_file(currentImage.c_str()); - if (exifData) - { - ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); - ExifEntry *exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION); - - if (exifEntry) - { - orientation = exif_get_short(exifEntry->data, byteOrder); - } - exif_data_free(exifData); - } - - int degrees = 0; - switch(orientation) { - case 8: - degrees = 270; - break; - case 3: - degrees = 180; - break; - case 6: - degrees = 90; - break; - } - - return degrees; -} - void MainWindow::updateImage(bool immediately) { if (currentImage == "") @@ -162,6 +129,7 @@ void MainWindow::drawForeground(QPixmap& background, const QPixmap& foreground) void MainWindow::setOverlay(Overlay* o) { overlay = o; + overlay->setExifHelper(m_exifHelper); } QPixmap MainWindow::getBlurredBackground(const QPixmap& originalSize, const QPixmap& scaled) @@ -180,7 +148,7 @@ QPixmap MainWindow::getBlurredBackground(const QPixmap& originalSize, const QPix QPixmap MainWindow::getRotatedPixmap(const QPixmap& p) { QMatrix matrix; - matrix.rotate(getImageRotation()); + matrix.rotate(m_exifHelper->getImageRotation()); return p.transformed(matrix); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 362a0cc..791d48d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -10,6 +10,7 @@ class MainWindow; class QLabel; class QKeyEvent; class Overlay; +class ExifHelper; class MainWindow : public QMainWindow { @@ -38,7 +39,6 @@ class MainWindow : public QMainWindow void drawText(QPixmap& image, int margin, int fontsize, QString text, int alignment); void updateImage(bool immediately); - int getImageRotation(); QPixmap getBlurredBackground(const QPixmap& originalSize, const QPixmap& scaled); QPixmap getRotatedPixmap(const QPixmap& p); @@ -46,6 +46,8 @@ class MainWindow : public QMainWindow void drawBackground(const QPixmap& originalSize, const QPixmap& scaled); void drawForeground(QPixmap& background, const QPixmap& foreground); QPixmap blur(const QPixmap& input); + + ExifHelper *m_exifHelper; }; #endif // MAINWINDOW_H diff --git a/src/overlay.cpp b/src/overlay.cpp index 9bd9aae..36b2ef6 100644 --- a/src/overlay.cpp +++ b/src/overlay.cpp @@ -1,7 +1,7 @@ #include "overlay.h" +#include "exifhelper.h" #include #include -#include #include #include #include @@ -123,7 +123,9 @@ std::string Overlay::renderString(QString overlayTemplate, std::string filename) result.replace("", filename.c_str()); result.replace("", getFilename(filename)); result.replace("", getBasename(filename)); - result.replace("", getExifDate(filename)); + result.replace("", m_eh->getExifDate()); + result.replace("", m_eh->getExifGPS()); + result.replace("", m_eh->getExifCamera()); return result.toStdString(); } @@ -147,23 +149,6 @@ QString Overlay::getPath(std::string filename) { return fileInfo.path(); } -QString Overlay::getExifDate(std::string filename) { - - QString dateTime; - ExifData *exifData = exif_data_new_from_file(filename.c_str()); - if (exifData) - { - ExifEntry *exifEntry = exif_data_get_entry(exifData, EXIF_TAG_DATE_TIME_ORIGINAL); - - if (exifEntry) - { - char buf[2048]; - dateTime = exif_entry_get_value(exifEntry, buf, sizeof(buf)); - } - exif_data_free(exifData); - QString exifDateFormat = "yyyy:MM:dd hh:mm:ss"; - QDateTime exifDateTime = QDateTime::fromString(dateTime, exifDateFormat); - return QLocale::system().toString(exifDateTime); - } - return dateTime; +void Overlay::setExifHelper(ExifHelper *eh){ + m_eh=eh; } diff --git a/src/overlay.h b/src/overlay.h index 3707c47..025dcc2 100644 --- a/src/overlay.h +++ b/src/overlay.h @@ -8,6 +8,8 @@ #include class MainWindow; +class ExifHelper; + class Overlay { public: @@ -27,6 +29,8 @@ class Overlay int getMarginBottomRight(); int getFontsizeBottomRight(); + void setExifHelper(ExifHelper *eh); + private: const std::string overlayInput; @@ -53,12 +57,13 @@ class Overlay int getFontsize(QStringList components); QString getTemplate(QStringList components); - QString getExifDate(std::string filename); QString getDir(std::string filename); QString getPath(std::string filename); QString getFilename(std::string filename); QString getBasename(std::string filename); void parseInput(); std::string renderString(QString overlayTemplate, std::string filename); + + ExifHelper *m_eh; }; #endif diff --git a/src/pathtraverser.h b/src/pathtraverser.h index 743278c..da4ca52 100644 --- a/src/pathtraverser.h +++ b/src/pathtraverser.h @@ -5,7 +5,7 @@ #include #include -static const QStringList supportedFormats={"jpg","jpeg","png","tif","tiff"}; +static const QStringList supportedFormats={"jpg","jpeg","png","tif","tiff","heic"}; class MainWindow; class PathTraverser diff --git a/src/slide.pro b/src/slide.pro index a3804b9..4053252 100644 --- a/src/slide.pro +++ b/src/slide.pro @@ -28,6 +28,7 @@ mac: QMAKE_LFLAGS += -L$$system(brew --prefix libexif)/lib SOURCES += \ + exifhelper.cpp \ main.cpp \ mainwindow.cpp \ imageswitcher.cpp \ @@ -36,6 +37,7 @@ SOURCES += \ imageselector.cpp HEADERS += \ + exifhelper.h \ mainwindow.h \ imageselector.h \ pathtraverser.h \ @@ -49,3 +51,5 @@ target.path = /usr/local/bin/ INSTALLS += target unix|win32: LIBS += -lexif + +unix|win32: LIBS += -lheif diff --git a/src/slide.pro.user b/src/slide.pro.user deleted file mode 100644 index f1a8380..0000000 --- a/src/slide.pro.user +++ /dev/null @@ -1,336 +0,0 @@ - - - - - - EnvironmentId - {d48a34de-ff8d-4757-9c3b-132a14098d6d} - - - ProjectExplorer.Project.ActiveTarget - 0 - - - ProjectExplorer.Project.EditorSettings - - true - false - true - - Cpp - - CppGlobal - - - - QmlJS - - QmlJSGlobal - - - 2 - UTF-8 - false - 4 - false - 80 - true - true - 1 - true - false - 0 - true - true - 0 - 8 - true - 1 - true - true - true - false - - - - ProjectExplorer.Project.PluginSettings - - - - ProjectExplorer.Project.Target.0 - - Desktop - Desktop - {608421ee-2bdd-4e28-85e9-148db782e738} - 0 - 0 - 0 - - /home/manuel/slide/build-slide-Desktop-Debug - - - true - qmake - - QtProjectManager.QMakeBuildStep - true - - false - false - false - - - true - Make - - Qt4ProjectManager.MakeStep - - -w - -r - - false - - - - 2 - Erstellen - - ProjectExplorer.BuildSteps.Build - - - - true - Make - - Qt4ProjectManager.MakeStep - - -w - -r - - true - clean - - - 1 - Bereinigen - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Debug - Debug - Qt4ProjectManager.Qt4BuildConfiguration - 2 - true - - - /home/manuel/slide/build-slide-Desktop-Release - - - true - qmake - - QtProjectManager.QMakeBuildStep - false - - false - false - true - - - true - Make - - Qt4ProjectManager.MakeStep - - -w - -r - - false - - - - 2 - Erstellen - - ProjectExplorer.BuildSteps.Build - - - - true - Make - - Qt4ProjectManager.MakeStep - - -w - -r - - true - clean - - - 1 - Bereinigen - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Release - Release - Qt4ProjectManager.Qt4BuildConfiguration - 0 - true - - - /home/manuel/slide/build-slide-Desktop-Profile - - - true - qmake - - QtProjectManager.QMakeBuildStep - true - - false - true - true - - - true - Make - - Qt4ProjectManager.MakeStep - - -w - -r - - false - - - - 2 - Erstellen - - ProjectExplorer.BuildSteps.Build - - - - true - Make - - Qt4ProjectManager.MakeStep - - -w - -r - - true - clean - - - 1 - Bereinigen - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Profile - Profile - Qt4ProjectManager.Qt4BuildConfiguration - 0 - true - - 3 - - - 0 - Deployment - - ProjectExplorer.BuildSteps.Deploy - - 1 - Deployment-Konfiguration - - ProjectExplorer.DefaultDeployConfiguration - - 1 - - - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - slide - - Qt4ProjectManager.Qt4RunConfiguration:/home/manuel/slide/src/slide.pro - true - - slide.pro - false - - /home/manuel/slide/build-slide-Desktop-Debug - 3768 - false - true - false - false - true - - 1 - - - - ProjectExplorer.Project.TargetCount - 1 - - - ProjectExplorer.Project.Updater.FileVersion - 18 - - - Version - 18 - -