Changes to black compression and saturation controls. Black compression from 0-50 acts the same as 0-100 on the previous version, compressing dark tones without crushing blacks. 50-100 then starts crushing blacks until by 100 on the slider, all tones up to the set black point are sent to zero. In the new saturation control, negative values of the slider set a linear curve rather than an inverted S curve, and smoothly decrease saturation to zero across the board.
This commit is contained in:
425
rtgui/batchqueue.cc
Normal file
425
rtgui/batchqueue.cc
Normal file
@@ -0,0 +1,425 @@
|
||||
/*
|
||||
* This file is part of RawTherapee.
|
||||
*
|
||||
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
|
||||
*
|
||||
* RawTherapee is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* RawTherapee is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with RawTherapee. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <batchqueue.h>
|
||||
#include <glibmm.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <multilangmgr.h>
|
||||
#include <filecatalog.h>
|
||||
#include <batchqueuebuttonset.h>
|
||||
#include <guiutils.h>
|
||||
#include <safegtk.h>
|
||||
|
||||
using namespace rtengine;
|
||||
|
||||
BatchQueue::BatchQueue () : processing(NULL), listener(NULL) {
|
||||
|
||||
int p = 0;
|
||||
pmenu = new Gtk::Menu ();
|
||||
pmenu->attach (*(cancel = new Gtk::MenuItem (M("FILEBROWSER_POPUPCANCELJOB"))), 0, 1, p, p+1); p++;
|
||||
pmenu->attach (*(new Gtk::SeparatorMenuItem ()), 0, 1, p, p+1); p++;
|
||||
pmenu->attach (*(head = new Gtk::MenuItem (M("FILEBROWSER_POPUPMOVEHEAD"))), 0, 1, p, p+1); p++;
|
||||
pmenu->attach (*(tail = new Gtk::MenuItem (M("FILEBROWSER_POPUPMOVEEND"))), 0, 1, p, p+1); p++;
|
||||
pmenu->attach (*(new Gtk::SeparatorMenuItem ()), 0, 1, p, p+1); p++;
|
||||
pmenu->attach (*(selall = new Gtk::MenuItem (M("FILEBROWSER_POPUPSELECTALL"))), 0, 1, p, p+1); p++;
|
||||
pmenu->show_all ();
|
||||
|
||||
cancel->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &BatchQueue::cancelItems), &selected));
|
||||
head->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &BatchQueue::headItems), &selected));
|
||||
tail->signal_activate().connect (sigc::bind(sigc::mem_fun(*this, &BatchQueue::tailItems), &selected));
|
||||
selall->signal_activate().connect (sigc::mem_fun(*this, &BatchQueue::selectAll));
|
||||
}
|
||||
|
||||
void BatchQueue::rightClicked (ThumbBrowserEntryBase* entry) {
|
||||
|
||||
pmenu->popup (3, 0);
|
||||
}
|
||||
|
||||
void BatchQueue::addEntry (BatchQueueEntry* entry, bool head) {
|
||||
|
||||
entry->setParent (this);
|
||||
entry->resize (options.thumbSize);
|
||||
|
||||
entry->selected = false;
|
||||
if (!head)
|
||||
fd.push_back (entry);
|
||||
else {
|
||||
std::vector<ThumbBrowserEntryBase*>::iterator pos;
|
||||
for (pos=fd.begin(); pos!=fd.end(); pos++)
|
||||
if (!(*pos)->processing) {
|
||||
fd.insert (pos, entry);
|
||||
break;
|
||||
}
|
||||
if (pos==fd.end())
|
||||
fd.push_back (entry);
|
||||
}
|
||||
|
||||
if (entry->thumbnail)
|
||||
entry->thumbnail->imageEnqueued ();
|
||||
|
||||
BatchQueueButtonSet* bqbs = new BatchQueueButtonSet (entry);
|
||||
bqbs->setButtonListener (this);
|
||||
entry->addButtonSet (bqbs);
|
||||
|
||||
arrangeFiles ();
|
||||
queue_draw ();
|
||||
notifyListener ();
|
||||
}
|
||||
|
||||
int deleteitem (void* data) {
|
||||
|
||||
gdk_threads_enter ();
|
||||
delete (BatchQueueEntry*)data;
|
||||
gdk_threads_leave ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BatchQueue::cancelItems (std::vector<ThumbBrowserEntryBase*>* items) {
|
||||
|
||||
for (int i=0; i<items->size(); i++) {
|
||||
BatchQueueEntry* entry = (BatchQueueEntry*)(*items)[i];
|
||||
if (entry->processing)
|
||||
continue;
|
||||
std::vector<ThumbBrowserEntryBase*>::iterator pos = std::find (fd.begin(), fd.end(), entry);
|
||||
if (pos!=fd.end()) {
|
||||
fd.erase (pos);
|
||||
rtengine::ProcessingJob::destroy (entry->job);
|
||||
if (entry->thumbnail)
|
||||
entry->thumbnail->imageRemovedFromQueue ();
|
||||
g_idle_add (deleteitem, entry);
|
||||
}
|
||||
}
|
||||
for (int i=0; i<fd.size(); i++)
|
||||
fd[i]->selected = false;
|
||||
lastClicked = NULL;
|
||||
selected.clear ();
|
||||
redraw ();
|
||||
notifyListener ();
|
||||
}
|
||||
|
||||
void BatchQueue::headItems (std::vector<ThumbBrowserEntryBase*>* items) {
|
||||
|
||||
for (int i=items->size()-1; i>=0; i--) {
|
||||
BatchQueueEntry* entry = (BatchQueueEntry*)(*items)[i];
|
||||
if (entry->processing)
|
||||
continue;
|
||||
std::vector<ThumbBrowserEntryBase*>::iterator pos = std::find (fd.begin(), fd.end(), entry);
|
||||
if (pos!=fd.end() && pos!=fd.begin()) {
|
||||
fd.erase (pos);
|
||||
// find the first item that is not under processing
|
||||
for (pos=fd.begin(); pos!=fd.end(); pos++)
|
||||
if (!(*pos)->processing) {
|
||||
fd.insert (pos, entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
redraw ();
|
||||
}
|
||||
|
||||
void BatchQueue::tailItems (std::vector<ThumbBrowserEntryBase*>* items) {
|
||||
|
||||
for (int i=0; i<items->size(); i++) {
|
||||
BatchQueueEntry* entry = (BatchQueueEntry*)(*items)[i];
|
||||
if (entry->processing)
|
||||
continue;
|
||||
std::vector<ThumbBrowserEntryBase*>::iterator pos = std::find (fd.begin(), fd.end(), entry);
|
||||
if (pos!=fd.end()) {
|
||||
fd.erase (pos);
|
||||
fd.push_back (entry);
|
||||
}
|
||||
}
|
||||
redraw ();
|
||||
}
|
||||
|
||||
void BatchQueue::selectAll () {
|
||||
|
||||
lastClicked = NULL;
|
||||
selected.clear ();
|
||||
for (int i=0; i<fd.size(); i++) {
|
||||
if (fd[i]->processing)
|
||||
continue;
|
||||
fd[i]->selected = true;
|
||||
selected.push_back (fd[i]);
|
||||
}
|
||||
queue_draw ();
|
||||
}
|
||||
void BatchQueue::startProcessing () {
|
||||
|
||||
if (!processing && fd.size()>0) {
|
||||
BatchQueueEntry* next = (BatchQueueEntry*)fd[0];
|
||||
// tag it as processing
|
||||
next->processing = true;
|
||||
processing = next;
|
||||
// remove from selection
|
||||
if (processing->selected) {
|
||||
std::vector<ThumbBrowserEntryBase*>::iterator pos = std::find (selected.begin(), selected.end(), processing);
|
||||
if (pos!=selected.end())
|
||||
selected.erase (pos);
|
||||
processing->selected = false;
|
||||
}
|
||||
// remove button set
|
||||
next->removeButtonSet ();
|
||||
// start batch processing
|
||||
rtengine::startBatchProcessing (next->job, this);
|
||||
queue_draw ();
|
||||
}
|
||||
}
|
||||
|
||||
rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImage16* img) {
|
||||
|
||||
gdk_threads_enter ();
|
||||
// save image img
|
||||
Glib::ustring fname;
|
||||
SaveFormat saveFormat;
|
||||
if (processing->outFileName=="") { // auto file name
|
||||
fname = obtainFileName (processing->filename);
|
||||
saveFormat = options.saveFormat;
|
||||
}
|
||||
else { // use the save-as filename with automatic completion for uniqueness
|
||||
fname = autoCompleteFileName (removeExtension(processing->outFileName), getExtension(processing->outFileName));
|
||||
saveFormat = processing->saveFormat;
|
||||
}
|
||||
printf ("fname=%s, %s\n", fname.c_str(), removeExtension(fname).c_str());
|
||||
if (img && fname!="") {
|
||||
int err = 0;
|
||||
if (saveFormat.format=="tif")
|
||||
err = img->saveAsTIFF (fname, saveFormat.tiffBits,saveFormat.tiffUncompressed);
|
||||
else if (saveFormat.format=="png")
|
||||
err = img->saveAsPNG (fname, saveFormat.pngCompression, saveFormat.pngBits);
|
||||
else if (saveFormat.format=="jpg")
|
||||
err = img->saveAsJPEG (fname, saveFormat.jpegQuality);
|
||||
img->free ();
|
||||
if (!err && saveFormat.saveParams)
|
||||
// We keep the extension to avoid overwriting the profile when we have
|
||||
// the same output filename with different extension
|
||||
//processing->params.save (removeExtension(fname) + paramFileExtension);
|
||||
processing->params.save (fname + paramFileExtension);
|
||||
else {
|
||||
printf("Unable to process or save %s\n", fname.c_str());
|
||||
}
|
||||
if (processing->thumbnail) {
|
||||
processing->thumbnail->imageDeveloped ();
|
||||
processing->thumbnail->imageRemovedFromQueue ();
|
||||
if (listener)
|
||||
listener->imageProcessingReady (processing->filename);
|
||||
}
|
||||
}
|
||||
|
||||
// delete from the queue
|
||||
delete processing;
|
||||
processing = NULL;
|
||||
fd.erase (fd.begin());
|
||||
// return next job
|
||||
if (fd.size()==0) {
|
||||
if (listener)
|
||||
listener->queueEmpty ();
|
||||
}
|
||||
else if (listener && listener->canStartNext ()) {
|
||||
BatchQueueEntry* next = (BatchQueueEntry*)fd[0];
|
||||
// tag it as selected
|
||||
next->processing = true;
|
||||
processing = next;
|
||||
// remove from selection
|
||||
if (processing->selected) {
|
||||
std::vector<ThumbBrowserEntryBase*>::iterator pos = std::find (selected.begin(), selected.end(), processing);
|
||||
if (pos!=selected.end())
|
||||
selected.erase (pos);
|
||||
processing->selected = false;
|
||||
}
|
||||
// remove button set
|
||||
next->removeButtonSet ();
|
||||
}
|
||||
redraw ();
|
||||
notifyListener ();
|
||||
gdk_threads_leave ();
|
||||
return processing ? processing->job : NULL;
|
||||
}
|
||||
|
||||
Glib::ustring BatchQueue::obtainFileName (const Glib::ustring& origFileName) {
|
||||
|
||||
std::vector<Glib::ustring> pa;
|
||||
std::vector<Glib::ustring> da;
|
||||
|
||||
for (int i=0; i<origFileName.size(); i++) {
|
||||
while ((i<origFileName.size()) && (origFileName[i]=='\\' || origFileName[i]=='/'))
|
||||
i++;
|
||||
if (i>=origFileName.size())
|
||||
break;
|
||||
Glib::ustring tok = "";
|
||||
while ((i<origFileName.size()) && !(origFileName[i]=='\\' || origFileName[i]=='/'))
|
||||
tok = tok + origFileName[i++];
|
||||
da.push_back (tok);
|
||||
}
|
||||
|
||||
if (origFileName[0]=='/' || origFileName[0]=='\\')
|
||||
pa.push_back ("/" + da[0]);
|
||||
else
|
||||
pa.push_back (da[0]);
|
||||
|
||||
for (int i=1; i<da.size(); i++)
|
||||
pa.push_back (pa[i-1] + "/" + da[i]);
|
||||
|
||||
// for (int i=0; i<da.size(); i++)
|
||||
// printf ("da: %s\n", da[i].c_str());
|
||||
// for (int i=0; i<pa.size(); i++)
|
||||
// printf ("pa: %s\n", pa[i].c_str());
|
||||
|
||||
// extracting filebase
|
||||
Glib::ustring filename;
|
||||
|
||||
int extpos = origFileName.size()-1;
|
||||
for (; extpos>=0 && origFileName[extpos]!='.'; extpos--);
|
||||
for (int k=extpos-1; k>=0 && origFileName[k]!='/' && origFileName[k]!='\\'; k--)
|
||||
filename = origFileName[k] + filename;
|
||||
|
||||
// printf ("%d, |%s|\n", extpos, filename.c_str());
|
||||
|
||||
// constructing full output path
|
||||
// printf ("path=|%s|\n", options.savePath.c_str());
|
||||
|
||||
Glib::ustring path="";
|
||||
if (options.saveUsePathTemplate) {
|
||||
int ix=0;
|
||||
while (options.savePathTemplate[ix]!=0) {
|
||||
if (options.savePathTemplate[ix]=='%') {
|
||||
ix++;
|
||||
if (options.savePathTemplate[ix]=='p') {
|
||||
ix++;
|
||||
int i = options.savePathTemplate[ix]-'0';
|
||||
if (i<pa.size())
|
||||
path = path + pa[pa.size()-i-1] + '/';
|
||||
ix++;
|
||||
}
|
||||
else if (options.savePathTemplate[ix]=='d') {
|
||||
ix++;
|
||||
int i = options.savePathTemplate[ix]-'0';
|
||||
if (i<da.size())
|
||||
path = path + da[da.size()-i-1] + '/';
|
||||
ix++;
|
||||
}
|
||||
else if (options.savePathTemplate[ix]=='f') {
|
||||
path = path + filename;
|
||||
}
|
||||
}
|
||||
else
|
||||
path = path + options.savePathTemplate[ix];
|
||||
ix++;
|
||||
}
|
||||
}
|
||||
else
|
||||
path = Glib::build_filename (options.savePathFolder, filename);
|
||||
|
||||
return autoCompleteFileName (path, options.saveFormat.format);
|
||||
}
|
||||
|
||||
Glib::ustring BatchQueue::autoCompleteFileName (const Glib::ustring& fileName, const Glib::ustring& format) {
|
||||
|
||||
// separate filename and the path to the destination directory
|
||||
Glib::ustring dstdir = Glib::path_get_dirname (fileName);
|
||||
Glib::ustring dstfname = Glib::path_get_basename (fileName);
|
||||
|
||||
// create directory, if does not exist
|
||||
if (g_mkdir_with_parents (dstdir.c_str(), 0755) )
|
||||
return "";
|
||||
|
||||
// In overwrite mode we TRY to delete the old file first.
|
||||
// if that's not possible (e.g. locked by viewer, R/O), we revert to the standard naming scheme
|
||||
bool inOverwriteMode=options.overwriteOutputFile;
|
||||
|
||||
for (int tries=0; tries<100; tries++) {
|
||||
Glib::ustring fname;
|
||||
if (tries==0)
|
||||
fname = Glib::ustring::compose ("%1.%2", Glib::build_filename (dstdir, dstfname), format);
|
||||
else
|
||||
fname = Glib::ustring::compose ("%1-%2.%3", Glib::build_filename (dstdir, dstfname), tries, format);
|
||||
|
||||
int fileExists=Glib::file_test (fname, Glib::FILE_TEST_EXISTS);
|
||||
|
||||
if (inOverwriteMode && fileExists) {
|
||||
// do NOT use g_remove as it has compiler problems on Unix and OSX (GTK namespace problem)
|
||||
if (::remove(safe_locale_from_utf8(fname).c_str ()) == -1)
|
||||
inOverwriteMode = false; // failed to delete- revert to old naming scheme
|
||||
else
|
||||
fileExists = false; // deleted now
|
||||
}
|
||||
|
||||
if (!fileExists) {
|
||||
return fname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int bqredraw (void* p) {
|
||||
|
||||
gdk_threads_enter ();
|
||||
((BatchQueue*)p)->redraw();
|
||||
gdk_threads_leave ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BatchQueue::setProgress (double p) {
|
||||
|
||||
if (processing)
|
||||
processing->progress = p;
|
||||
|
||||
g_idle_add (bqredraw, this);
|
||||
}
|
||||
|
||||
void BatchQueue::buttonPressed (LWButton* button, int actionCode, void* actionData) {
|
||||
|
||||
std::vector<ThumbBrowserEntryBase*> bqe;
|
||||
bqe.push_back ((BatchQueueEntry*)actionData);
|
||||
|
||||
if (actionCode==10) // cancel
|
||||
cancelItems (&bqe);
|
||||
else if (actionCode==8) // to head
|
||||
headItems (&bqe);
|
||||
else if (actionCode==9) // to tail
|
||||
tailItems (&bqe);
|
||||
}
|
||||
|
||||
struct NLParams {
|
||||
BatchQueueListener* listener;
|
||||
int qsize;
|
||||
};
|
||||
|
||||
int bqnotifylistener (void* data) {
|
||||
|
||||
gdk_threads_enter ();
|
||||
NLParams* params = (NLParams*)data;
|
||||
params->listener->queueSizeChanged (params->qsize);
|
||||
delete params;
|
||||
gdk_threads_leave ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BatchQueue::notifyListener () {
|
||||
|
||||
if (listener) {
|
||||
NLParams* params = new NLParams;
|
||||
params->listener = listener;
|
||||
params->qsize = fd.size();
|
||||
g_idle_add (bqnotifylistener, params);
|
||||
}
|
||||
}
|
||||
|
||||
void BatchQueue::redrawNeeded (LWButton* button) {
|
||||
|
||||
queue_draw ();
|
||||
}
|
Reference in New Issue
Block a user