384 lines
11 KiB
C++
384 lines
11 KiB
C++
#include "imageselector.h"
|
|
#include "pathtraverser.h"
|
|
#include "mainwindow.h"
|
|
#include "logger.h"
|
|
#include <QDirIterator>
|
|
#include <QTimer>
|
|
#include <QApplication>
|
|
#include <QDir>
|
|
#include <libexif/exif-data.h>
|
|
#include <iostream>
|
|
#include <stdlib.h> /* srand, rand */
|
|
#include <time.h> /* time */
|
|
#include <algorithm> // std::shuffle
|
|
#include <random> // std::default_random_engine
|
|
|
|
ImageSelector::ImageSelector(std::unique_ptr<PathTraverser>& pathTraverserIn):
|
|
pathTraverser(std::move(pathTraverserIn))
|
|
{
|
|
}
|
|
|
|
ImageSelector::ImageSelector() {}
|
|
|
|
ImageSelector::~ImageSelector(){}
|
|
|
|
int ReadExifTag(ExifData* exifData, ExifTag tag, bool shortRead = false)
|
|
{
|
|
int value = -1;
|
|
ExifByteOrder byteOrder = exif_data_get_byte_order(exifData);
|
|
ExifEntry *exifEntry = exif_data_get_entry(exifData, tag);
|
|
|
|
if (exifEntry)
|
|
{
|
|
if (shortRead)
|
|
{
|
|
value = exif_get_short(exifEntry->data, byteOrder);
|
|
}
|
|
else
|
|
{
|
|
value = exif_get_long(exifEntry->data, byteOrder);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
ImageDetails ImageSelector::populateImageDetails(const std::string&fileName, const ImageDisplayOptions &baseOptions)
|
|
{
|
|
ImageDetails imageDetails;
|
|
int orientation = -1;
|
|
int imageWidth = -1;
|
|
int imageHeight = -1;
|
|
ExifData *exifData = exif_data_new_from_file(fileName.c_str());
|
|
if (exifData)
|
|
{
|
|
orientation = ReadExifTag(exifData, EXIF_TAG_ORIENTATION, true);
|
|
|
|
/*
|
|
// It looks like you can't trust Exif dimensions, so just forcefully load the file below
|
|
// try to get the image dimensions from exifData so we don't need to fully load the file
|
|
imageWidth = ReadExifTag(exifData, EXIF_TAG_IMAGE_WIDTH);
|
|
if ( imageWidth == -1)
|
|
imageWidth = ReadExifTag(exifData, EXIF_TAG_PIXEL_X_DIMENSION);
|
|
|
|
imageHeight = ReadExifTag(exifData, EXIF_TAG_RELATED_IMAGE_WIDTH); // means height, height is related to width
|
|
if ( imageHeight == -1)
|
|
imageHeight = ReadExifTag(exifData, EXIF_TAG_PIXEL_Y_DIMENSION);*/
|
|
|
|
exif_data_free(exifData);
|
|
}
|
|
|
|
int degrees = 0;
|
|
switch(orientation) {
|
|
case 8:
|
|
degrees = 270;
|
|
break;
|
|
case 3:
|
|
degrees = 180;
|
|
break;
|
|
case 6:
|
|
degrees = 90;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (imageWidth <=0 || imageHeight <=0)
|
|
{
|
|
// fallback to QPixmap to determine image size
|
|
QPixmap p( fileName.c_str() );
|
|
imageWidth = p.width();
|
|
imageHeight = p.height();
|
|
}
|
|
|
|
// if the image is rotated then swap height/width here to show displayed sizes
|
|
if( degrees == 90 || degrees == 270 )
|
|
{
|
|
std::swap(imageWidth,imageHeight);
|
|
}
|
|
|
|
// setup the imageDetails structure
|
|
imageDetails.filename = fileName;
|
|
|
|
imageDetails.width = imageWidth;
|
|
imageDetails.height = imageHeight;
|
|
imageDetails.rotation = degrees;
|
|
if (imageWidth > imageHeight) {
|
|
imageDetails.aspect = ImageAspect_Landscape;
|
|
} else if (imageHeight > imageWidth) {
|
|
imageDetails.aspect = ImageAspect_Portrait;
|
|
} else {
|
|
imageDetails.aspect = ImageAspect_Any;
|
|
}
|
|
|
|
imageDetails.options = pathTraverser->UpdateOptionsForImage(imageDetails.filename, baseOptions);
|
|
|
|
return imageDetails;
|
|
}
|
|
|
|
bool ImageSelector::imageInsideTimeWindow(const QVector<DisplayTimeWindow> &timeWindows)
|
|
{
|
|
if(timeWindows.count() == 0)
|
|
{
|
|
return true; // no specified time windows means always display
|
|
}
|
|
const QTime currentTime = QTime::currentTime();
|
|
for(auto &window : timeWindows)
|
|
{
|
|
if(currentTime > window.startDisplay && currentTime < window.endDisplay)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if(ShouldLog() && timeWindows.count() > 0)
|
|
{
|
|
Log( "image display time outside window: ");
|
|
for(auto &timeWindow : timeWindows)
|
|
{
|
|
Log("time: ", timeWindow.startDisplay.toString().toStdString(), "-", timeWindow.endDisplay.toString().toStdString());
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ImageSelector::imageMatchesFilter(const ImageDetails& imageDetails)
|
|
{
|
|
if(!QFileInfo::exists(QString(imageDetails.filename.c_str())))
|
|
{
|
|
Log("file not found: ", imageDetails.filename);
|
|
return false;
|
|
}
|
|
|
|
if(!imageValidForAspect(imageDetails))
|
|
{
|
|
Log("image aspect ratio doesn't match filter '", imageDetails.options.onlyAspect, "' : ", imageDetails.filename);
|
|
return false;
|
|
}
|
|
|
|
if(!imageInsideTimeWindow(imageDetails.options.timeWindows))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ImageSelector::imageValidForAspect(const ImageDetails& imageDetails)
|
|
{
|
|
if (imageDetails.options.onlyAspect == ImageAspect_Any ||
|
|
imageDetails.aspect == imageDetails.options.onlyAspect)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
RandomImageSelector::RandomImageSelector(std::unique_ptr<PathTraverser>& pathTraverser):
|
|
ImageSelector(pathTraverser)
|
|
{
|
|
srand (time(NULL));
|
|
}
|
|
|
|
RandomImageSelector::~RandomImageSelector(){}
|
|
|
|
const ImageDetails RandomImageSelector::getNextImage(const ImageDisplayOptions &baseOptions)
|
|
{
|
|
ImageDetails imageDetails;
|
|
try
|
|
{
|
|
QStringList images = pathTraverser->getImages();
|
|
unsigned int selectedImage = selectRandom(images);
|
|
imageDetails = populateImageDetails(pathTraverser->getImagePath(images.at(selectedImage).toStdString()), baseOptions);
|
|
while(!imageMatchesFilter(imageDetails))
|
|
{
|
|
unsigned int selectedImage = selectRandom(images);
|
|
imageDetails = populateImageDetails(pathTraverser->getImagePath(images.at(selectedImage).toStdString()), baseOptions);
|
|
}
|
|
}
|
|
catch(const std::string& err)
|
|
{
|
|
std::cerr << "Error: " << err << std::endl;
|
|
}
|
|
std::cout << "updating image: " << imageDetails.filename << std::endl;
|
|
return imageDetails;
|
|
}
|
|
|
|
unsigned int RandomImageSelector::selectRandom(const QStringList& images) const
|
|
{
|
|
Log("images: ", images.size());
|
|
if (images.size() == 0)
|
|
{
|
|
throw std::string("No jpg images found in given folder");
|
|
}
|
|
return rand() % images.size();
|
|
}
|
|
|
|
ShuffleImageSelector::ShuffleImageSelector(std::unique_ptr<PathTraverser>& pathTraverser):
|
|
ImageSelector(pathTraverser),
|
|
current_image_shuffle(-1),
|
|
images()
|
|
{
|
|
srand (time(NULL));
|
|
}
|
|
|
|
ShuffleImageSelector::~ShuffleImageSelector()
|
|
{
|
|
}
|
|
|
|
const ImageDetails ShuffleImageSelector::getNextImage(const ImageDisplayOptions &baseOptions)
|
|
{
|
|
reloadImagesIfNoneLeft();
|
|
ImageDetails imageDetails;
|
|
if (images.size() == 0)
|
|
{
|
|
return imageDetails;
|
|
}
|
|
bool bReloadedImages = false;
|
|
imageDetails = populateImageDetails(pathTraverser->getImagePath(images.at(current_image_shuffle).toStdString()), baseOptions);
|
|
current_image_shuffle = current_image_shuffle + 1; // ignore and move to next image
|
|
while(!imageMatchesFilter(imageDetails)) {
|
|
if(current_image_shuffle >= images.size()) {
|
|
// don't keep looping
|
|
if(bReloadedImages == true)
|
|
return ImageDetails();
|
|
bReloadedImages = true;
|
|
}
|
|
reloadImagesIfNoneLeft();
|
|
imageDetails = populateImageDetails(pathTraverser->getImagePath(images.at(current_image_shuffle).toStdString()),baseOptions);
|
|
current_image_shuffle = current_image_shuffle + 1; // ignore and move to next image
|
|
}
|
|
std::cout << "updating image: " << imageDetails.filename << std::endl;
|
|
return imageDetails;
|
|
}
|
|
|
|
void ShuffleImageSelector::reloadImagesIfNoneLeft()
|
|
{
|
|
if (images.size() == 0 || current_image_shuffle >= images.size())
|
|
{
|
|
current_image_shuffle = 0;
|
|
images = pathTraverser->getImages();
|
|
std::cout << "Shuffling " << images.size() << " images." << std::endl;
|
|
std::random_device rd;
|
|
std::mt19937 randomizer(rd());
|
|
std::shuffle(images.begin(), images.end(), randomizer);
|
|
}
|
|
}
|
|
|
|
SortedImageSelector::SortedImageSelector(std::unique_ptr<PathTraverser>& pathTraverser):
|
|
ImageSelector(pathTraverser),
|
|
images()
|
|
{
|
|
srand (time(NULL));
|
|
}
|
|
|
|
SortedImageSelector::~SortedImageSelector()
|
|
{
|
|
}
|
|
|
|
bool operator<(const QString& lhs, const QString& rhs) noexcept{
|
|
if (lhs.count(QLatin1Char('/')) < rhs.count(QLatin1Char('/'))) {
|
|
return true;
|
|
}
|
|
if (lhs.count(QLatin1Char('/')) > rhs.count(QLatin1Char('/'))) {
|
|
return false;
|
|
}
|
|
|
|
return lhs.toStdString() < rhs.toStdString();
|
|
|
|
}
|
|
|
|
const ImageDetails SortedImageSelector::getNextImage(const ImageDisplayOptions &baseOptions)
|
|
{
|
|
reloadImagesIfEmpty();
|
|
ImageDetails imageDetails;
|
|
if (images.size() == 0)
|
|
{
|
|
return imageDetails;
|
|
}
|
|
bool bReloadedImages = false;
|
|
imageDetails = populateImageDetails(pathTraverser->getImagePath(images.takeFirst().toStdString()), baseOptions);
|
|
while(!imageMatchesFilter(imageDetails)) {
|
|
if (images.size() == 0) {
|
|
// don't keep looping
|
|
if(bReloadedImages == true)
|
|
return ImageDetails();
|
|
bReloadedImages = true;
|
|
}
|
|
|
|
reloadImagesIfEmpty();
|
|
imageDetails = populateImageDetails(pathTraverser->getImagePath(images.takeFirst().toStdString()), baseOptions);
|
|
}
|
|
|
|
std::cout << "updating image: " << imageDetails.filename << std::endl;
|
|
imageDetails.options = pathTraverser->UpdateOptionsForImage(imageDetails.filename, imageDetails.options);
|
|
return imageDetails;
|
|
}
|
|
|
|
void SortedImageSelector::reloadImagesIfEmpty()
|
|
{
|
|
if (images.size() == 0)
|
|
{
|
|
images = pathTraverser->getImages();
|
|
std::sort(images.begin(), images.end());
|
|
if(ShouldLog())
|
|
{
|
|
Log( "read ", images.size(), " images.");
|
|
for (int i = 0;i <images.size();i++){
|
|
Log(images[i].toStdString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
ListImageSelector::ListImageSelector()
|
|
{
|
|
currentSelector = imageSelectors.begin();
|
|
}
|
|
|
|
ListImageSelector::~ListImageSelector()
|
|
{
|
|
}
|
|
|
|
void ListImageSelector::AddImageSelector(std::unique_ptr<ImageSelector>& selector, const bool exclusiveIn, const ImageDisplayOptions& baseDisplayOptionsIn)
|
|
{
|
|
SelectoryEntry entry;
|
|
entry.selector = std::move(selector);
|
|
entry.exclusive = exclusiveIn;
|
|
entry.baseDisplayOptions = baseDisplayOptionsIn;
|
|
imageSelectors.push_back(std::move(entry));
|
|
currentSelector = imageSelectors.begin();
|
|
}
|
|
|
|
|
|
const ImageDetails ListImageSelector::getNextImage(const ImageDisplayOptions& baseOptions)
|
|
{
|
|
// check for exclusive time windows
|
|
for(auto& selector: imageSelectors)
|
|
{
|
|
if (imageInsideTimeWindow(selector.baseDisplayOptions.timeWindows) && selector.exclusive)
|
|
{
|
|
ImageDisplayOptions options = baseOptions;
|
|
if (selector.baseDisplayOptions.fitAspectAxisToWindow)
|
|
options.fitAspectAxisToWindow = true;
|
|
return selector.selector->getNextImage(options);
|
|
}
|
|
}
|
|
|
|
// fall back to the next in the list
|
|
do
|
|
{
|
|
++currentSelector;
|
|
if(currentSelector == imageSelectors.end())
|
|
{
|
|
currentSelector = imageSelectors.begin();
|
|
}
|
|
if (imageInsideTimeWindow(currentSelector->baseDisplayOptions.timeWindows))
|
|
{
|
|
ImageDisplayOptions options = baseOptions;
|
|
if (currentSelector->baseDisplayOptions.fitAspectAxisToWindow)
|
|
options.fitAspectAxisToWindow = true;
|
|
return currentSelector->selector->getNextImage(options);
|
|
}
|
|
}
|
|
while(true);
|
|
} |