From 808aeef74eff4acc6caee0732c44dd661b476a04 Mon Sep 17 00:00:00 2001 From: CarVac Date: Sat, 27 Jan 2018 14:08:21 -0500 Subject: [PATCH 01/23] Add UI functionality for double-requesting images It needs a way to notify the pipeline what type of image it wants, as well as doubling the pipeline and double-tracking progress, though. --- filmulator-gui/core/imread.cpp | 2 +- filmulator-gui/qml/filmulator-gui/Edit.qml | 47 +++++++++++++++---- .../qml/filmulator-gui/Settings.qml | 19 +++++++- filmulator-gui/ui/settings.cpp | 17 +++++++ filmulator-gui/ui/settings.h | 5 ++ 5 files changed, 79 insertions(+), 11 deletions(-) diff --git a/filmulator-gui/core/imread.cpp b/filmulator-gui/core/imread.cpp index a0fab574..9b153136 100644 --- a/filmulator-gui/core/imread.cpp +++ b/filmulator-gui/core/imread.cpp @@ -43,7 +43,7 @@ bool imread(std::string input_image_filename, matrix &returnmatrix, #define COLOR image_processor.imgdata.color //Now we'll set demosaic and other processing settings. - PARAM.user_qual = 10;//9;//10 is AMaZE; -q[#] in dcraw + PARAM.user_qual = 9;//10 is AMaZE; -q[#] in dcraw PARAM.no_auto_bright = 1;//Don't autoadjust brightness (-W) PARAM.output_bps = 16;//16 bits per channel (-6) PARAM.gamm[0] = 1; diff --git a/filmulator-gui/qml/filmulator-gui/Edit.qml b/filmulator-gui/qml/filmulator-gui/Edit.qml index 9579044a..1d56af0a 100644 --- a/filmulator-gui/qml/filmulator-gui/Edit.qml +++ b/filmulator-gui/qml/filmulator-gui/Edit.qml @@ -8,7 +8,7 @@ SplitView { anchors.fill: parent orientation: Qt.Horizontal property real uiScale: 1 - property bool imageReady: false + property bool imageReady: false//must only be made ready when the full size image is ready property bool requestingCropping: false property bool cropping: false property bool cancelCropping: false @@ -132,10 +132,16 @@ SplitView { asynchronous: true onStatusChanged: { if (hiddenImage.status == Image.Ready) { - //console.log("hidden image ready") - topImage.state = "i" + console.log("hidden image ready; state: " + topImage.state) + //topImage.state = "i"//probably unnecessary? topImage.source = hiddenImage.source - root.imageReady = true + + if ("i" == topImage.state) { + root.imageReady = true//only after the full size image is done + } + else { + root.imageReady = false + } } else { root.imageReady = false @@ -149,7 +155,11 @@ SplitView { target: paramManager//root onUpdateImage: { console.log("update image") - if (topImage.state == "i") {//only if we're done with the thumbnail + if (topImage.state == "i" || topImage.state == "q") {//only if we're done with the thumbnail + if (settings.getQuickPreview()) { + topImage.state = "q"//make it do the quick processed image first + }//otherwise, leave it as whatever it was. + var num = (topImage.index + 1) % 1000000//1 in a million topImage.index = num; var s = num+""; @@ -204,12 +214,31 @@ SplitView { //now we notify the queue that the latest image is ready for display root.imageURL(topImage.source) } - if (topImage.state == "p") {//if the thumbnail or full size preview is loaded - //now we say that the state is loading the actual image, so as to not load a new preview. + if (topImage.state == "q") {//if the Quick processed image is loaded topImage.state = "i" - console.log("TopImage state: " + topImage.state) + console.log("TopImage state after quick: " + topImage.state) + + //begin loading the main, full-size image image + var num = (topImage.index + 1) % 1000000//1 in a million + topImage.index = num; + var s = num+""; + var size = 6 //6 digit number + while (s.length < size) {s = "0" + s} + topImage.indexString = s + //topImage.source = "image://filmy/" + topImage.indexString + hiddenImage.source = "image://filmy/" + topImage.indexString + } + if (topImage.state == "p") {//if the thumbnail Preview is loaded + //now we say that the state is loading the actual image, so as to not load a new preview. + if (settings.getQuickPreview()) { + topImage.state = "q" + } + else {//skip the quick preview + topImage.state = "i" + } + console.log("TopImage state after thumb: " + topImage.state) - //begin loading the main image + //begin loading the image var num = (topImage.index + 1) % 1000000//1 in a million topImage.index = num; var s = num+""; diff --git a/filmulator-gui/qml/filmulator-gui/Settings.qml b/filmulator-gui/qml/filmulator-gui/Settings.qml index ac521ffb..cfbf58e4 100644 --- a/filmulator-gui/qml/filmulator-gui/Settings.qml +++ b/filmulator-gui/qml/filmulator-gui/Settings.qml @@ -75,13 +75,27 @@ Rectangle { uiScale: root.uiScale } + ToolSwitch { + id: quickPreviewSwitch + text: qsTr("Render small preview first") + tooltipText: qsTr("Enabling this causes the editor to process a small-size image before processing at full resolution, for better responsiveness. It will make it take longer before you can export an image, though.") + isOn: settings.getQuickPreview() + defaultOn: settings.getQuickPreview() + changed: false + onIsOnChanged: quickPreviewSwitch.changed = true + Component.onCompleted: { + quickPreviewSwitch.tooltipWanted.connect(root.tooltipWanted) + } + uiScale: root.uiScale + } + ToolButton { id: saveSettings text: qsTr("Save Settings") tooltipText: qsTr("Apply settings and save for future use") width: settingsList.width height: 40 * uiScale - notDisabled: uiScaleSlider.changed || mipmapSwitch.changed || lowMemModeSwitch.changed + notDisabled: uiScaleSlider.changed || mipmapSwitch.changed || lowMemModeSwitch.changed || quickPreviewSwitch.changed onTriggered: { settings.uiScale = uiScaleSlider.value uiScaleSlider.defaultValue = uiScaleSlider.value @@ -92,6 +106,9 @@ Rectangle { settings.lowMemMode = lowMemModeSwitch.isOn lowMemModeSwitch.defaultOn = lowMemModeSwitch.isOn lowMemModeSwitch.changed = false; + settings.quickPreview = quickPreviewSwitch.isOn + quickPreviewSwitch.defaultOn = quickPreviewSwitch.isOn + quickPreviewSwitch.changed = false; } uiScale: root.uiScale } diff --git a/filmulator-gui/ui/settings.cpp b/filmulator-gui/ui/settings.cpp index 7332d00a..700d6673 100644 --- a/filmulator-gui/ui/settings.cpp +++ b/filmulator-gui/ui/settings.cpp @@ -228,3 +228,20 @@ bool Settings::getLowMemMode() emit lowMemModeChanged(); return lowMemMode; } + +void Settings::setQuickPreview(bool quickPreviewIn) +{ + QSettings settings(QSettings::UserScope, "Filmulator", "Filmulator"); + quickPreview = quickPreviewIn; + settings.setValue("edit/quickPreview", quickPreviewIn); + emit quickPreviewChanged(); +} + +bool Settings::getQuickPreview() +{ + QSettings settings(QSettings::UserScope, "Filmulator", "Filmulator"); + //Default: 1; it should default to being on. + quickPreview = settings.value("edit/quickPreview", 1).toBool(); + emit quickPreviewChanged(); + return quickPreview; +} diff --git a/filmulator-gui/ui/settings.h b/filmulator-gui/ui/settings.h index 2dea39d5..76235b9b 100644 --- a/filmulator-gui/ui/settings.h +++ b/filmulator-gui/ui/settings.h @@ -21,6 +21,7 @@ class Settings : public QObject Q_PROPERTY(bool appendHash READ getAppendHash WRITE setAppendHash NOTIFY appendHashChanged) Q_PROPERTY(bool mipmapView READ getMipmapView WRITE setMipmapView NOTIFY mipmapViewChanged) Q_PROPERTY(bool lowMemMode READ getLowMemMode WRITE setLowMemMode NOTIFY lowMemModeChanged) + Q_PROPERTY(bool quickPreview READ getQuickPreview WRITE setQuickPreview NOTIFY quickPreviewChanged) public: explicit Settings(QObject *parent = 0); @@ -37,6 +38,7 @@ class Settings : public QObject void setAppendHash(bool appendHashIn); void setMipmapView(bool mipmapViewIn); void setLowMemMode(bool lowMemModeIn); + void setQuickPreview(bool quickPreviewIn); Q_INVOKABLE QString getPhotoStorageDir(); Q_INVOKABLE QString getPhotoBackupDir(); @@ -51,6 +53,7 @@ class Settings : public QObject Q_INVOKABLE bool getAppendHash(); Q_INVOKABLE bool getMipmapView(); Q_INVOKABLE bool getLowMemMode(); + Q_INVOKABLE bool getQuickPreview(); protected: QString photoStorageDir; @@ -66,6 +69,7 @@ class Settings : public QObject bool appendHash; bool mipmapView; bool lowMemMode; + bool quickPreview; signals: void photoStorageDirChanged(); @@ -81,6 +85,7 @@ class Settings : public QObject void appendHashChanged(); void mipmapViewChanged(); void lowMemModeChanged(); + void quickPreviewChanged(); }; #endif // SETTINGS_H From 645b7b20b8a95af7bf7f75048f3a395500303dfa Mon Sep 17 00:00:00 2001 From: CarVac Date: Sat, 27 Jan 2018 15:18:14 -0500 Subject: [PATCH 02/23] Update ppa from qt 5.9 to 5.9.1 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e25bb8be..85ea5194 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ sudo: require dist: trusty before_install: - - sudo add-apt-repository ppa:beineri/opt-qt59-trusty -y + - sudo add-apt-repository ppa:beineri/opt-qt591-trusty -y - sudo apt-get update -qq install: From 0d6bb89d74bbe055f62e61955de5ee7b43d99f7a Mon Sep 17 00:00:00 2001 From: CarVac Date: Tue, 30 Jan 2018 12:13:26 -0500 Subject: [PATCH 03/23] clean up pipeline a bit --- filmulator-gui/ui/filmImageProvider.h | 7 +------ filmulator-gui/ui/parameterManager.h | 1 + 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/filmulator-gui/ui/filmImageProvider.h b/filmulator-gui/ui/filmImageProvider.h index 5b2870a1..b45f55cb 100644 --- a/filmulator-gui/ui/filmImageProvider.h +++ b/filmulator-gui/ui/filmImageProvider.h @@ -50,6 +50,7 @@ class FilmImageProvider : public QObject, public QQuickImageProvider, public Int protected: ImagePipeline pipeline = ImagePipeline(WithCache, WithHisto, HighQuality); + ImagePipeline quickPipe = ImagePipeline(WithCache, WithHisto, LowQuality);//for now it'll just be the 600 size ThumbWriteWorker *worker = new ThumbWriteWorker; QThread workerThread; @@ -63,14 +64,8 @@ class FilmImageProvider : public QObject, public QQuickImageProvider, public Int struct timeval request_start_time; - matrix input_image; - matrix pre_film_image; Exiv2::ExifData exifData; std::string outputFilename; - matrix filmulated_image; - matrix contrast_image; - matrix color_curve_image; - matrix vibrance_saturation_image; matrix last_image; Histogram finalHist; diff --git a/filmulator-gui/ui/parameterManager.h b/filmulator-gui/ui/parameterManager.h index c365ed0d..890d12c4 100644 --- a/filmulator-gui/ui/parameterManager.h +++ b/filmulator-gui/ui/parameterManager.h @@ -226,6 +226,7 @@ class ParameterManager : public QObject std::tuple claimFilmlikeCurvesParams(); Valid getValid(); + std::string getFullFilename(){return m_fullFilename;} protected: From fa640b278e5d34d06a69f07be032b5c03ed765b1 Mon Sep 17 00:00:00 2001 From: CarVac Date: Wed, 7 Feb 2018 19:30:33 -0500 Subject: [PATCH 04/23] Implement quick pipe with thumb size preview Eventually I want to have the user set the size. --- filmulator-gui/qml/filmulator-gui/Edit.qml | 14 +- filmulator-gui/ui/filmImageProvider.cpp | 41 +- filmulator-gui/ui/filmImageProvider.h | 3 + filmulator-gui/ui/parameterManager.cpp | 439 ++++++++++++++++++++- filmulator-gui/ui/parameterManager.h | 55 +++ 5 files changed, 533 insertions(+), 19 deletions(-) diff --git a/filmulator-gui/qml/filmulator-gui/Edit.qml b/filmulator-gui/qml/filmulator-gui/Edit.qml index 1d56af0a..3ebd144a 100644 --- a/filmulator-gui/qml/filmulator-gui/Edit.qml +++ b/filmulator-gui/qml/filmulator-gui/Edit.qml @@ -166,12 +166,10 @@ SplitView { var size = 6 //6 digit number while (s.length < size) {s = "0" + s} topImage.indexString = s - //topImage.source = "image://filmy/" + topImage.indexString - hiddenImage.source = "image://filmy/" + topImage.indexString + hiddenImage.source = "image://filmy/q" + topImage.indexString } if (topImage.state == "p") {//if we're planning on doing the thumbnail var thumbPath = organizeModel.thumbDir() + '/' + paramManager.imageIndex.slice(0,4) + '/' + paramManager.imageIndex + '.jpg' - console.log("thumb path: " + thumbPath) topImage.source = thumbPath } //if a new image is selected, OR the same image re-selected @@ -214,7 +212,7 @@ SplitView { //now we notify the queue that the latest image is ready for display root.imageURL(topImage.source) } - if (topImage.state == "q") {//if the Quick processed image is loaded + else if (topImage.state == "q") {//if the Quick processed image is loaded topImage.state = "i" console.log("TopImage state after quick: " + topImage.state) @@ -225,10 +223,9 @@ SplitView { var size = 6 //6 digit number while (s.length < size) {s = "0" + s} topImage.indexString = s - //topImage.source = "image://filmy/" + topImage.indexString - hiddenImage.source = "image://filmy/" + topImage.indexString + hiddenImage.source = "image://filmy/i" + topImage.indexString } - if (topImage.state == "p") {//if the thumbnail Preview is loaded + else if (topImage.state == "p") {//if the thumbnail Preview is loaded //now we say that the state is loading the actual image, so as to not load a new preview. if (settings.getQuickPreview()) { topImage.state = "q" @@ -245,8 +242,7 @@ SplitView { var size = 6 //6 digit number while (s.length < size) {s = "0" + s} topImage.indexString = s - //topImage.source = "image://filmy/" + topImage.indexString - hiddenImage.source = "image://filmy/" + topImage.indexString + hiddenImage.source = "image://filmy/q" + topImage.indexString } if (root.requestingCropping) { root.cropping = true diff --git a/filmulator-gui/ui/filmImageProvider.cpp b/filmulator-gui/ui/filmImageProvider.cpp index f82be646..34a9623c 100644 --- a/filmulator-gui/ui/filmImageProvider.cpp +++ b/filmulator-gui/ui/filmImageProvider.cpp @@ -18,6 +18,8 @@ FilmImageProvider::FilmImageProvider(ParameterManager * manager) : QQuickImageProvider::ForceAsynchronousImageLoading) { paramManager = manager; + cloneParam = new ParameterManager; + connect(paramManager, SIGNAL(updateClone(ParameterManager*)), cloneParam, SLOT(cloneParams(ParameterManager*))); zeroHistogram(finalHist); zeroHistogram(postFilmHist); zeroHistogram(preFilmHist); @@ -37,31 +39,60 @@ FilmImageProvider::FilmImageProvider(ParameterManager * manager) : { pipeline.setCache(WithCache); } + + //Check if we want to use dual pipelines + if (settingsObject.getQuickPreview()) + { + useQuickPipe = true; + } + else + { + useQuickPipe = false; + } } FilmImageProvider::~FilmImageProvider() { } -QImage FilmImageProvider::requestImage(const QString& /*id*/, +QImage FilmImageProvider::requestImage(const QString& id, QSize *size, const QSize& /*requestedSize*/) { gettimeofday(&request_start_time,NULL); QImage output = emptyImage(); cout << "FilmImageProvider::requestImage Here?" << endl; + cout << "FilmImageProvider::requestImage id: " << id.toStdString() << endl; //Copy out the filename. - std::string filename = paramManager->getFullFilename(); + std::string filename; //Record whether to write this thumbnail writeThisThumbnail = thumbnailWriteEnabled; - //Get a variable that says whether or not to - //Run the pipeline. Exiv2::ExifData data; - matrix image = pipeline.processImage(paramManager, this, data); + matrix image; + if (!useQuickPipe) + { + filename = paramManager->getFullFilename(); + image = pipeline.processImage(paramManager, this, data); + } + else + { + //need to check if we want the small or big image + if (id[0] == "q") + { + filename = paramManager->getFullFilename(); + image = quickPipe.processImage(paramManager, this, data); + } + else + { + filename = cloneParam->getFullFilename(); + cout << "FilmImageProvider::requestImage filename: " << filename << endl; + image = pipeline.processImage(cloneParam, this, data); + } + } //Ensure that the tiff and jpeg outputs don't write the previous image. processMutex.lock(); diff --git a/filmulator-gui/ui/filmImageProvider.h b/filmulator-gui/ui/filmImageProvider.h index b45f55cb..07983cd2 100644 --- a/filmulator-gui/ui/filmImageProvider.h +++ b/filmulator-gui/ui/filmImageProvider.h @@ -57,7 +57,10 @@ class FilmImageProvider : public QObject, public QQuickImageProvider, public Int bool thumbnailWriteEnabled = true; bool writeThisThumbnail = true; + bool useQuickPipe; + ParameterManager * paramManager; + ParameterManager * cloneParam; QMutex processMutex;//Ensures that output files are only of the currently selected image. QMutex writeDataMutex;//binds together the update of outputFilename and the outputImage. float progress; diff --git a/filmulator-gui/ui/parameterManager.cpp b/filmulator-gui/ui/parameterManager.cpp index e0ca490a..2732263a 100644 --- a/filmulator-gui/ui/parameterManager.cpp +++ b/filmulator-gui/ui/parameterManager.cpp @@ -627,7 +627,8 @@ Valid ParameterManager::getValid() //It syncs the main (slider-interfaced) settings with the database. void ParameterManager::writeback() { - //Writeback gets called by setters, which themselves are called by + //Writeback gets called by sliders once you let go. + //Non-range-type params like orientation have the setters call this. if (paramChangeEnabled) { writeToDB(imageIndex); @@ -1632,7 +1633,7 @@ void ParameterManager::loadParams(QString imageID) const float temp_cropHeight = query.value(nameCol).toFloat(); if (temp_cropHeight != m_cropHeight) { - cout << "ParameterManager::loadParams cropHeight" << endl; + //cout << "ParameterManager::loadParams cropHeight" << endl; m_cropHeight = temp_cropHeight; validity = min(validity, Valid::filmulation); } @@ -1643,7 +1644,7 @@ void ParameterManager::loadParams(QString imageID) const float temp_cropAspect = query.value(nameCol).toFloat(); if (temp_cropAspect != m_cropAspect) { - cout << "ParameterManager::loadParams cropAspect" << endl; + //cout << "ParameterManager::loadParams cropAspect" << endl; m_cropAspect = temp_cropAspect; validity = min(validity, Valid::filmulation); } @@ -1654,7 +1655,7 @@ void ParameterManager::loadParams(QString imageID) const float temp_cropVoffset = query.value(nameCol).toFloat(); if (temp_cropVoffset != m_cropVoffset) { - cout << "ParameterManager::loadParams cropVoffset" << endl; + //cout << "ParameterManager::loadParams cropVoffset" << endl; m_cropVoffset = temp_cropVoffset; validity = min(validity, Valid::filmulation); } @@ -1665,7 +1666,7 @@ void ParameterManager::loadParams(QString imageID) const float temp_cropHoffset = query.value(nameCol).toFloat(); if (temp_cropHoffset != m_cropHoffset) { - cout << "ParameterManager::loadParams cropHoffset" << endl; + //cout << "ParameterManager::loadParams cropHoffset" << endl; m_cropHoffset = temp_cropHoffset; validity = min(validity, Valid::filmulation); } @@ -1748,6 +1749,431 @@ void ParameterManager::loadParams(QString imageID) } } +//This clones parameters from another manager. +//It's intended to be used for dual pipelines and dual parametermanagers +//It's a slot that updates stuff when the other param manager gets edited. +void ParameterManager::cloneParams(ParameterManager * sourceParams) +{ + QMutexLocker paramLocker(¶mMutex);//Make all the param changes happen together. + disableParamChange();//Prevent aborting of computation. + + //Load the image index + const QString temp_imageIndex = sourceParams->getImageIndex(); + if (temp_imageIndex != imageIndex) + { + imageIndex = temp_imageIndex; + validity = min(validity, Valid::none); + } + + //do stuff to load filename and other file info; taken from selectImage + QString tempString = imageIndex; + tempString.truncate(32);//length of md5 + QSqlQuery query; + query.prepare("SELECT \ + FTfilePath,FTsensitivity,FTexposureTime,FTaperture,FTfocalLength \ + FROM FileTable WHERE FTfileID = ?;"); + query.bindValue(0, QVariant(tempString)); + query.exec(); + query.first(); + + //This will help us get the column index of the desired column name. + int nameCol; + QSqlRecord rec = query.record(); + + //Filename. First is the full path for the image pipeline. + nameCol = rec.indexOf("FTfilePath"); + if (-1 == nameCol) { std::cout << "paramManager FTfilePath" << endl; } + QString name = query.value(nameCol).toString(); + m_fullFilename = name.toStdString(); + filename = name.right(name.size() - name.lastIndexOf(QString("/")) - 1); + emit filenameChanged(); + + nameCol = rec.indexOf("FTsensitivity"); + if (-1 == nameCol) { std::cout << "paramManager FTsensitivity" << endl; } + sensitivity = query.value(nameCol).toInt(); + emit sensitivityChanged(); + + nameCol = rec.indexOf("FTexposureTime"); + if (-1 == nameCol) { std::cout << "paramManager FTexposureTime" << endl; } + QString expTimeTemp = query.value(nameCol).toString(); + bool okNum; + unsigned int numerator = expTimeTemp.left(expTimeTemp.lastIndexOf("/")).toInt(&okNum, 10); + bool okDen; + unsigned int denominator = expTimeTemp.right(expTimeTemp.size() - expTimeTemp.lastIndexOf("/") - 1).toInt(&okDen, 10); + if (okNum && okDen) + { + unsigned int divisor = gcd(numerator, denominator); + numerator /= divisor; + denominator /= divisor; + double expTime = double(numerator)/double(denominator); + if (expTime > 0.5) + { + exposureTime = QString::number(expTime, 'f', 1); + } + else + { + exposureTime = QString("%1/%2").arg(numerator).arg(denominator); + } + } + else + { + exposureTime = expTimeTemp; + } + emit exposureTimeChanged(); + + nameCol = rec.indexOf("FTaperture"); + if (-1 == nameCol) { std::cout << "paramManager FTaperture" << endl; } + float numAperture = query.value(nameCol).toFloat(); + if (numAperture >= 8) + { + aperture = QString::number(numAperture,'f',0); + } + else //numAperture < 8 + { + aperture = QString::number(numAperture,'f',1); + } + emit apertureChanged(); + + nameCol = rec.indexOf("FTfocalLength"); + if (-1 == nameCol) { std::cout << "paramManager FTfocalLength" << endl; } + focalLength = query.value(nameCol).toFloat(); + emit focalLengthChanged(); + + //tiffIn should be false. + const bool temp_tiffIn = sourceParams->getTiffIn(); + if (temp_tiffIn != m_tiffIn) + { + m_tiffIn = temp_tiffIn; + validity = min(validity, Valid::none); + } + + //So should jpegIn. + const bool temp_jpegIn = sourceParams->getJpegIn(); + if (temp_jpegIn != m_jpegIn) + { + m_jpegIn = temp_jpegIn; + validity = min(validity, Valid::none); + } + + //Then is caEnabled. + const bool temp_caEnabled = sourceParams->getCaEnabled(); + if (temp_caEnabled != m_caEnabled) + { + //cout << "ParameterManager::cloneParams caEnabled" << endl; + m_caEnabled = temp_caEnabled; + validity = min(validity, Valid::none); + } + + //Highlight recovery + const int temp_highlights = sourceParams->getHighlights(); + if (temp_highlights != m_highlights) + { + //cout << "ParameterManager::cloneParams highlights" << endl; + m_highlights = temp_highlights; + validity = min(validity, Valid::none); + } + + //Exposure compensation + const float temp_exposureComp = sourceParams->getExposureComp(); + if (temp_exposureComp != m_exposureComp) + { + //cout << "ParameterManager::cloneParams exposureComp" << endl; + m_exposureComp = temp_exposureComp; + validity = min(validity, Valid::load); + } + + //Temperature + const float temp_temperature = sourceParams->getTemperature(); + if (temp_temperature != m_temperature) + { + //cout << "ParameterManager::cloneParams temperature" << endl; + m_temperature = temp_temperature; + validity = min(validity, Valid::load); + } + + //Tint + const float temp_tint = sourceParams->getTint(); + if (temp_tint != m_tint) + { + //cout << "ParameterManager::cloneParams tint" << endl; + m_tint = temp_tint; + validity = min(validity, Valid::load); + } + + //Initial developer concentration + const float temp_initialDeveloperConcentration = sourceParams->getInitialDeveloperConcentration(); + if (temp_initialDeveloperConcentration != m_initialDeveloperConcentration) + { + //cout << "ParameterManager::cloneParams initialDeveloperConcentration" << endl; + m_initialDeveloperConcentration = temp_initialDeveloperConcentration; + validity = min(validity, Valid::prefilmulation); + } + + //Reservoir thickness + const float temp_reservoirThickness = sourceParams->getReservoirThickness(); + if (temp_reservoirThickness != m_reservoirThickness) + { + //cout << "ParameterManager::cloneParams reservoirThickness" << endl; + m_reservoirThickness = temp_reservoirThickness; + validity = min(validity, Valid::prefilmulation); + } + + //Active layer thickness + const float temp_activeLayerThickness = sourceParams->getActiveLayerThickness(); + if (temp_activeLayerThickness != m_activeLayerThickness) + { + //cout << "ParameterManager::cloneParams activeLayerThickness" << endl; + m_activeLayerThickness = temp_activeLayerThickness; + validity = min(validity, Valid::prefilmulation); + } + + //Crystals per pixel + const float temp_crystalsPerPixel = sourceParams->getCrystalsPerPixel(); + if (temp_crystalsPerPixel != m_crystalsPerPixel) + { + //cout << "ParameterManager::cloneParams crystalsPerPixel" << endl; + m_crystalsPerPixel = temp_crystalsPerPixel; + validity = min(validity, Valid::prefilmulation); + } + + //Initial crystal radius + const float temp_initialCrystalRadius = sourceParams->getInitialCrystalRadius(); + if (temp_initialCrystalRadius != m_initialCrystalRadius) + { + //cout << "ParameterManager::cloneParams initialCrystalRadius" << endl; + m_initialCrystalRadius = temp_initialCrystalRadius; + validity = min(validity, Valid::prefilmulation); + } + + //Initial silver salt area density + const float temp_initialSilverSaltDensity = sourceParams->getInitialSilverSaltDensity(); + if (temp_initialSilverSaltDensity != m_initialSilverSaltDensity) + { + //cout << "ParameterManager::cloneParams initialSilverSaltDensity" << endl; + m_initialSilverSaltDensity = temp_initialSilverSaltDensity; + validity = min(validity, Valid::prefilmulation); + } + + //Developer consumption rate constant + const float temp_developerConsumptionConst = sourceParams->getDeveloperConsumptionConst(); + if (temp_developerConsumptionConst != m_developerConsumptionConst) + { + //cout << "ParameterManager::cloneParams developerConsumptionConst" << endl; + m_developerConsumptionConst = temp_developerConsumptionConst; + validity = min(validity, Valid::prefilmulation); + } + + //Crystal growth rate constant + const float temp_crystalGrowthConst = sourceParams->getCrystalGrowthConst(); + if (temp_crystalGrowthConst != m_crystalGrowthConst) + { + //cout << "ParameterManager::cloneParams crystalGrowthConst" << endl; + m_crystalGrowthConst = temp_crystalGrowthConst; + validity = min(validity, Valid::prefilmulation); + } + + //Silver halide consumption rate constant + const float temp_silverSaltConsumptionConst = sourceParams->getSilverSaltConsumptionConst(); + if (temp_silverSaltConsumptionConst != m_silverSaltConsumptionConst) + { + //cout << "ParameterManager::cloneParams silverSaltConsumptionConst" << endl; + m_silverSaltConsumptionConst = temp_silverSaltConsumptionConst; + validity = min(validity, Valid::prefilmulation); + } + + //Total development time + const float temp_totalDevelopmentTime = sourceParams->getTotalDevelopmentTime(); + if (temp_totalDevelopmentTime != m_totalDevelopmentTime) + { + //cout << "ParameterManager::cloneParams totalDevelopmentTime" << endl; + m_totalDevelopmentTime = temp_totalDevelopmentTime; + validity = min(validity, Valid::prefilmulation); + } + + //Number of agitations + const int temp_agitateCount = sourceParams->getAgitateCount(); + if (temp_agitateCount != m_agitateCount) + { + //cout << "ParameterManager::cloneParams agitateCount" << endl; + m_agitateCount = temp_agitateCount; + validity = min(validity, Valid::prefilmulation); + } + + //Number of simulation steps for development + const int temp_developmentSteps = sourceParams->getDevelopmentSteps(); + if (temp_developmentSteps != m_developmentSteps) + { + //cout << "ParameterManager::cloneParams developmentSteps" << endl; + m_developmentSteps = temp_developmentSteps; + validity = min(validity, Valid::prefilmulation); + } + + //Area of film for the simulation + const float temp_filmArea = sourceParams->getFilmArea(); + if (temp_filmArea != m_filmArea) + { + //cout << "ParameterManager::cloneParams filmArea" << endl; + m_filmArea = temp_filmArea; + validity = min(validity, Valid::prefilmulation); + } + + //A constant for the size of the diffusion. It...affects the same thing as film area. + const float temp_sigmaConst = sourceParams->getSigmaConst(); + if (temp_sigmaConst != m_sigmaConst) + { + //cout << "ParameterManager::cloneParams sigmaConst" << endl; + m_sigmaConst = temp_sigmaConst; + validity = min(validity, Valid::prefilmulation); + } + + //Layer mix constant: the amount of active developer that gets exchanged with the reservoir. + const float temp_layerMixConst = sourceParams->getLayerMixConst(); + if (temp_layerMixConst != m_layerMixConst) + { + //cout << "ParameterManager::cloneParams layerMixConst" << endl; + m_layerMixConst = temp_layerMixConst; + validity = min(validity, Valid::prefilmulation); + } + + //Layer time divisor: Controls the relative intra-layer and inter-layer diffusion. + const float temp_layerTimeDivisor = sourceParams->getLayerTimeDivisor(); + if (temp_layerTimeDivisor != m_layerTimeDivisor) + { + //cout << "ParameterManager::cloneParams layerTimeDivisor" << endl; + m_layerTimeDivisor = temp_layerTimeDivisor; + validity = min(validity, Valid::prefilmulation); + } + + //Rolloff boundary. This is where highlights start to roll off. + const float temp_rolloffBoundary = sourceParams->getRolloffBoundary(); + if (temp_rolloffBoundary != m_rolloffBoundary) + { + //cout << "ParameterManager::cloneParams rolloffBoundary" << endl; + m_rolloffBoundary = temp_rolloffBoundary; + validity = min(validity, Valid::prefilmulation); + } + + //Post-filmulator black clipping point + const float temp_blackpoint = sourceParams->getBlackpoint(); + if (temp_blackpoint != m_blackpoint) + { + //cout << "ParameterManager::cloneParams blackpoint" << endl; + m_blackpoint = temp_blackpoint; + validity = min(validity, Valid::filmulation); + } + + //Post-filmulator white clipping point + const float temp_whitepoint = sourceParams->getWhitepoint(); + if (temp_whitepoint != m_whitepoint) + { + //cout << "ParameterManager::cloneParams whitepoint" << endl; + m_whitepoint = temp_whitepoint; + validity = min(validity, Valid::filmulation); + } + + //Height of the crop WRT image height + const float temp_cropHeight = sourceParams->getCropHeight(); + if (temp_cropHeight != m_cropHeight) + { + //cout << "ParameterManager::cloneParams cropHeight" << endl; + m_cropHeight = temp_cropHeight; + validity = min(validity, Valid::filmulation); + } + + //Aspect ratio of the crop + const float temp_cropAspect = sourceParams->getCropAspect(); + if (temp_cropAspect != m_cropAspect) + { + //cout << "ParameterManager::cloneParams cropAspect" << endl; + m_cropAspect = temp_cropAspect; + validity = min(validity, Valid::filmulation); + } + + //Vertical position offset relative to center, WRT image height + const float temp_cropVoffset = sourceParams->getCropVoffset(); + if (temp_cropVoffset != m_cropVoffset) + { + //cout << "ParameterManager::cloneParams cropVoffset" << endl; + m_cropVoffset = temp_cropVoffset; + validity = min(validity, Valid::filmulation); + } + + //Horizontal position offset relative to center, WRT image width + const float temp_cropHoffset = sourceParams->getCropHoffset(); + if (temp_cropHoffset != m_cropHoffset) + { + //cout << "ParameterManager::cloneParams cropHoffset" << endl; + m_cropHoffset = temp_cropHoffset; + validity = min(validity, Valid::filmulation); + } + + //Shadow control point x value + const float temp_shadowsX = sourceParams->getShadowsX(); + if (temp_shadowsX != m_shadowsX) + { + //cout << "ParameterManager::cloneParams shadowsX" << endl; + m_shadowsX = temp_shadowsX; + validity = min(validity, Valid::blackwhite); + } + + //Shadow control point y value + const float temp_shadowsY = sourceParams->getShadowsY(); + if (temp_shadowsY != m_shadowsY) + { + //cout << "ParameterManager::cloneParams shadowsY" << endl; + m_shadowsY = temp_shadowsY; + validity = min(validity, Valid::blackwhite); + } + + //Highlight control point x value + const float temp_highlightsX = sourceParams->getHighlightsX(); + if (temp_highlightsX != m_highlightsX) + { + //cout << "ParameterManager::cloneParams highlightsX" << endl; + m_highlightsX = temp_highlightsX; + validity = min(validity, Valid::blackwhite); + } + + //Highlight control point y value + const float temp_highlightsY = sourceParams->getHighlightsY(); + if (temp_highlightsY != m_highlightsY) + { + //cout << "ParameterManager::cloneParams highlightsY" << endl; + m_highlightsY = temp_highlightsY; + validity = min(validity, Valid::blackwhite); + } + + //Vibrance (saturation of less-saturated things) + const float temp_vibrance = sourceParams->getVibrance(); + if (temp_vibrance != m_vibrance) + { + //cout << "ParameterManager::cloneParams vibrance" << endl; + m_vibrance = temp_vibrance; + validity = min(validity, Valid::blackwhite); + } + + //Saturation + const float temp_saturation = sourceParams->getSaturation(); + if (temp_saturation != m_saturation) + { + //cout << "ParameterManager::cloneParams saturation" << endl; + m_saturation = temp_saturation; + validity = min(validity, Valid::blackwhite); + } + + //Rotation + const int temp_rotation = sourceParams->getRotation(); + if (temp_rotation != m_rotation) + { + //cout << "ParameterManager::cloneParams rotation" << endl; + m_rotation = temp_rotation; + validity = min(validity, Valid::filmlikecurve); + } + + enableParamChange();//Re-enable updating of the image. + paramChangeWrapper(QString("cloneParams")); +} + //This prevents the back-and-forth between this object and QML from aborting // computation, and also prevents the sliders' moving from marking the photo // as edited. @@ -1764,6 +2190,9 @@ void ParameterManager::paramChangeWrapper(QString source) if (paramChangeEnabled) { emit paramChanged(source); + + //update any follower parametermanagers + emit updateClone(this); if (QString("selectImage") == source) { emit updateImage(true);// it is a new image } diff --git a/filmulator-gui/ui/parameterManager.h b/filmulator-gui/ui/parameterManager.h index 890d12c4..b4fe645e 100644 --- a/filmulator-gui/ui/parameterManager.h +++ b/filmulator-gui/ui/parameterManager.h @@ -229,6 +229,9 @@ class ParameterManager : public QObject std::string getFullFilename(){return m_fullFilename;} +public slots: + void cloneParams(ParameterManager * sourceParams); + protected: //This is here for the sql insertion to pull the values from. void loadParams(QString imageID); @@ -405,6 +408,57 @@ class ParameterManager : public QObject //Rotation int getDefRotation(){return d_rotation;} + //Getters for the actual params + //Loading + bool getTiffIn(){return m_tiffIn;} + bool getJpegIn(){return m_jpegIn;} + //Demosaic + bool getCaEnabled(){return m_caEnabled;} + int getHighlights(){return m_highlights;} + + //Prefilmulation + float getExposureComp(){return m_exposureComp;} + float getTemperature(){return m_temperature;} + float getTint(){return m_tint;} + + //Filmulation + float getInitialDeveloperConcentration(){return m_initialDeveloperConcentration;} + float getReservoirThickness(){return m_reservoirThickness;} + float getActiveLayerThickness(){return m_activeLayerThickness;} + float getCrystalsPerPixel(){return m_crystalsPerPixel;} + float getInitialCrystalRadius(){return m_initialCrystalRadius;} + float getInitialSilverSaltDensity(){return m_initialSilverSaltDensity;} + float getDeveloperConsumptionConst(){return m_developerConsumptionConst;} + float getCrystalGrowthConst(){return m_crystalGrowthConst;} + float getSilverSaltConsumptionConst(){return m_silverSaltConsumptionConst;} + float getTotalDevelopmentTime(){return m_totalDevelopmentTime;} + int getAgitateCount(){return m_agitateCount;} + int getDevelopmentSteps(){return m_developmentSteps;} + float getFilmArea(){return m_filmArea;} + float getSigmaConst(){return m_sigmaConst;} + float getLayerMixConst(){return m_layerMixConst;} + float getLayerTimeDivisor(){return m_layerTimeDivisor;} + float getRolloffBoundary(){return m_rolloffBoundary;} + + //Whitepoint & blackpoint + float getBlackpoint(){return m_blackpoint;} + float getWhitepoint(){return m_whitepoint;} + float getCropHeight(){return m_cropHeight;} + float getCropAspect(){return m_cropAspect;} + float getCropVoffset(){return m_cropVoffset;} + float getCropHoffset(){return m_cropHoffset;} + + //Global all-color curves. + float getShadowsX(){return m_shadowsX;} + float getShadowsY(){return m_shadowsY;} + float getHighlightsX(){return m_highlightsX;} + float getHighlightsY(){return m_highlightsY;} + float getVibrance(){return m_vibrance;} + float getSaturation(){return m_saturation;} + + //Rotation + int getRotation(){return m_rotation;} + //Setters for the properties. //Loading void setTiffIn(bool); @@ -561,6 +615,7 @@ class ParameterManager : public QObject //General: if any param changes, emit this one as well after the param-specific signal. void paramChanged(QString source); + void updateClone(ParameterManager * param); void updateImage(bool newImage); void updateTableOut(QString table, int operation); }; From 86699e7eabea986768aa82f7f1c9aba90445503b Mon Sep 17 00:00:00 2001 From: CarVac Date: Wed, 7 Feb 2018 21:10:02 -0500 Subject: [PATCH 05/23] Make save buttons wait for full image to complete Also fix the settings revert buttons. --- filmulator-gui/qml/filmulator-gui/Edit.qml | 3 ++- filmulator-gui/qml/filmulator-gui/Settings.qml | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/filmulator-gui/qml/filmulator-gui/Edit.qml b/filmulator-gui/qml/filmulator-gui/Edit.qml index 3ebd144a..1f90d354 100644 --- a/filmulator-gui/qml/filmulator-gui/Edit.qml +++ b/filmulator-gui/qml/filmulator-gui/Edit.qml @@ -137,7 +137,7 @@ SplitView { topImage.source = hiddenImage.source if ("i" == topImage.state) { - root.imageReady = true//only after the full size image is done + root.imageReady = false//if true, it activates after the quick preview, which is wrong } else { root.imageReady = false @@ -210,6 +210,7 @@ SplitView { if (topImage.state == "i") {//if it's the full image //now we notify the queue that the latest image is ready for display + root.imageReady = true root.imageURL(topImage.source) } else if (topImage.state == "q") {//if the Quick processed image is loaded diff --git a/filmulator-gui/qml/filmulator-gui/Settings.qml b/filmulator-gui/qml/filmulator-gui/Settings.qml index cfbf58e4..fe068681 100644 --- a/filmulator-gui/qml/filmulator-gui/Settings.qml +++ b/filmulator-gui/qml/filmulator-gui/Settings.qml @@ -53,10 +53,10 @@ Rectangle { tooltipText: qsTr("This enables mipmaps for the Filmulate tab's image view. It's recommended for noisy images where not mipmapping may cause patterns to appear at different zoom levels.\n\nIt has slight impact on responsiveness for the last few tools, but it doesn't affect performance when zooming and panning. It also softens the image slightly, which may be undesireable.\n\nThis is applied as soon as you save settings.") isOn: settings.getMipmapView() defaultOn: settings.getMipmapView() - changed: false onIsOnChanged: mipmapSwitch.changed = true Component.onCompleted: { mipmapSwitch.tooltipWanted.connect(root.tooltipWanted) + mipmapSwitch.changed = false } uiScale: root.uiScale } @@ -67,10 +67,10 @@ Rectangle { tooltipText: qsTr("Warning: VERY SLOW!\n\nEnabling this turns off caching in the editor. It will consume less memory but moving any slider will cause it to recompute from the beginning.\n\nThis setting takes effect after applying settings and then restarting Filmulator.") isOn: settings.getLowMemMode() defaultOn: settings.getLowMemMode() - changed: false onIsOnChanged: lowMemModeSwitch.changed = true Component.onCompleted: { lowMemModeSwitch.tooltipWanted.connect(root.tooltipWanted) + lowMemModeSwitch.changed = false } uiScale: root.uiScale } @@ -81,10 +81,10 @@ Rectangle { tooltipText: qsTr("Enabling this causes the editor to process a small-size image before processing at full resolution, for better responsiveness. It will make it take longer before you can export an image, though.") isOn: settings.getQuickPreview() defaultOn: settings.getQuickPreview() - changed: false onIsOnChanged: quickPreviewSwitch.changed = true Component.onCompleted: { quickPreviewSwitch.tooltipWanted.connect(root.tooltipWanted) + quickPreviewSwitch.changed = false } uiScale: root.uiScale } From e732859c46e3930ab290f9acf64995b6a654783e Mon Sep 17 00:00:00 2001 From: CarVac Date: Wed, 7 Feb 2018 23:06:47 -0500 Subject: [PATCH 06/23] Correct image saving button enablement Last commit corrected it being enabled after the quick preview. But it overcorrected; it never became available again. Now it works properly. --- filmulator-gui/qml/filmulator-gui/Edit.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filmulator-gui/qml/filmulator-gui/Edit.qml b/filmulator-gui/qml/filmulator-gui/Edit.qml index 1f90d354..41b7f381 100644 --- a/filmulator-gui/qml/filmulator-gui/Edit.qml +++ b/filmulator-gui/qml/filmulator-gui/Edit.qml @@ -137,7 +137,7 @@ SplitView { topImage.source = hiddenImage.source if ("i" == topImage.state) { - root.imageReady = false//if true, it activates after the quick preview, which is wrong + //do nothing } else { root.imageReady = false From 3320d89e2b058101b20d6f44c59a547d5ce1f75f Mon Sep 17 00:00:00 2001 From: CarVac Date: Thu, 8 Feb 2018 20:01:28 -0500 Subject: [PATCH 07/23] Refactor validity to enable frequent checking Also fix a bug where when reloading from db or by cloning, the validity was incorrectly set too low --- filmulator-gui/core/filmulate.cpp | 12 +- filmulator-gui/core/imagePipeline.cpp | 45 +++-- filmulator-gui/ui/parameterManager.cpp | 246 ++++++++++++++++++++++--- filmulator-gui/ui/parameterManager.h | 33 +++- 4 files changed, 292 insertions(+), 44 deletions(-) diff --git a/filmulator-gui/core/filmulate.cpp b/filmulator-gui/core/filmulate.cpp index 3a5b7039..4388400f 100644 --- a/filmulator-gui/core/filmulate.cpp +++ b/filmulator-gui/core/filmulate.cpp @@ -31,7 +31,7 @@ bool ImagePipeline::filmulate(matrix &input_image, FilmParams filmParam; AbortStatus abort; Valid valid; - std::tie(valid, abort, filmParam) = paramManager->claimFilmParams(FilmFetch::initial); + std::tie(valid, abort, filmParam) = paramManager->claimFilmParams(); if(abort == AbortStatus::restart) { return true; @@ -120,14 +120,14 @@ bool ImagePipeline::filmulate(matrix &input_image, for(int i = 0; i <= development_steps; i++) { //Check for cancellation - std::tie(valid, abort, filmParam) = paramManager->claimFilmParams(FilmFetch::subsequent); + abort = paramManager->claimFilmAbort(); if(abort == AbortStatus::restart) { return true; } //Updating for starting the development simulation. Valid is one too high here. - pipeline->updateProgress(Valid::prefilmulation, float(i)/float(development_steps)); + pipeline->updateProgress(Valid::partfilmulation, float(i)/float(development_steps)); gettimeofday(&develop_start,NULL); @@ -148,14 +148,14 @@ bool ImagePipeline::filmulate(matrix &input_image, gettimeofday(&diffuse_start,NULL); //Check for cancellation - std::tie(valid, abort, filmParam) = paramManager->claimFilmParams(FilmFetch::subsequent); + abort = paramManager->claimFilmAbort(); if(abort == AbortStatus::restart) { return true; } //Updating for starting the diffusion simulation. Valid is one too high here. - pipeline->updateProgress(Valid::prefilmulation, float(i)/float(development_steps)); + pipeline->updateProgress(Valid::partfilmulation, float(i)/float(development_steps)); //Now, we are going to perform the diffusion part. //Here we mix the layer among itself, which grants us the @@ -216,7 +216,7 @@ bool ImagePipeline::filmulate(matrix &input_image, struct timeval mult_start; gettimeofday(&mult_start,NULL); - std::tie(valid, abort, filmParam) = paramManager->claimFilmParams(FilmFetch::subsequent); + abort = paramManager->claimFilmAbort(); if(abort == AbortStatus::restart) { return true; diff --git a/filmulator-gui/core/imagePipeline.cpp b/filmulator-gui/core/imagePipeline.cpp index 9cec6dbe..b6da4176 100644 --- a/filmulator-gui/core/imagePipeline.cpp +++ b/filmulator-gui/core/imagePipeline.cpp @@ -24,15 +24,11 @@ ImagePipeline::ImagePipeline(Cache cacheIn, Histo histoIn, QuickQuality qualityI int ImagePipeline::libraw_callback(void *data, LibRaw_progress, int, int) { AbortStatus abort; - Valid validity; //Recover the param_manager from the data ParameterManager * pManager = static_cast(data); //See whether to abort or not. - //Because LibRaw does the demosaicing, we need to use the check that's performed afterwards - //That's prefilmulation. - //If we ever use LibRaw only for decoding, then change this to do the check for demosaicing. - std::tie(validity, abort, std::ignore) = pManager->claimPrefilmParams(); + abort = pManager->claimDemosaicAbort(); if (abort == AbortStatus::restart) { return 1;//cancel processing @@ -63,7 +59,7 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag LoadParams loadParam; DemosaicParams demosaicParam; PrefilmParams prefilmParam; - FilmParams filmParam; + //FilmParams filmParam; BlackWhiteParams blackWhiteParam; FilmlikeCurvesParams curvesParam; @@ -80,13 +76,17 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag return emptyMatrix(); } //In the future we'll actually perform loading here. + valid = paramManager->markLoadComplete(); updateProgress(valid, 0.0f); + [[fallthrough]]; } + case partdemosaic: [[fallthrough]]; case load://Do demosaic { AbortStatus abort; //Because the load params are used here std::tie(valid, abort, loadParam) = paramManager->claimLoadParams(); + paramManager->markLoadComplete();//otherwise we reset validity back half a step std::tie(valid, abort, demosaicParam) = paramManager->claimDemosaicParams(); if (abort == AbortStatus::restart) { @@ -138,13 +138,13 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag LibRaw image_processor; //Connect image processor with callback for cancellation - image_processor.set_progress_handler(ImagePipeline::libraw_callback, paramManager); + //image_processor.set_progress_handler(ImagePipeline::libraw_callback, paramManager); //Open the file. const char *cstr = loadParam.fullFilename.c_str(); if (0 != image_processor.open_file(cstr)) { - cerr << "processImage: Could not read input file!" << endl; + cout << "processImage: Could not read input file!" << endl; return emptyMatrix(); } //Make abbreviations for brevity in accessing data. @@ -176,7 +176,7 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag } AbortStatus abort; - std::tie(valid, abort, prefilmParam) = paramManager->claimPrefilmParams(); + abort = paramManager->claimDemosaicAbort(); if (abort == AbortStatus::restart) { return emptyMatrix(); @@ -190,7 +190,7 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag return emptyMatrix(); } - std::tie(valid, abort, prefilmParam) = paramManager->claimPrefilmParams(); + abort = paramManager->claimDemosaicAbort(); if (abort == AbortStatus::restart) { return emptyMatrix(); @@ -244,8 +244,12 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag { cropped_image = input_image; } + + valid = paramManager->markDemosaicComplete(); updateProgress(valid, 0.0f); + [[fallthrough]]; } + case partprefilmulation: [[fallthrough]]; case demosaic://Do pre-filmulation work. { AbortStatus abort; @@ -280,12 +284,15 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag cout << "ImagePipeline::processImage: Prefilmulation complete." << endl; + valid = paramManager->markPrefilmComplete(); updateProgress(valid, 0.0f); + [[fallthrough]]; } + case partfilmulation: [[fallthrough]]; case prefilmulation://Do filmulation { //We don't need to check abort status out here, because - //the filmulate function will do so inside its loop multiple times. + //the filmulate function will do so inside its loop. //We just check for it returning an empty matrix. //Here we do the film simulation on the image... @@ -315,12 +322,11 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag cout << "ImagePipeline::processImage: Filmulation complete." << endl; - //Now, since we didn't check abort status out here, we do have to at least - // increment the validity. - AbortStatus abort; - std::tie(valid, abort, filmParam) = paramManager->claimFilmParams(FilmFetch::subsequent); + valid = paramManager->markFilmComplete(); updateProgress(valid, 0.0f); + [[fallthrough]]; } + case partblackwhite: [[fallthrough]]; case filmulation://Do whitepoint_blackpoint { AbortStatus abort; @@ -391,8 +397,11 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag blackWhiteParam.whitepoint, blackWhiteParam.blackpoint); + valid = paramManager->markBlackWhiteComplete(); updateProgress(valid, 0.0f); + [[fallthrough]]; } + case partcolorcurve: [[fallthrough]]; case blackwhite: // Do color_curve { //It's not gonna abort because we have no color curves yet.. @@ -414,8 +423,12 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag { cacheEmpty = false; } + + valid = paramManager->markColorCurvesComplete(); updateProgress(valid, 0.0f); + [[fallthrough]]; } + case partfilmlikecurve: [[fallthrough]]; case colorcurve://Do film-like curve { AbortStatus abort; @@ -457,6 +470,7 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag curvesParam.saturation); updateProgress(valid, 0.0f); + [[fallthrough]]; } default://output { @@ -473,6 +487,7 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag { interface->updateHistFinal(vibrance_saturation_image); } + valid = paramManager->markFilmLikeCurvesComplete(); updateProgress(valid, 0.0f); exifOutput = exifData; diff --git a/filmulator-gui/ui/parameterManager.cpp b/filmulator-gui/ui/parameterManager.cpp index 2732263a..32244bd9 100644 --- a/filmulator-gui/ui/parameterManager.cpp +++ b/filmulator-gui/ui/parameterManager.cpp @@ -25,11 +25,16 @@ std::tuple ParameterManager::claimLoadParams() { abort = AbortStatus::restart;//not actually possible } + else if (isClone && changeMadeSinceCheck) + { + abort = AbortStatus::restart; + } else { abort = AbortStatus::proceed; - validity = Valid::load;//mark it as started + validity = Valid::partload;//mark as being in progress } + changeMadeSinceCheck = false; LoadParams params; params.fullFilename = m_fullFilename; params.tiffIn = m_tiffIn; @@ -38,6 +43,34 @@ std::tuple ParameterManager::claimLoadParams() return tup; } +AbortStatus ParameterManager::claimLoadAbort() +{ + QMutexLocker paramLocker(¶mMutex); + changeMadeSinceCheck = false; + if (validity < Valid::partload)//make sure that progress on this step isn't invalid + { + return AbortStatus::restart; + } + else if (isClone && changeMadeSinceCheck) + { + return AbortStatus::restart; + } + else + { + return AbortStatus::proceed; + } +} + +Valid ParameterManager::markLoadComplete() +{ + QMutexLocker paramLocker(¶mMutex); + if (Valid::partload == validity) + { + validity = Valid::load;//mark step complete (duh) + } + return validity; +} + void ParameterManager::setTiffIn(bool tiffIn) { QMutexLocker paramLocker(¶mMutex); @@ -68,11 +101,16 @@ std::tuple ParameterManager::claimDemosaicPara { abort = AbortStatus::restart; } + else if (isClone && changeMadeSinceCheck) + { + abort = AbortStatus::restart; + } else { abort = AbortStatus::proceed; - validity = Valid::demosaic;//mark it as started + validity = Valid::partdemosaic; } + changeMadeSinceCheck = false; DemosaicParams params; params.caEnabled = m_caEnabled; params.highlights = m_highlights; @@ -80,6 +118,34 @@ std::tuple ParameterManager::claimDemosaicPara return tup; } +AbortStatus ParameterManager::claimDemosaicAbort() +{ + QMutexLocker paramLocker(¶mMutex); + changeMadeSinceCheck = false; + if (validity < Valid::partdemosaic) + { + return AbortStatus::restart; + } + else if (isClone && changeMadeSinceCheck) + { + return AbortStatus::restart; + } + else + { + return AbortStatus::proceed; + } +} + +Valid ParameterManager::markDemosaicComplete() +{ + QMutexLocker paramLocker(¶mMutex); + if (Valid::partdemosaic == validity) + { + validity = Valid::demosaic; + } + return validity; +} + void ParameterManager::setCaEnabled(bool caEnabled) { QMutexLocker paramLocker(¶mMutex); @@ -110,11 +176,16 @@ std::tuple ParameterManager::claimPrefilmParams { abort = AbortStatus::restart; } + else if (isClone && changeMadeSinceCheck) + { + abort = AbortStatus::restart; + } else { abort = AbortStatus::proceed; - validity = Valid::prefilmulation;//mark it as started + validity = Valid::partprefilmulation; } + changeMadeSinceCheck = false; PrefilmParams params; params.exposureComp = m_exposureComp; params.temperature = m_temperature; @@ -124,6 +195,34 @@ std::tuple ParameterManager::claimPrefilmParams return tup; } +AbortStatus ParameterManager::claimPrefilmAbort() +{ + QMutexLocker paramLocker(¶mMutex); + changeMadeSinceCheck = false; + if (validity < Valid::partprefilmulation) + { + return AbortStatus::restart; + } + else if (isClone && changeMadeSinceCheck) + { + return AbortStatus::restart; + } + else + { + return AbortStatus::proceed; + } +} + +Valid ParameterManager::markPrefilmComplete() +{ + QMutexLocker paramLocker(¶mMutex); + if (Valid::partprefilmulation == validity) + { + validity = Valid::prefilmulation; + } + return validity; +} + void ParameterManager::setExposureComp(float exposureComp) { QMutexLocker paramLocker(¶mMutex); @@ -157,27 +256,26 @@ void ParameterManager::setTint(float tint) paramChangeWrapper(QString("setTint")); } -std::tuple ParameterManager::claimFilmParams(FilmFetch fetch) +std::tuple ParameterManager::claimFilmParams() { QMutexLocker paramLocker(¶mMutex); AbortStatus abort; //If it's the first time, the source data is from prefilmulation. - if (fetch == FilmFetch::initial && validity < Valid::prefilmulation) + if (validity < Valid::prefilmulation) { abort = AbortStatus::restart; } - //If it's not the first time, the source data is from the last filmulation round - // and will be invalidated by a filmulation param being modified. - else if (fetch == FilmFetch::subsequent && validity < Valid::filmulation) + else if (isClone && changeMadeSinceCheck) { abort = AbortStatus::restart; } else { abort = AbortStatus::proceed; - validity = Valid::filmulation;//mark it as started + validity = Valid::partfilmulation; } + changeMadeSinceCheck = false; FilmParams params; params.initialDeveloperConcentration = m_initialDeveloperConcentration, params.reservoirThickness = m_reservoirThickness, @@ -200,6 +298,34 @@ std::tuple ParameterManager::claimFilmParams(FilmF return tup; } +AbortStatus ParameterManager::claimFilmAbort() +{ + QMutexLocker paramLocker(¶mMutex); + changeMadeSinceCheck = false; + if (validity < Valid::partfilmulation) + { + return AbortStatus::restart; + } + else if (isClone && changeMadeSinceCheck) + { + return AbortStatus::restart; + } + else + { + return AbortStatus::proceed; + } +} + +Valid ParameterManager::markFilmComplete() +{ + QMutexLocker paramLocker(¶mMutex); + if (Valid::partfilmulation == validity) + { + validity = Valid::filmulation; + } + return validity; +} + void ParameterManager::setInitialDeveloperConcentration(float initialDeveloperConcentration) { QMutexLocker paramLocker(¶mMutex); @@ -395,11 +521,16 @@ std::tuple ParameterManager::claimBlackWhite { abort = AbortStatus::restart; } + else if (isClone && changeMadeSinceCheck) + { + abort = AbortStatus::restart; + } else { abort = AbortStatus::proceed; - validity = Valid::blackwhite;//mark it as started + validity = Valid::partblackwhite; } + changeMadeSinceCheck = false; BlackWhiteParams params; params.blackpoint = m_blackpoint; params.whitepoint = m_whitepoint; @@ -412,6 +543,34 @@ std::tuple ParameterManager::claimBlackWhite return tup; } +AbortStatus ParameterManager::claimBlackWhiteAbort() +{ + QMutexLocker paramLocker(¶mMutex); + changeMadeSinceCheck = false; + if (validity < Valid::partblackwhite) + { + return AbortStatus::restart; + } + else if (isClone && changeMadeSinceCheck) + { + return AbortStatus::restart; + } + else + { + return AbortStatus::proceed; + } +} + +Valid ParameterManager::markBlackWhiteComplete() +{ + QMutexLocker paramLocker(¶mMutex); + if (Valid::partblackwhite == validity) + { + validity = Valid::blackwhite; + } + return validity; +} + void ParameterManager::setBlackpoint(float blackpoint) { QMutexLocker paramLocker(¶mMutex); @@ -523,6 +682,16 @@ void ParameterManager::rotateLeft() writeback();//Normally the slider has to call this when released, but this isn't a slider. } +Valid ParameterManager::markColorCurvesComplete() +{ + QMutexLocker paramLocker(¶mMutex); + if (Valid::blackwhite == validity) + { + validity = Valid::colorcurve; + } + return validity; +} + //We don't have any color curves, so this one short-circuits those // and checks back to blackwhite validity. //If we add color curves in that place, we do need to replace the following @@ -531,15 +700,20 @@ std::tuple ParameterManager::claimFilmli { QMutexLocker paramLocker(¶mMutex); AbortStatus abort; - if (validity < Valid::blackwhite) + if (validity < Valid::colorcurve) + { + abort = AbortStatus::restart; + } + else if (isClone && changeMadeSinceCheck) { abort = AbortStatus::restart; } else { abort = AbortStatus::proceed; - validity = Valid::filmlikecurve;//mark it as started + validity = Valid::partfilmlikecurve; } + changeMadeSinceCheck = false; FilmlikeCurvesParams params; params.shadowsX = m_shadowsX; params.shadowsY = m_shadowsY; @@ -551,6 +725,34 @@ std::tuple ParameterManager::claimFilmli return tup; } +AbortStatus ParameterManager::claimFilmLikeCurvesAbort() +{ + QMutexLocker paramLocker(¶mMutex); + changeMadeSinceCheck = false; + if (validity < Valid::partfilmlikecurve) + { + return AbortStatus::restart; + } + else if (isClone && changeMadeSinceCheck) + { + return AbortStatus::restart; + } + else + { + return AbortStatus::proceed; + } +} + +Valid ParameterManager::markFilmLikeCurvesComplete() +{ + QMutexLocker paramLocker(¶mMutex); + if (Valid::partfilmlikecurve == validity) + { + validity = Valid::filmlikecurve; + } + return validity; +} + void ParameterManager::setShadowsX(float shadowsX) { QMutexLocker paramLocker(¶mMutex); @@ -1371,7 +1573,7 @@ void ParameterManager::loadParams(QString imageID) { //cout << "ParameterManager::loadParams caEnabled" << endl; m_caEnabled = temp_caEnabled; - validity = min(validity, Valid::none); + validity = min(validity, Valid::load); } //Next is highlights (highlight recovery) @@ -1382,7 +1584,7 @@ void ParameterManager::loadParams(QString imageID) { //cout << "ParameterManager::loadParams highlights" << endl; m_highlights = temp_highlights; - validity = min(validity, Valid::none); + validity = min(validity, Valid::load); } //Exposure compensation @@ -1393,7 +1595,7 @@ void ParameterManager::loadParams(QString imageID) { //cout << "ParameterManager::loadParams exposureComp" << endl; m_exposureComp = temp_exposureComp; - validity = min(validity, Valid::load); + validity = min(validity, Valid::demosaic); } //Temperature @@ -1404,7 +1606,7 @@ void ParameterManager::loadParams(QString imageID) { //cout << "ParameterManager::loadParams temperature" << endl; m_temperature = temp_temperature; - validity = min(validity, Valid::load); + validity = min(validity, Valid::demosaic); } //Tint @@ -1415,7 +1617,7 @@ void ParameterManager::loadParams(QString imageID) { //cout << "ParameterManager::loadParams tint" << endl; m_tint = temp_tint; - validity = min(validity, Valid::load); + validity = min(validity, Valid::demosaic); } //Initial developer concentration @@ -1861,7 +2063,7 @@ void ParameterManager::cloneParams(ParameterManager * sourceParams) { //cout << "ParameterManager::cloneParams caEnabled" << endl; m_caEnabled = temp_caEnabled; - validity = min(validity, Valid::none); + validity = min(validity, Valid::load); } //Highlight recovery @@ -1870,7 +2072,7 @@ void ParameterManager::cloneParams(ParameterManager * sourceParams) { //cout << "ParameterManager::cloneParams highlights" << endl; m_highlights = temp_highlights; - validity = min(validity, Valid::none); + validity = min(validity, Valid::load); } //Exposure compensation @@ -1879,7 +2081,7 @@ void ParameterManager::cloneParams(ParameterManager * sourceParams) { //cout << "ParameterManager::cloneParams exposureComp" << endl; m_exposureComp = temp_exposureComp; - validity = min(validity, Valid::load); + validity = min(validity, Valid::demosaic); } //Temperature @@ -1888,7 +2090,7 @@ void ParameterManager::cloneParams(ParameterManager * sourceParams) { //cout << "ParameterManager::cloneParams temperature" << endl; m_temperature = temp_temperature; - validity = min(validity, Valid::load); + validity = min(validity, Valid::demosaic); } //Tint @@ -1897,7 +2099,7 @@ void ParameterManager::cloneParams(ParameterManager * sourceParams) { //cout << "ParameterManager::cloneParams tint" << endl; m_tint = temp_tint; - validity = min(validity, Valid::load); + validity = min(validity, Valid::demosaic); } //Initial developer concentration diff --git a/filmulator-gui/ui/parameterManager.h b/filmulator-gui/ui/parameterManager.h index b4fe645e..52170113 100644 --- a/filmulator-gui/ui/parameterManager.h +++ b/filmulator-gui/ui/parameterManager.h @@ -16,12 +16,19 @@ #include "../database/exifFunctions.h" enum Valid {none, + partload, load, + partdemosaic, demosaic, + partprefilmulation, prefilmulation, + partfilmulation, filmulation, + partblackwhite, blackwhite, + partcolorcurve, colorcurve, + partfilmlikecurve, filmlikecurve, count}; @@ -207,28 +214,47 @@ class ParameterManager : public QObject //Each stage creates its struct, checks validity, marks the validity to indicate it's begun, //and then returns the struct and the validity. + //There's a second validity-check-only method for more frequent cancellation. + //And the final marking of complete checks one more time (and doesn't mark complete if invalid) //Input std::tuple claimLoadParams(); + AbortStatus claimLoadAbort(); + Valid markLoadComplete(); //Demosaic std::tuple claimDemosaicParams(); + AbortStatus claimDemosaicAbort(); + Valid markDemosaicComplete(); //Prefilmulation std::tuple claimPrefilmParams(); + AbortStatus claimPrefilmAbort(); + Valid markPrefilmComplete(); //Filmulation - std::tuple claimFilmParams(FilmFetch fetch); + std::tuple claimFilmParams(); + AbortStatus claimFilmAbort(); + Valid markFilmComplete(); //Whitepoint & Blackpoint (and cropping and rotation and distortion) std::tuple claimBlackWhiteParams(); + AbortStatus claimBlackWhiteAbort(); + Valid markBlackWhiteComplete(); + + //Individual color curves: not implemented, so we just have to mark complete + Valid markColorCurvesComplete(); //Global, all-color curves. std::tuple claimFilmlikeCurvesParams(); + AbortStatus claimFilmLikeCurvesAbort(); + Valid markFilmLikeCurvesComplete(); Valid getValid(); std::string getFullFilename(){return m_fullFilename;} + void setClone(){isClone = true;} + public slots: void cloneParams(ParameterManager * sourceParams); @@ -237,6 +263,11 @@ public slots: void loadParams(QString imageID); void loadDefaults(const CopyDefaults useDefaults, const std::string absFilePath); + //If this is true, then this is the clone parameter manager + //and we should always abort whenever there's a change made + bool isClone = false; + bool changeMadeSinceCheck = false; + //The paramMutex exists to prevent race conditions between //changes in the parameters and changes in validity. QMutex paramMutex; From 5a7e71022c12f9c4876ba7f5fa35d4197ace46ff Mon Sep 17 00:00:00 2001 From: CarVac Date: Thu, 8 Feb 2018 22:48:16 -0500 Subject: [PATCH 08/23] Make crop play nicely with the quick preview It disables the drag handles until the full size image is ready. --- filmulator-gui/qml/filmulator-gui/Edit.qml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/filmulator-gui/qml/filmulator-gui/Edit.qml b/filmulator-gui/qml/filmulator-gui/Edit.qml index 41b7f381..0da642b8 100644 --- a/filmulator-gui/qml/filmulator-gui/Edit.qml +++ b/filmulator-gui/qml/filmulator-gui/Edit.qml @@ -707,7 +707,11 @@ SplitView { onCroppingChanged: { if (cropping) { cropDrag.updatePosition() - cropDrag.enabled = true + if (root.imageReady) { + cropDrag.enabled = true//case for no quick preview + } else { + cropDrag.enabled = false//for quick preview + } cropDrag.visible = true } else { @@ -715,6 +719,11 @@ SplitView { cropDrag.visible = false } } + onImageReadyChanged: { + if (cropping) { + cropDrag.enabled = true//only needed for quick preview + } + } } property real oldX From b9be15daa36d78bc56485281a8e9592b0b9fc0dc Mon Sep 17 00:00:00 2001 From: CarVac Date: Fri, 9 Feb 2018 20:26:10 -0500 Subject: [PATCH 09/23] Fix incorrect histogram updating on slider drag I first redid the Edit state machine for loading images, cleaning it up a lot. That didn't fix it, but then I found that the sliders were updating position (but not moving) upon mouse release... That meant that the quick image would be reloaded, but not displayed because no parameters were changed. Likewise, the full size image would not be recomputed, so the histogram would be from the low res image and not the high res one. Weird, fun, funky. Death to all bugs! --- filmulator-gui/qml/filmulator-gui/Edit.qml | 133 +++++++----------- .../gui_components/SlipperySlider.qml | 1 - 2 files changed, 53 insertions(+), 81 deletions(-) diff --git a/filmulator-gui/qml/filmulator-gui/Edit.qml b/filmulator-gui/qml/filmulator-gui/Edit.qml index 0da642b8..f19e0637 100644 --- a/filmulator-gui/qml/filmulator-gui/Edit.qml +++ b/filmulator-gui/qml/filmulator-gui/Edit.qml @@ -121,75 +121,57 @@ SplitView { scale: bottomImage.scale transformOrigin: Item.TopLeft - //This is a hidden image to do filmImageProvider loading without interrupting thumbnails. - Image { - z: -1 - id: hiddenImage - x:0 - y:0 - mipmap: settings.getMipmapView() - opacity: 0 - asynchronous: true - onStatusChanged: { - if (hiddenImage.status == Image.Ready) { - console.log("hidden image ready; state: " + topImage.state) - //topImage.state = "i"//probably unnecessary? - topImage.source = hiddenImage.source - - if ("i" == topImage.state) { - //do nothing - } - else { - root.imageReady = false - } - } - else { - root.imageReady = false - } - } + Connections { + target: settings + onMipmapViewChanged: topImage.mipmap = settings.getMipmapView() } - property string state: "p" - //This connection finds out when the parameters are done changing, and then updates the url to fetch the latest image. + property string state: "nl"//not loaded + Connections { - target: paramManager//root + target: paramManager + onImageIndexChanged: { + //this happens when paramManager.selectImage is performed and the selected image changed + topImage.state = "lt"//loading thumbnail + //selectImage still emits update image via paramChargeWrapper so we don't need to do any more + } onUpdateImage: { - console.log("update image") - if (topImage.state == "i" || topImage.state == "q") {//only if we're done with the thumbnail - if (settings.getQuickPreview()) { - topImage.state = "q"//make it do the quick processed image first - }//otherwise, leave it as whatever it was. + if (newImage) {//If this comes from paramManager.selectImage, then we want to cancel crop. + cancelCropping = true + requestingCropping = false + } + //Irrespective of that + if (topImage.state == "lt") {//a NEW image has been selected + //load thumbnail into top image + var thumbPath = organizeModel.thumbDir() + '/' + paramManager.imageIndex.slice(0,4) + '/' + paramManager.imageIndex + '.jpg' + topImage.source = thumbPath + } + else {//not a new image; probably just a slider move + //Increment the image index var num = (topImage.index + 1) % 1000000//1 in a million topImage.index = num; var s = num+""; var size = 6 //6 digit number while (s.length < size) {s = "0" + s} topImage.indexString = s - hiddenImage.source = "image://filmy/q" + topImage.indexString - } - if (topImage.state == "p") {//if we're planning on doing the thumbnail - var thumbPath = organizeModel.thumbDir() + '/' + paramManager.imageIndex.slice(0,4) + '/' + paramManager.imageIndex + '.jpg' - topImage.source = thumbPath - } - //if a new image is selected, OR the same image re-selected - if (newImage) { - cancelCropping = true - requestingCropping = false + + //now actually ask for the image + if (settings.getQuickPreview()) {//load the quick pipe + topImage.state = "lq"//loading quick pipe + topImage.source = "image://filmy/q" + topImage.indexString + } + else {//load the full size image + topImage.state = "lf"// loading full image + topImage.source = "image://filmy/f" + topImage.indexString + } } } - onImageIndexChanged: { - console.log("image index changed") - topImage.state = "p" - //topImage.source = topImage.thumbPath - } - } - Connections { - target: settings - onMipmapViewChanged: topImage.mipmap = settings.getMipmapView() } + onStatusChanged: { - if (topImage.status == Image.Ready) { + if (topImage.status == Image.Ready) { //if the image is now ready + // First, we copy to the bottom image, regardless of what else. console.log("top image ready") var topFitScaleX = flicky.width/topImage.width var topFitScaleY = flicky.height/topImage.height @@ -208,43 +190,34 @@ SplitView { console.log("TopImage state: " + topImage.state) - if (topImage.state == "i") {//if it's the full image - //now we notify the queue that the latest image is ready for display + if (topImage.state == "lf") {//it was loading the full image + topImage.state = "sf"//showing full image root.imageReady = true - root.imageURL(topImage.source) + root.imageURL(topImage.source)//replace the thumbnail in the queue with the live image } - else if (topImage.state == "q") {//if the Quick processed image is loaded - topImage.state = "i" - console.log("TopImage state after quick: " + topImage.state) - - //begin loading the main, full-size image image + else {//it was loading the thumb or the quick image + //Increment the image index var num = (topImage.index + 1) % 1000000//1 in a million topImage.index = num; var s = num+""; var size = 6 //6 digit number while (s.length < size) {s = "0" + s} topImage.indexString = s - hiddenImage.source = "image://filmy/i" + topImage.indexString - } - else if (topImage.state == "p") {//if the thumbnail Preview is loaded - //now we say that the state is loading the actual image, so as to not load a new preview. - if (settings.getQuickPreview()) { - topImage.state = "q" + + //now actually ask for the image + if (topImage.state == "lt") {//it was loading the thumbnail + topImage.state = "lq"//loading quick pipe + topImage.source = "image://filmy/q" + topImage.indexString } - else {//skip the quick preview - topImage.state = "i" + else if (topImage.state == "lq") {//it was loading the quick image + topImage.state = "lf"//loading full image + topImage.source = "image://filmy/f" + topImage.indexString } - console.log("TopImage state after thumb: " + topImage.state) - - //begin loading the image - var num = (topImage.index + 1) % 1000000//1 in a million - topImage.index = num; - var s = num+""; - var size = 6 //6 digit number - while (s.length < size) {s = "0" + s} - topImage.indexString = s - hiddenImage.source = "image://filmy/q" + topImage.indexString } + + //When the image is ready, we want to process cropping. + //Cropping should only be enabled when the state is 'lq' or 'lf'... + // but if it's 'lt' then it's already not going to be cropping. if (root.requestingCropping) { root.cropping = true } else { diff --git a/filmulator-gui/qml/filmulator-gui/gui_components/SlipperySlider.qml b/filmulator-gui/qml/filmulator-gui/gui_components/SlipperySlider.qml index bfc49f92..35424bee 100644 --- a/filmulator-gui/qml/filmulator-gui/gui_components/SlipperySlider.qml +++ b/filmulator-gui/qml/filmulator-gui/gui_components/SlipperySlider.qml @@ -347,7 +347,6 @@ Item { } onReleased: { - updateHandlePosition(mouse) if (slider.sliderState !== "fromValue") { slider.sliderState = "fromValue" } From 290f9e762056b182c51f134a4be4edbac55a442a Mon Sep 17 00:00:00 2001 From: CarVac Date: Wed, 14 Feb 2018 20:35:06 -0500 Subject: [PATCH 10/23] Make image smoothing default to off --- filmulator-gui/ui/settings.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/filmulator-gui/ui/settings.cpp b/filmulator-gui/ui/settings.cpp index 7332d00a..ccbc5a46 100644 --- a/filmulator-gui/ui/settings.cpp +++ b/filmulator-gui/ui/settings.cpp @@ -203,12 +203,7 @@ bool Settings::getMipmapView() { const bool oldMipmapView = mipmapView; QSettings settings(QSettings::UserScope, "Filmulator", "Filmulator"); - mipmapView = settings.value("edit/mipmapView", 1).toBool(); - if (oldMipmapView == mipmapView) - { - //This loops like crazy. - //emit mipmapViewChanged(); - } + mipmapView = settings.value("edit/mipmapView", 0).toBool(); return mipmapView; } From 38339ed03920465e8118085168fe128a53ef5035 Mon Sep 17 00:00:00 2001 From: CarVac Date: Sun, 18 Mar 2018 11:57:19 -0400 Subject: [PATCH 11/23] in process of implementing upscaling --- filmulator-gui/core/diffuse.cpp | 35 ++++++++++++++++++++++++++++++- filmulator-gui/core/filmSim.hpp | 5 +++++ filmulator-gui/core/filmulate.cpp | 20 +++++++++++------- filmulator-gui/core/scale.cpp | 34 ++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 9 deletions(-) diff --git a/filmulator-gui/core/diffuse.cpp b/filmulator-gui/core/diffuse.cpp index 3772b70e..a28bbef0 100644 --- a/filmulator-gui/core/diffuse.cpp +++ b/filmulator-gui/core/diffuse.cpp @@ -169,6 +169,8 @@ void diffuse_x(matrix &developer_concentration, int convlength, // in Signal Processing 44 (1995) 139-151 //Referencing code from here: //https://github.com/halide/Halide/blob/e23f83b9bde63ed64f4d9a2fbe1ed29b9cfbf2e6/test/generator/gaussian_blur_generator.cpp + +//Don't use this for radii > 70!!! void diffuse_short_convolution(matrix &developer_concentration, const float sigma_const, const float pixels_per_millimeter, @@ -178,7 +180,7 @@ void diffuse_short_convolution(matrix &developer_concentration, const int width = developer_concentration.nc(); //Compute the standard deviation of the blur we want, in pixels. - double sigma = sqrt(timestep*pow(sigma_const*pixels_per_millimeter,2)); + const double sigma = sqrt(timestep*pow(sigma_const*pixels_per_millimeter,2)); //We set the padding to be 4 standard deviations so as to catch as much as possible. const int paddedWidth = width + 4*sigma + 3; @@ -456,3 +458,34 @@ void diffuse_short_convolution(matrix &developer_concentration, } } } + +//Since the aforementioned infinite impulse response doesn't work nicely with large radii, +//this will downsample it so that the radius ends up at about 30. +//Then, it'll apply the van Vliet IIR filter. +//If the radius was already less than 70, then it won't downsample at all. +void diffuse_resize_iir(matrix &developer_concentration, + const float sigma_const, + const float pixels_per_millimeter, + const float timestep) +{ + //set up test sigma + const double sigma = sqrt(timestep*pow(sigma_const*pixels_per_millimeter,2)); + + std::cout << "sigma: " << sigma << "=======================================================" << std::endl; + + //If it's small enough, we're not going to resize at all. + if (sigma < 70) + { + diffuse_short_convolution(developer_concentration, + sigma_const, + pixels_per_millimeter, + timestep); + } + else + { + diffuse(developer_concentration, + sigma_const, + pixels_per_millimeter, + timestep); + } +} diff --git a/filmulator-gui/core/filmSim.hpp b/filmulator-gui/core/filmSim.hpp index adbf071a..ae86f78a 100644 --- a/filmulator-gui/core/filmSim.hpp +++ b/filmulator-gui/core/filmSim.hpp @@ -97,6 +97,11 @@ void diffuse_short_convolution(matrix &developer_concentration, const float pixels_per_millimeter, const float timestep); +void diffuse_resize_iir(matrix &developer_concentration, + const float sigma_const, + const float pixels_per_millimeter, + const float timestep); + //Reading raws with libraw //TODO: remove //PROBABLY NOT NECESSARY ANYMORE diff --git a/filmulator-gui/core/filmulate.cpp b/filmulator-gui/core/filmulate.cpp index 4388400f..dc358d38 100644 --- a/filmulator-gui/core/filmulate.cpp +++ b/filmulator-gui/core/filmulate.cpp @@ -160,14 +160,18 @@ bool ImagePipeline::filmulate(matrix &input_image, //Now, we are going to perform the diffusion part. //Here we mix the layer among itself, which grants us the // local contrast increases. -// diffuse(developer_concentration, -// sigma_const, -// pixels_per_millimeter, -// timestep); - diffuse_short_convolution(developer_concentration, - sigma_const, - pixels_per_millimeter, - timestep); + diffuse(developer_concentration, + sigma_const, + pixels_per_millimeter, + timestep); +// diffuse_short_convolution(developer_concentration, +// sigma_const, +// pixels_per_millimeter, +// timestep); +// diffuse_resize_iir(developer_concentration, +// sigma_const, +// pixels_per_millimeter, +// timestep); diffuse_dif += timeDiff(diffuse_start); diff --git a/filmulator-gui/core/scale.cpp b/filmulator-gui/core/scale.cpp index 2bec9a1c..35cb29f7 100644 --- a/filmulator-gui/core/scale.cpp +++ b/filmulator-gui/core/scale.cpp @@ -15,6 +15,11 @@ void downscaleBilinear1D(const matrix input, const double scaleFactor, const bool interleaved); +template +void upscaleBilinear1D(const matrix input, + matrix &output, + const int outNumCols, + const bool interleaved); //Scales the input to the output to fit within the output sizes. @@ -174,5 +179,34 @@ void downscaleBilinear1D(const matrix input, output(i,j) = startWeight*double(input(i,inputStart)) + endWeight*double(input(i,inputEnd)); } } +} + +//Scales the image up so that the number of columns is increased to the desired number. +//outputNumCols should be for the un-interleaved image. +//TODO: COMPLETE THIS +template +void upscaleBilinear1D(const matrix input, + matrix &output, + const int outNumCols, + const bool interleaved) +{ + const int inputNumRows = input.nr(); + const int inputNumCols = input.nc(); + + if (outNumCols <= inputNumCols) + { + output.set_size(0,0); + return; + } + + if (interleaved) + { + output.set_size(inputNumRows, outNumCols*3); + } + else + { + output.set_size(inputNumRows, outNumCols); + } + } From fdfb4028c9b7cdd01f11e2c8f94fe14c79d9130c Mon Sep 17 00:00:00 2001 From: CarVac Date: Mon, 19 Mar 2018 21:36:21 -0400 Subject: [PATCH 12/23] now it compiles but still doesn't do anything special --- filmulator-gui/core/scale.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filmulator-gui/core/scale.cpp b/filmulator-gui/core/scale.cpp index 35cb29f7..aeddab97 100644 --- a/filmulator-gui/core/scale.cpp +++ b/filmulator-gui/core/scale.cpp @@ -186,7 +186,7 @@ void downscaleBilinear1D(const matrix input, //TODO: COMPLETE THIS template void upscaleBilinear1D(const matrix input, - matrix &output, + matrix &output, const int outNumCols, const bool interleaved) { From 4d63f2fe363fb9d12e7f1cc26bb81808f6a50fad Mon Sep 17 00:00:00 2001 From: CarVac Date: Sat, 14 Apr 2018 21:58:10 -0400 Subject: [PATCH 13/23] Steal demosaic from quick pipe, make res adjable Stealing the demosaiced image from quick pipe makes it respond a lot faster to a new image. With an adjustable resolution, you can now make the quick pipeline sharper to your taste, at the tradeoff of responsiveness. --- filmulator-gui/core/imagePipeline.cpp | 30 ++++++++++------ filmulator-gui/core/imagePipeline.h | 14 ++++++-- .../qml/filmulator-gui/Settings.qml | 36 ++++++++++++++++--- filmulator-gui/ui/filmImageProvider.cpp | 4 +++ filmulator-gui/ui/filmImageProvider.h | 2 +- filmulator-gui/ui/settings.cpp | 17 +++++++++ filmulator-gui/ui/settings.h | 5 +++ 7 files changed, 90 insertions(+), 18 deletions(-) diff --git a/filmulator-gui/core/imagePipeline.cpp b/filmulator-gui/core/imagePipeline.cpp index b6da4176..cce063f2 100644 --- a/filmulator-gui/core/imagePipeline.cpp +++ b/filmulator-gui/core/imagePipeline.cpp @@ -96,7 +96,6 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag cout << "imagePipeline.cpp: Opening " << loadParam.fullFilename << endl; - matrix input_image; //Reads in the photo. cout << "load start:" << timeDiff (timeRequested) << endl; struct timeval imload_time; @@ -116,7 +115,11 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag return emptyMatrix(); } */ - if (loadParam.tiffIn) + if ((HighQuality == quality) && stealData)//only full pipelines may steal data + { + input_image = stealVictim->input_image; + } + else if (loadParam.tiffIn) { if (imread_tiff(loadParam.fullFilename, input_image, exifData)) { @@ -236,13 +239,20 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag cout << "scale start:" << timeDiff (timeRequested) << endl; struct timeval downscale_time; gettimeofday( &downscale_time, NULL ); - downscale_and_crop(input_image,cropped_image, 0, 0, (input_image.nc()/3)-1,input_image.nr()-1, 600, 600); - //cropped_image = input_image; + downscale_and_crop(input_image,scaled_image, 0, 0, (input_image.nc()/3)-1,input_image.nr()-1, 600, 600); + cout << "scale end: " << timeDiff( downscale_time ) << endl; + } + else if (PreviewQuality == quality) + { + cout << "scale start:" << timeDiff (timeRequested) << endl; + struct timeval downscale_time; + gettimeofday( &downscale_time, NULL ); + downscale_and_crop(input_image,scaled_image, 0, 0, (input_image.nc()/3)-1,input_image.nr()-1, resolution, resolution); cout << "scale end: " << timeDiff( downscale_time ) << endl; } else { - cropped_image = input_image; + scaled_image = input_image; } valid = paramManager->markDemosaicComplete(); @@ -260,7 +270,7 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag } //Here we apply the exposure compensation and white balance. - matrix exposureImage = cropped_image * pow(2, prefilmParam.exposureComp); + matrix exposureImage = scaled_image * pow(2, prefilmParam.exposureComp); whiteBalance(exposureImage, pre_film_image, prefilmParam.temperature, @@ -269,7 +279,7 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag if (NoCache == cache) { - cropped_image.set_size( 0, 0 ); + scaled_image.set_size( 0, 0 ); cacheEmpty = true; } else @@ -379,10 +389,10 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag height = imHeight; } - matrix actually_cropped_image; + matrix cropped_image; downscale_and_crop(rotated_image, - actually_cropped_image, + cropped_image, startX, startY, endX, @@ -392,7 +402,7 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag rotated_image.set_size(0, 0);// clean up ram that's not needed anymore - whitepoint_blackpoint(actually_cropped_image,//filmulated_image, + whitepoint_blackpoint(cropped_image,//filmulated_image, contrast_image, blackWhiteParam.whitepoint, blackWhiteParam.blackpoint); diff --git a/filmulator-gui/core/imagePipeline.h b/filmulator-gui/core/imagePipeline.h index 0c84cdc6..dd5b6e63 100644 --- a/filmulator-gui/core/imagePipeline.h +++ b/filmulator-gui/core/imagePipeline.h @@ -8,7 +8,7 @@ enum Cache {WithCache, NoCache}; enum Histo {WithHisto, NoHisto}; -enum QuickQuality { LowQuality, HighQuality }; +enum QuickQuality { LowQuality, PreviewQuality, HighQuality }; class ImagePipeline { @@ -30,6 +30,13 @@ class ImagePipeline //Lets the consumer turn cache on and off void setCache(Cache cacheIn); + //Variable relating to stealing the demosaiced data from another imagepipeline + bool stealData = false; + ImagePipeline * stealVictim; + + //The resolution of a quick preview + int resolution; + protected: matrix emptyMatrix(){matrix mat; return mat;} @@ -48,7 +55,8 @@ class ImagePipeline struct timeval timeRequested; - matrix cropped_image; + matrix input_image; + matrix scaled_image; matrix pre_film_image; Exiv2::ExifData exifData; matrix filmulated_image; @@ -61,7 +69,7 @@ class ImagePipeline void updateProgress(Valid valid, float CurrFractionCompleted); //The core filmulation. It needs to access ProcessingParameters, so it's here. - bool filmulate(matrix &cropped_image, + bool filmulate(matrix &scaled_image, matrix &output_density, ParameterManager * paramManager, ImagePipeline * pipeline); diff --git a/filmulator-gui/qml/filmulator-gui/Settings.qml b/filmulator-gui/qml/filmulator-gui/Settings.qml index fe068681..eaa43acd 100644 --- a/filmulator-gui/qml/filmulator-gui/Settings.qml +++ b/filmulator-gui/qml/filmulator-gui/Settings.qml @@ -78,7 +78,7 @@ Rectangle { ToolSwitch { id: quickPreviewSwitch text: qsTr("Render small preview first") - tooltipText: qsTr("Enabling this causes the editor to process a small-size image before processing at full resolution, for better responsiveness. It will make it take longer before you can export an image, though.") + tooltipText: qsTr("Enabling this causes the editor to process a small-size image before processing at full resolution, for better responsiveness. It will make it take longer before you can export an image, though.\n\nThis takes effect after applying settings and restarting Filmulator.") isOn: settings.getQuickPreview() defaultOn: settings.getQuickPreview() onIsOnChanged: quickPreviewSwitch.changed = true @@ -89,13 +89,38 @@ Rectangle { uiScale: root.uiScale } + ToolSlider { + id: previewResSlider + title: qsTr("Preview render resolution") + tooltipText: qsTr("When the small preview is active, the preview image will be processed at an image size with this value as the long dimension. The larger this is, the sharper the preview, but the longer it takes to generate.\n\nThis takes effect after applying settings and restarting Filmulator.") + minimumValue: 100 + maximumValue: 8000 + stepSize: 100 + tickmarksEnabled: true + tickmarkFactor: 1 + value: settings.getPreviewResolution() + defaultValue: settings.getPreviewResolution() + changed: false + onValueChanged: { + if (Math.abs(value - defaultValue) < 0.5) { + previewResSlider.changed = false + } else { + previewResSlider.changed = true + } + } + Component.onCompleted: { + previewResSlider.tooltipWanted.connect(root.tooltipWanted) + } + uiScale: root.uiScale + } + ToolButton { id: saveSettings text: qsTr("Save Settings") tooltipText: qsTr("Apply settings and save for future use") width: settingsList.width height: 40 * uiScale - notDisabled: uiScaleSlider.changed || mipmapSwitch.changed || lowMemModeSwitch.changed || quickPreviewSwitch.changed + notDisabled: uiScaleSlider.changed || mipmapSwitch.changed || lowMemModeSwitch.changed || quickPreviewSwitch.changed || previewResSlider.changed onTriggered: { settings.uiScale = uiScaleSlider.value uiScaleSlider.defaultValue = uiScaleSlider.value @@ -105,10 +130,13 @@ Rectangle { mipmapSwitch.changed = false settings.lowMemMode = lowMemModeSwitch.isOn lowMemModeSwitch.defaultOn = lowMemModeSwitch.isOn - lowMemModeSwitch.changed = false; + lowMemModeSwitch.changed = false settings.quickPreview = quickPreviewSwitch.isOn quickPreviewSwitch.defaultOn = quickPreviewSwitch.isOn - quickPreviewSwitch.changed = false; + quickPreviewSwitch.changed = false + settings.previewResolution = previewResSlider.value + previewResSlider.defaultValue = previewResSlider.value + previewResSlider.changed = false } uiScale: root.uiScale } diff --git a/filmulator-gui/ui/filmImageProvider.cpp b/filmulator-gui/ui/filmImageProvider.cpp index 34a9623c..6ebfc734 100644 --- a/filmulator-gui/ui/filmImageProvider.cpp +++ b/filmulator-gui/ui/filmImageProvider.cpp @@ -40,10 +40,14 @@ FilmImageProvider::FilmImageProvider(ParameterManager * manager) : pipeline.setCache(WithCache); } + quickPipe.resolution = settingsObject.getPreviewResolution(); + //Check if we want to use dual pipelines if (settingsObject.getQuickPreview()) { useQuickPipe = true; + pipeline.stealData = true; + pipeline.stealVictim = &quickPipe; } else { diff --git a/filmulator-gui/ui/filmImageProvider.h b/filmulator-gui/ui/filmImageProvider.h index 07983cd2..336bf85d 100644 --- a/filmulator-gui/ui/filmImageProvider.h +++ b/filmulator-gui/ui/filmImageProvider.h @@ -50,7 +50,7 @@ class FilmImageProvider : public QObject, public QQuickImageProvider, public Int protected: ImagePipeline pipeline = ImagePipeline(WithCache, WithHisto, HighQuality); - ImagePipeline quickPipe = ImagePipeline(WithCache, WithHisto, LowQuality);//for now it'll just be the 600 size + ImagePipeline quickPipe = ImagePipeline(WithCache, WithHisto, PreviewQuality);//for now it'll just be the 600 size ThumbWriteWorker *worker = new ThumbWriteWorker; QThread workerThread; diff --git a/filmulator-gui/ui/settings.cpp b/filmulator-gui/ui/settings.cpp index 700d6673..28aa978e 100644 --- a/filmulator-gui/ui/settings.cpp +++ b/filmulator-gui/ui/settings.cpp @@ -245,3 +245,20 @@ bool Settings::getQuickPreview() emit quickPreviewChanged(); return quickPreview; } + +void Settings::setPreviewResolution(int resolutionIn) +{ + QSettings settings(QSettings::UserScope, "Filmulator", "Filmulator"); + previewResolution = resolutionIn; + settings.setValue("edit/previewResolution", resolutionIn); + emit previewResolutionChanged(); +} + +int Settings::getPreviewResolution() +{ + QSettings settings(QSettings::UserScope, "Filmulator", "Filmulator"); + //Default: 1000 pixels wide + previewResolution = settings.value("edit/previewResolution", 1500).toInt(); + emit previewResolutionChanged(); + return previewResolution; +} diff --git a/filmulator-gui/ui/settings.h b/filmulator-gui/ui/settings.h index 76235b9b..dec87cb9 100644 --- a/filmulator-gui/ui/settings.h +++ b/filmulator-gui/ui/settings.h @@ -22,6 +22,7 @@ class Settings : public QObject Q_PROPERTY(bool mipmapView READ getMipmapView WRITE setMipmapView NOTIFY mipmapViewChanged) Q_PROPERTY(bool lowMemMode READ getLowMemMode WRITE setLowMemMode NOTIFY lowMemModeChanged) Q_PROPERTY(bool quickPreview READ getQuickPreview WRITE setQuickPreview NOTIFY quickPreviewChanged) + Q_PROPERTY(int previewResolution READ getPreviewResolution WRITE setPreviewResolution NOTIFY previewResolutionChanged) public: explicit Settings(QObject *parent = 0); @@ -39,6 +40,7 @@ class Settings : public QObject void setMipmapView(bool mipmapViewIn); void setLowMemMode(bool lowMemModeIn); void setQuickPreview(bool quickPreviewIn); + void setPreviewResolution(int resolutionIn); Q_INVOKABLE QString getPhotoStorageDir(); Q_INVOKABLE QString getPhotoBackupDir(); @@ -54,6 +56,7 @@ class Settings : public QObject Q_INVOKABLE bool getMipmapView(); Q_INVOKABLE bool getLowMemMode(); Q_INVOKABLE bool getQuickPreview(); + Q_INVOKABLE int getPreviewResolution(); protected: QString photoStorageDir; @@ -70,6 +73,7 @@ class Settings : public QObject bool mipmapView; bool lowMemMode; bool quickPreview; + int previewResolution; signals: void photoStorageDirChanged(); @@ -86,6 +90,7 @@ class Settings : public QObject void mipmapViewChanged(); void lowMemModeChanged(); void quickPreviewChanged(); + void previewResolutionChanged(); }; #endif // SETTINGS_H From f4a17f879349cb358b4bbdc63461057bcf8464f4 Mon Sep 17 00:00:00 2001 From: CarVac Date: Sun, 15 Apr 2018 10:47:13 -0400 Subject: [PATCH 14/23] use 0.18 version of LibRaw --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 85ea5194..3dfac8e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ script: - git clone https://github.com/LibRaw/LibRaw-demosaic-pack-GPL2.git - git clone https://github.com/LibRaw/LibRaw-demosaic-pack-GPL3.git - cd LibRaw + - git checkout 0.18-stable - patch -p1 < ../patches/libraw-makefile.patch - make -j3 -f Makefile.dist - sudo make install -f Makefile.dist From 63aadad75a19d0ef794f758a2d3c9b93aae8fab6 Mon Sep 17 00:00:00 2001 From: CarVac Date: Sun, 15 Apr 2018 11:12:11 -0400 Subject: [PATCH 15/23] Make LibRaw 0.18 build --- patches/libraw-makefile.patch | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/patches/libraw-makefile.patch b/patches/libraw-makefile.patch index f1640e68..b5159506 100644 --- a/patches/libraw-makefile.patch +++ b/patches/libraw-makefile.patch @@ -1,13 +1,23 @@ *** LibRaw/Makefile.dist Wed Jun 28 14:44:51 2017 --- LibRaw-new/Makefile.dist Wed Jun 28 15:13:02 2017 *************** -*** 7,11 **** +*** 1,11 **** + #all: library all_samples + +! #CFLAGS=-arch i386 -arch x86_64 -O3 -I. -w + #CC=gcc + #CXX=g++ # OpenMP support ! #CFLAGS+=-fopenmp # RawSpeed Support ---- 7,11 ---- +--- 1,11 ---- + #all: library all_samples + +! CFLAGS=-march=i386 -march=x86-64 -O3 -I. -w + #CC=gcc + #CXX=g++ # OpenMP support ! CFLAGS+=-fopenmp From 0102dc012f33d356d73b1ccb70dd5084963d2980 Mon Sep 17 00:00:00 2001 From: CarVac Date: Sun, 15 Apr 2018 11:23:47 -0400 Subject: [PATCH 16/23] another try to fix libraw patch --- patches/libraw-makefile.patch | 36 ++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/patches/libraw-makefile.patch b/patches/libraw-makefile.patch index b5159506..f91c0e3e 100644 --- a/patches/libraw-makefile.patch +++ b/patches/libraw-makefile.patch @@ -1,45 +1,52 @@ -*** LibRaw/Makefile.dist Wed Jun 28 14:44:51 2017 ---- LibRaw-new/Makefile.dist Wed Jun 28 15:13:02 2017 +*** Makefile.dist 2018-04-15 11:21:14.849133275 -0400 +--- patch/Makefile.dist 2018-04-15 11:21:38.477269777 -0400 *************** *** 1,11 **** - #all: library all_samples + all: library all_samples -! #CFLAGS=-arch i386 -arch x86_64 -O3 -I. -w - #CC=gcc - #CXX=g++ +! CFLAGS=-arch i386 -arch x86_64 -O3 -I. -w + CC=gcc + CXX=g++ # OpenMP support ! #CFLAGS+=-fopenmp # RawSpeed Support + #CFLAGS+=-pthread -DUSE_RAWSPEED -I../RawSpeed -I/usr/local/include/libxml2 --- 1,11 ---- - #all: library all_samples + all: library all_samples ! CFLAGS=-march=i386 -march=x86-64 -O3 -I. -w - #CC=gcc - #CXX=g++ + CC=gcc + CXX=g++ # OpenMP support ! CFLAGS+=-fopenmp # RawSpeed Support + #CFLAGS+=-pthread -DUSE_RAWSPEED -I../RawSpeed -I/usr/local/include/libxml2 *************** -*** 23,28 **** +*** 21,28 **** + #LDADD+=-L/usr/local/lib -ljasper # JPEG support for lossy DNG ! #CFLAGS+=-DUSE_JPEG -I/usr/local/include ! #LDADD+=-L/usr/local/lib -ljpeg # LIBJPEG8: #CFLAGS+=-DUSE_JPEG8 ---- 23,28 ---- + +--- 21,28 ---- + #LDADD+=-L/usr/local/lib -ljasper # JPEG support for lossy DNG ! CFLAGS+=-DUSE_JPEG ! LDADD+=-ljpeg # LIBJPEG8: #CFLAGS+=-DUSE_JPEG8 + *************** -*** 37,46 **** +*** 35,46 **** + #LDADD+=-L/usr/local/lib -llcms2 # Demosaic Pack GPL2: ! #DPCFLAGS+=-I../LibRaw-demosaic-pack-GPL2 @@ -50,7 +57,9 @@ ! #CFLAGS+=-DLIBRAW_DEMOSAIC_PACK_GPL3 ---- 37,46 ---- + DCRAW_LIB_OBJECTS=object/dcraw_common.o object/libraw_cxx.o object/libraw_datastream.o object/libraw_c_api.o object/dcraw_fileio.o object/demosaic_packs.o +--- 35,46 ---- + #LDADD+=-L/usr/local/lib -llcms2 # Demosaic Pack GPL2: ! DPCFLAGS+=-I../LibRaw-demosaic-pack-GPL2 @@ -61,3 +70,4 @@ ! CFLAGS+=-DLIBRAW_DEMOSAIC_PACK_GPL3 + DCRAW_LIB_OBJECTS=object/dcraw_common.o object/libraw_cxx.o object/libraw_datastream.o object/libraw_c_api.o object/dcraw_fileio.o object/demosaic_packs.o From 0c8a6f4ab82c5f0a75aa82e457e02536c40e726a Mon Sep 17 00:00:00 2001 From: CarVac Date: Sun, 15 Apr 2018 20:35:36 -0400 Subject: [PATCH 17/23] Reduce memory usage of quick pipe demosaic stealincg --- filmulator-gui/core/imagePipeline.cpp | 8 ++++++-- filmulator-gui/qml/filmulator-gui/Settings.qml | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/filmulator-gui/core/imagePipeline.cpp b/filmulator-gui/core/imagePipeline.cpp index cce063f2..e02fb69c 100644 --- a/filmulator-gui/core/imagePipeline.cpp +++ b/filmulator-gui/core/imagePipeline.cpp @@ -117,7 +117,7 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag */ if ((HighQuality == quality) && stealData)//only full pipelines may steal data { - input_image = stealVictim->input_image; + scaled_image = stealVictim->input_image; } else if (loadParam.tiffIn) { @@ -252,7 +252,11 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag } else { - scaled_image = input_image; + if (!stealData) //If we had to compute the input image ourselves + { + scaled_image = input_image; + input_image.set_size(0,0); + } } valid = paramManager->markDemosaicComplete(); diff --git a/filmulator-gui/qml/filmulator-gui/Settings.qml b/filmulator-gui/qml/filmulator-gui/Settings.qml index eaa43acd..9850fbf0 100644 --- a/filmulator-gui/qml/filmulator-gui/Settings.qml +++ b/filmulator-gui/qml/filmulator-gui/Settings.qml @@ -96,8 +96,6 @@ Rectangle { minimumValue: 100 maximumValue: 8000 stepSize: 100 - tickmarksEnabled: true - tickmarkFactor: 1 value: settings.getPreviewResolution() defaultValue: settings.getPreviewResolution() changed: false From 4502fde845bd131f161d1c4fbb37c2c97ecfb9c9 Mon Sep 17 00:00:00 2001 From: CarVac Date: Mon, 16 Apr 2018 20:09:30 -0400 Subject: [PATCH 18/23] re-add disabling crop and export while processing --- filmulator-gui/qml/filmulator-gui/Edit.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/filmulator-gui/qml/filmulator-gui/Edit.qml b/filmulator-gui/qml/filmulator-gui/Edit.qml index f19e0637..e1699d54 100644 --- a/filmulator-gui/qml/filmulator-gui/Edit.qml +++ b/filmulator-gui/qml/filmulator-gui/Edit.qml @@ -196,6 +196,7 @@ SplitView { root.imageURL(topImage.source)//replace the thumbnail in the queue with the live image } else {//it was loading the thumb or the quick image + root.imageReady = false //Increment the image index var num = (topImage.index + 1) % 1000000//1 in a million topImage.index = num; From 45865d609d7b8ce22526610ee2aa876a3c3f71a7 Mon Sep 17 00:00:00 2001 From: CarVac Date: Mon, 16 Apr 2018 20:44:10 -0400 Subject: [PATCH 19/23] Make full size steal the exif data --- filmulator-gui/core/imagePipeline.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/filmulator-gui/core/imagePipeline.cpp b/filmulator-gui/core/imagePipeline.cpp index e02fb69c..61a7e78f 100644 --- a/filmulator-gui/core/imagePipeline.cpp +++ b/filmulator-gui/core/imagePipeline.cpp @@ -118,6 +118,7 @@ matrix ImagePipeline::processImage(ParameterManager * paramManag if ((HighQuality == quality) && stealData)//only full pipelines may steal data { scaled_image = stealVictim->input_image; + exifData = stealVictim->exifData; } else if (loadParam.tiffIn) { From 7c1fd30092ca054427036e13e56dc77691de5d43 Mon Sep 17 00:00:00 2001 From: CarVac Date: Fri, 20 Apr 2018 21:16:51 -0400 Subject: [PATCH 20/23] Make full pipeline abort for any parameter change Not just ones earlier in the pipeline, also ones that are later. This dramatically improves responsiveness for certain tasks. --- filmulator-gui/qml/filmulator-gui/Edit.qml | 16 ++++++++-- filmulator-gui/ui/filmImageProvider.cpp | 1 + filmulator-gui/ui/parameterManager.cpp | 36 ++++++++++++++++------ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/filmulator-gui/qml/filmulator-gui/Edit.qml b/filmulator-gui/qml/filmulator-gui/Edit.qml index e1699d54..37623ef1 100644 --- a/filmulator-gui/qml/filmulator-gui/Edit.qml +++ b/filmulator-gui/qml/filmulator-gui/Edit.qml @@ -225,6 +225,20 @@ SplitView { root.cropping = false } } + else if (topImage.status == Image.Error) { + root.imageReady = false + //Increment the image index + var num = (topImage.index + 1) % 1000000//1 in a million + topImage.index = num; + var s = num+""; + var size = 6 //6 digit number + while (s.length < size) {s = "0" + s} + topImage.indexString = s + + //now actually ask for the image + topImage.state = "lq"//loading quick pipe + topImage.source = "image://filmy/q" + topImage.indexString + } } } Image { @@ -1675,7 +1689,6 @@ SplitView { text: qsTr("Rotate Left") onTriggered: { paramManager.rotateLeft() - root.updateImage() } uiScale: root.uiScale } @@ -1688,7 +1701,6 @@ SplitView { text: qsTr("Rotate Right") onTriggered: { paramManager.rotateRight() - root.updateImage() } uiScale: root.uiScale } diff --git a/filmulator-gui/ui/filmImageProvider.cpp b/filmulator-gui/ui/filmImageProvider.cpp index 6ebfc734..c93c12ce 100644 --- a/filmulator-gui/ui/filmImageProvider.cpp +++ b/filmulator-gui/ui/filmImageProvider.cpp @@ -19,6 +19,7 @@ FilmImageProvider::FilmImageProvider(ParameterManager * manager) : { paramManager = manager; cloneParam = new ParameterManager; + cloneParam->setClone(); connect(paramManager, SIGNAL(updateClone(ParameterManager*)), cloneParam, SLOT(cloneParams(ParameterManager*))); zeroHistogram(finalHist); zeroHistogram(postFilmHist); diff --git a/filmulator-gui/ui/parameterManager.cpp b/filmulator-gui/ui/parameterManager.cpp index 32244bd9..ed6de93f 100644 --- a/filmulator-gui/ui/parameterManager.cpp +++ b/filmulator-gui/ui/parameterManager.cpp @@ -21,6 +21,7 @@ std::tuple ParameterManager::claimLoadParams() { QMutexLocker paramLocker(¶mMutex); AbortStatus abort; + changeMadeSinceCheck = false;//We can't have it abort first thing in the pipeline. if (validity < Valid::none)//If something earlier than this has changed { abort = AbortStatus::restart;//not actually possible @@ -34,7 +35,7 @@ std::tuple ParameterManager::claimLoadParams() abort = AbortStatus::proceed; validity = Valid::partload;//mark as being in progress } - changeMadeSinceCheck = false; + //changeMadeSinceCheck = false; LoadParams params; params.fullFilename = m_fullFilename; params.tiffIn = m_tiffIn; @@ -46,17 +47,19 @@ std::tuple ParameterManager::claimLoadParams() AbortStatus ParameterManager::claimLoadAbort() { QMutexLocker paramLocker(¶mMutex); - changeMadeSinceCheck = false; if (validity < Valid::partload)//make sure that progress on this step isn't invalid { + changeMadeSinceCheck = false; return AbortStatus::restart; } else if (isClone && changeMadeSinceCheck) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else { + changeMadeSinceCheck = false; return AbortStatus::proceed; } } @@ -121,17 +124,19 @@ std::tuple ParameterManager::claimDemosaicPara AbortStatus ParameterManager::claimDemosaicAbort() { QMutexLocker paramLocker(¶mMutex); - changeMadeSinceCheck = false; if (validity < Valid::partdemosaic) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else if (isClone && changeMadeSinceCheck) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else { + changeMadeSinceCheck = false; return AbortStatus::proceed; } } @@ -198,17 +203,19 @@ std::tuple ParameterManager::claimPrefilmParams AbortStatus ParameterManager::claimPrefilmAbort() { QMutexLocker paramLocker(¶mMutex); - changeMadeSinceCheck = false; if (validity < Valid::partprefilmulation) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else if (isClone && changeMadeSinceCheck) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else { + changeMadeSinceCheck = false; return AbortStatus::proceed; } } @@ -301,17 +308,19 @@ std::tuple ParameterManager::claimFilmParams() AbortStatus ParameterManager::claimFilmAbort() { QMutexLocker paramLocker(¶mMutex); - changeMadeSinceCheck = false; if (validity < Valid::partfilmulation) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else if (isClone && changeMadeSinceCheck) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else { + changeMadeSinceCheck = false; return AbortStatus::proceed; } } @@ -546,17 +555,19 @@ std::tuple ParameterManager::claimBlackWhite AbortStatus ParameterManager::claimBlackWhiteAbort() { QMutexLocker paramLocker(¶mMutex); - changeMadeSinceCheck = false; if (validity < Valid::partblackwhite) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else if (isClone && changeMadeSinceCheck) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else { + changeMadeSinceCheck = false; return AbortStatus::proceed; } } @@ -728,17 +739,19 @@ std::tuple ParameterManager::claimFilmli AbortStatus ParameterManager::claimFilmLikeCurvesAbort() { QMutexLocker paramLocker(¶mMutex); - changeMadeSinceCheck = false; if (validity < Valid::partfilmlikecurve) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else if (isClone && changeMadeSinceCheck) { + changeMadeSinceCheck = false; return AbortStatus::restart; } else { + changeMadeSinceCheck = false; return AbortStatus::proceed; } } @@ -1947,7 +1960,7 @@ void ParameterManager::loadParams(QString imageID) { //cout << "ParameterManager::loadParams rotation" << endl; m_rotation = temp_rotation; - validity = min(validity, Valid::filmlikecurve); + validity = min(validity, Valid::filmulation); } } @@ -1959,6 +1972,11 @@ void ParameterManager::cloneParams(ParameterManager * sourceParams) QMutexLocker paramLocker(¶mMutex);//Make all the param changes happen together. disableParamChange();//Prevent aborting of computation. + //Make sure that we always abort after any change while executing, + //even if it's later in the pipeline, + //Because we want to redo the small preview immediately. + changeMadeSinceCheck = true; + //Load the image index const QString temp_imageIndex = sourceParams->getImageIndex(); if (temp_imageIndex != imageIndex) @@ -2369,7 +2387,7 @@ void ParameterManager::cloneParams(ParameterManager * sourceParams) { //cout << "ParameterManager::cloneParams rotation" << endl; m_rotation = temp_rotation; - validity = min(validity, Valid::filmlikecurve); + validity = min(validity, Valid::filmulation); } enableParamChange();//Re-enable updating of the image. From c3076cac8f6858d4333a5980c1770f0ec8017515 Mon Sep 17 00:00:00 2001 From: CarVac Date: Sat, 28 Apr 2018 20:34:01 -0400 Subject: [PATCH 21/23] fix warning about unused var in settings --- filmulator-gui/ui/settings.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/filmulator-gui/ui/settings.cpp b/filmulator-gui/ui/settings.cpp index cf22f7ee..8d832438 100644 --- a/filmulator-gui/ui/settings.cpp +++ b/filmulator-gui/ui/settings.cpp @@ -201,7 +201,6 @@ void Settings::setMipmapView(bool mipmapViewIn) bool Settings::getMipmapView() { - const bool oldMipmapView = mipmapView; QSettings settings(QSettings::UserScope, "Filmulator", "Filmulator"); mipmapView = settings.value("edit/mipmapView", 0).toBool(); return mipmapView; From 2ada68b0129c765c99d1e5bdb2e3cccf2b7ebc56 Mon Sep 17 00:00:00 2001 From: CarVac Date: Sun, 29 Apr 2018 00:07:10 -0400 Subject: [PATCH 22/23] Check import destination directory for validity If you don't have write permissions somewhere up in the directory tree then it'll cut you off. It might not work on Windows in some circumstances. --- filmulator-gui/database/importModel.cpp | 28 ++++++++++++++++++++ filmulator-gui/database/importModel.h | 1 + filmulator-gui/qml/filmulator-gui/Import.qml | 9 ++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/filmulator-gui/database/importModel.cpp b/filmulator-gui/database/importModel.cpp index bada1df1..1942104b 100644 --- a/filmulator-gui/database/importModel.cpp +++ b/filmulator-gui/database/importModel.cpp @@ -1,9 +1,11 @@ #include "importModel.h" #include +#include using std::cout; using std::endl; +using std::max; ImportModel::ImportModel(QObject *parent) : SqlModel(parent) { @@ -79,6 +81,32 @@ bool ImportModel::pathContainsDCIM(const QString dir, const bool notDirectory) return false; } +//Check for whether a directory can be created. +//Apparently this might fail on Windows because you might have write permissions +// *in* a directory but not *to* the directory itself. +bool ImportModel::pathWritable(const QString dir) +{ + QString parentDir = dir; + while (parentDir.length() > 0) + { + QFileInfo fileInfo(parentDir); + if (fileInfo.isWritable()) + { + return true; + } + else //the dir hasn't been created yet + { + int lastIndex = max(parentDir.lastIndexOf("/"),parentDir.lastIndexOf("\\")); + if (lastIndex < 0) + { + return false; + } + parentDir.truncate(lastIndex); + } + } + return false; +} + void ImportModel::importDirectory_r(const QString dir, const bool importInPlace, const bool replaceLocation) { //This function reads in a directory and puts the raws into the database. diff --git a/filmulator-gui/database/importModel.h b/filmulator-gui/database/importModel.h index b7aad400..d51b4764 100644 --- a/filmulator-gui/database/importModel.h +++ b/filmulator-gui/database/importModel.h @@ -55,6 +55,7 @@ class ImportModel : public SqlModel public: explicit ImportModel(QObject *parent = 0); Q_INVOKABLE bool pathContainsDCIM(const QString dir, const bool notDirectory); + Q_INVOKABLE bool pathWritable(const QString dir); Q_INVOKABLE void importDirectory_r(const QString dir, const bool importInPlace, const bool replaceLocation); Q_INVOKABLE Validity importFile(const QString name, const bool importInPlace, const bool replaceLocation, const bool onlyCheck); Q_INVOKABLE void importFileList(const QString name, const bool importInPlace, const bool replaceLocation); diff --git a/filmulator-gui/qml/filmulator-gui/Import.qml b/filmulator-gui/qml/filmulator-gui/Import.qml index ce1ee245..60d3384d 100644 --- a/filmulator-gui/qml/filmulator-gui/Import.qml +++ b/filmulator-gui/qml/filmulator-gui/Import.qml @@ -243,10 +243,16 @@ Rectangle { title: qsTr("Destination Directory") tooltipText: qsTr("Select or type in the root directory of your photo file structure. If it doesn\'t exist, then it will be created.") dirDialogTitle: qsTr("Select the destination root directory") + warningTooltipText: empty ? qsTr("Choose a directory to move files to.") : qsTr("You do not have permissions to write in this directory. Please select another directory.") + erroneous: (empty || notWritable) + property bool notWritable: false + property bool empty: false enteredText: settings.getPhotoStorageDir() onEnteredTextChanged: { importModel.photoDir = enteredText settings.photoStorageDir = enteredText + notWritable = !importModel.pathWritable(enteredText) + empty = (enteredText == "") } Component.onCompleted: { importModel.photoDir = enteredText @@ -362,9 +368,10 @@ Rectangle { x: 0 * uiScale y: 0 * uiScale text: qsTr("Import") - tooltipText: qsTr("Start importing the selected file or folder. If importing is currently in progress, then the current file or folder will be imported after all current imports are complete.") + tooltipText: notDisabled ? qsTr("Start importing the selected file or folder. If importing is currently in progress, then the current file or folder will be imported after all current imports are complete.") : qsTr("Correct the errors that are highlighted above before importing.") width: parent.width height: 40 * uiScale + notDisabled: (!photoDirEntry.erroneous && (root.sourceIsFolder ? !sourceDirEntry.erroneous : !sourceFileEntry.erroneous)) onTriggered: { if (root.sourceIsFolder) { importModel.importDirectory_r(root.folderPath, root.importInPlace, root.replace) From 369f72a7be9b0488fce92470d7c7d8e3057824fa Mon Sep 17 00:00:00 2001 From: CarVac Date: Sun, 29 Apr 2018 23:05:16 -0400 Subject: [PATCH 23/23] update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 56cbbf6a..082a5a74 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,11 @@ libtiff libgomp libexiv2 libjpeg -libraw +libraw v0.18 or older Some libraw package maintainers don't include the GPL demosaic packs, so we highly encourage you to compile it yourself. -It also requires Qt 5.4: open the .pro file from Qt Creator and select Build in order to run it. You may have to initialize the build configurations upon first loading the project; I suggest you add the -j# flag to the Make build parameters to speed compilation. +It also requires Qt 5.4 or newer: open the .pro file from Qt Creator and select Build in order to run it. You may have to initialize the build configurations upon first loading the project; I suggest you add the -j# flag to the Make build parameters to speed compilation. A note: Use a standalone git client to clone the repository initially, and then you can use Qt Creator's built-in git tools. @@ -55,7 +55,7 @@ If you want the UI to appear larger on a high-pixel density display, use the Use # Status -If told to make a version number for it right now, I'd put it as 0.6.3. +If told to make a version number for it right now, I'd put it as 0.7.0. Currently, the photo editor is mostly complete, although noise reduction and sharpening are currently missing. Both the Import and Organize tabs need some UI massaging, as does the queue. Finally, the Output tab hasn't even been started yet.