From 824ecaed41dd6c02359180438627d261a7a4f5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=B6ssie?= Date: Thu, 9 Feb 2017 20:25:58 +0100 Subject: [PATCH 01/16] Add `IdleRegister` to deal with destruction while idle func is queued This adds a little helper class to `guiutils.*` that unregisters in-flight idle functions queued by `IdleRegister::add()`. It's best to call `IdleRegister::destroy()` in the destructor of the class owning the `IdleRegister` instance. Otherwise make sure, it is the last member which will be deleted first. `Resize` now makes use of this new facility in `setDimensions()`, which also fixes #3673. --- rtgui/guiutils.cc | 47 ++++++++++++++++++ rtgui/guiutils.h | 32 ++++++++++--- rtgui/resize.cc | 118 +++++++++++++++++++++++++--------------------- rtgui/resize.h | 36 +++++++------- 4 files changed, 155 insertions(+), 78 deletions(-) diff --git a/rtgui/guiutils.cc b/rtgui/guiutils.cc index 43bfc11b0..e8310ff0e 100644 --- a/rtgui/guiutils.cc +++ b/rtgui/guiutils.cc @@ -43,6 +43,53 @@ guint add_idle (GSourceFunc function, gpointer data) //gtk_main_iteration_do(false); } +IdleRegister::~IdleRegister() +{ + destroy(); +} + +void IdleRegister::add(GSourceFunc function, gpointer data) +{ + struct DataWrapper { + IdleRegister* const self; + GSourceFunc function; + gpointer data; + }; + + const auto dispatch = [](gpointer data) -> gboolean { + DataWrapper* const data_wrapper = static_cast(data); + + if (!data_wrapper->function(data_wrapper->data)) { + data_wrapper->self->mutex.lock(); + data_wrapper->self->ids.erase(data_wrapper); + data_wrapper->self->mutex.unlock(); + + delete data_wrapper; + return FALSE; + } + + return TRUE; + }; + + DataWrapper* const data_wrapper = new DataWrapper{ + this, + function, + data + }; + + mutex.lock(); + ids[data_wrapper] = add_idle(dispatch, data_wrapper); + mutex.unlock(); +} + +void IdleRegister::destroy() +{ + mutex.lock(); + for (const auto id : ids) { + g_source_remove(id.second); + } + mutex.unlock(); +} /* gboolean giveMeAGo(void* data) { diff --git a/rtgui/guiutils.h b/rtgui/guiutils.h index e5919c9e4..422a99a45 100644 --- a/rtgui/guiutils.h +++ b/rtgui/guiutils.h @@ -19,13 +19,19 @@ #ifndef __GUI_UTILS_ #define __GUI_UTILS_ -#include -#include -#include "../rtengine/rtengine.h" -#include "../rtengine/coord.h" -#include "rtimage.h" -#include #include +#include +#include + +#include + +#include + +#include "../rtengine/coord.h" +#include "../rtengine/noncopyable.h" +#include "../rtengine/rtengine.h" + +#include "rtimage.h" Glib::ustring escapeHtmlChars(const Glib::ustring &src); bool removeIfThere (Gtk::Container* cont, Gtk::Widget* w, bool increference = true); @@ -40,6 +46,20 @@ void setExpandAlignProperties(Gtk::Widget *widget, bool hExpand, bool vExpand, e guint add_idle (GSourceFunc function, gpointer data); +class IdleRegister final : + public rtengine::NonCopyable +{ +public: + ~IdleRegister(); + + void add(GSourceFunc function, gpointer data); + void destroy(); + +private: + std::map ids; + MyMutex mutex; +}; + // TODO: The documentation says gdk_threads_enter and gdk_threads_leave should be replaced // by g_main_context_invoke(), g_idle_add() and related functions, but this will require more extensive changes. // We silence those warnings until then so that we notice the others. diff --git a/rtgui/resize.cc b/rtgui/resize.cc index 0504a3934..bf2d4ce67 100644 --- a/rtgui/resize.cc +++ b/rtgui/resize.cc @@ -118,7 +118,7 @@ Resize::Resize () : FoldableToolPanel(this, "resize", M("TP_RESIZE_LABEL"), fals Resize::~Resize () { - + idle_register.destroy(); delete scale; delete sizeBox; } @@ -352,68 +352,76 @@ void Resize::sizeChanged (int mw, int mh, int ow, int oh) void Resize::setDimensions () { + const auto func = [](gpointer data) -> gboolean { + Resize* const self = static_cast(data); - int refw, refh; + self->wconn.block(true); + self->hconn.block(true); + self->scale->block(true); - wconn.block (true); - hconn.block (true); - scale->block(true); + int refw, refh; - if (appliesTo->get_active_row_number() == 0 && cropw) { - // Applies to Cropped area - refw = cropw; - refh = croph; - } else { - // Applies to Full image or crop is disabled - refw = maxw; - refh = maxh; - } - - GThreadLock lock; - w->set_range (32, 4 * refw); - h->set_range (32, 4 * refh); - - double tmpScale; - - switch (spec->get_active_row_number()) { - case (0): // Scale mode - w->set_value((double)((int)( (double)(refw) * scale->getValue() + 0.5) )); - h->set_value((double)((int)( (double)(refh) * scale->getValue() + 0.5) )); - break; - - case (1): // Width mode - tmpScale = w->get_value() / (double)refw; - scale->setValue (tmpScale); - h->set_value((double)((int)( (double)(refh) * tmpScale + 0.5) )); - break; - - case (2): // Height mode - tmpScale = h->get_value() / (double)refh; - scale->setValue (tmpScale); - w->set_value((double)((int)( (double)(refw) * tmpScale + 0.5) )); - break; - - case (3): { // Bounding box mode - double wSliderValue = w->get_value(); - double hSliderValue = h->get_value(); - - if ( (wSliderValue / hSliderValue) < ((double)refw / (double)refh)) { - tmpScale = wSliderValue / (double)refw; + if (self->appliesTo->get_active_row_number() == 0 && self->cropw) { + // Applies to Cropped area + refw = self->cropw; + refh = self->croph; } else { - tmpScale = hSliderValue / (double)refh; + // Applies to Full image or crop is disabled + refw = self->maxw; + refh = self->maxh; } - scale->setValue (tmpScale); - break; - } + self->w->set_range(32, 4 * refw); + self->h->set_range(32, 4 * refh); - default: - break; - } + switch (self->spec->get_active_row_number()) { + case 0: { + // Scale mode + self->w->set_value(static_cast(static_cast(static_cast(refw) * self->scale->getValue() + 0.5))); + self->h->set_value(static_cast(static_cast(static_cast(refh) * self->scale->getValue() + 0.5))); + break; + } - scale->block(false); - wconn.block (false); - hconn.block (false); + case 1: { + // Width mode + const double tmp_scale = self->w->get_value() / static_cast(refw); + self->scale->setValue(tmp_scale); + self->h->set_value(static_cast(static_cast(static_cast(refh) * tmp_scale + 0.5))); + break; + } + + case 2: { + // Height mode + const double tmp_scale = self->h->get_value() / static_cast(refh); + self->scale->setValue(tmp_scale); + self->w->set_value(static_cast(static_cast(static_cast(refw) * tmp_scale + 0.5))); + break; + } + + case 3: { + // Bounding box mode + const double tmp_scale = + self->w->get_value() / self->h->get_value() < static_cast(refw) / static_cast(refh) + ? self->w->get_value() / static_cast(refw) + : self->h->get_value() / static_cast(refh); + + self->scale->setValue(tmp_scale); + break; + } + + default: { + break; + } + } + + self->scale->block(false); + self->wconn.block(false); + self->hconn.block(false); + + return FALSE; + }; + + idle_register.add(func, this); } void Resize::fitBoxScale() diff --git a/rtgui/resize.h b/rtgui/resize.h index cf640c281..acba5b478 100644 --- a/rtgui/resize.h +++ b/rtgui/resize.h @@ -25,25 +25,13 @@ #include "toolpanel.h" #include "guiutils.h" -class Resize : public ToolParamBlock, public AdjusterListener, public FoldableToolPanel, public rtengine::SizeListener +class Resize final : + public ToolParamBlock, + public AdjusterListener, + public FoldableToolPanel, + public rtengine::SizeListener { - -protected: - Adjuster* scale; - Gtk::VBox* sizeBox; - MyComboBoxText* appliesTo; - MyComboBoxText* method; - MyComboBoxText* spec; - MySpinButton* w; - MySpinButton* h; - int maxw, maxh; - int cropw, croph; - sigc::connection sconn, aconn, wconn, hconn; - bool wDirty, hDirty; - ToolParamBlock* packBox; - public: - Resize (); ~Resize (); @@ -75,6 +63,20 @@ private: int getComputedHeight (); void notifyBBox (); void updateGUI (); + + Adjuster* scale; + Gtk::VBox* sizeBox; + MyComboBoxText* appliesTo; + MyComboBoxText* method; + MyComboBoxText* spec; + MySpinButton* w; + MySpinButton* h; + int maxw, maxh; + int cropw, croph; + sigc::connection sconn, aconn, wconn, hconn; + bool wDirty, hDirty; + ToolParamBlock* packBox; + IdleRegister idle_register; }; #endif From f9bd9956c0a5c0a341a8924b1cc6704c46345074 Mon Sep 17 00:00:00 2001 From: heckflosse Date: Fri, 10 Feb 2017 01:29:02 +0100 Subject: [PATCH 02/16] Removed clang related restriction. Should give a speedup for some parts of rt (denoise for example) when clang is used to build rt --- rtengine/opthelper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtengine/opthelper.h b/rtengine/opthelper.h index bee97f6b2..4b34fc58f 100644 --- a/rtengine/opthelper.h +++ b/rtengine/opthelper.h @@ -69,7 +69,7 @@ #define ALIGNED64 #define ALIGNED16 #endif - #if !defined(__clang__) && defined _OPENMP + #if defined _OPENMP #define _RT_NESTED_OPENMP #endif #endif From 5202f451376b69924872eec54bd19c34558bb254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=B6ssie?= Date: Fri, 10 Feb 2017 20:06:35 +0100 Subject: [PATCH 03/16] Protect all `Crop` accesses with `cropMutex` (fixes #3306) --- rtengine/dcrop.cc | 51 ++++++++++++++++++++++++++++++++++++++--------- rtengine/dcrop.h | 43 ++++++++------------------------------- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index a90c78069..bf4d1b194 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -22,8 +22,18 @@ #include "mytime.h" #include "refreshmap.h" #include "rt_math.h" -// "ceil" rounding -#define SKIPS(a,b) ((a) / (b) + ((a) % (b) > 0)) + +namespace +{ + + // "ceil" rounding + template + constexpr T skips(T a, T b) + { + return a / b + static_cast(a % b); + } + +} namespace rtengine { @@ -33,7 +43,7 @@ extern const Settings* settings; Crop::Crop (ImProcCoordinator* parent, EditDataProvider *editDataProvider, bool isDetailWindow) : PipetteBuffer(editDataProvider), origCrop(nullptr), laboCrop(nullptr), labnCrop(nullptr), cropImg(nullptr), cbuf_real(nullptr), cshmap(nullptr), transCrop(nullptr), cieCrop(nullptr), cbuffer(nullptr), - updating(false), newUpdatePending(false), skip(10), padding(0), + updating(false), newUpdatePending(false), skip(10), cropx(0), cropy(0), cropw(-1), croph(-1), trafx(0), trafy(0), trafw(-1), trafh(-1), rqcropx(0), rqcropy(0), rqcropw(-1), rqcroph(-1), @@ -111,6 +121,12 @@ void Crop::setEditSubscriber(EditSubscriber* newSubscriber) // If oldSubscriber == NULL && newSubscriber != NULL && newSubscriber->getEditingType() == ET_PIPETTE-> the image will be allocated when necessary } +bool Crop::hasListener() +{ + MyMutex::MyLock cropLock(cropMutex); + return cropImageListener; +} + void Crop::update (int todo) { MyMutex::MyLock cropLock(cropMutex); @@ -684,7 +700,7 @@ void Crop::update (int todo) } if (needstransform) - parent->ipf.transform (baseCrop, transCrop, cropx / skip, cropy / skip, trafx / skip, trafy / skip, SKIPS(parent->fw, skip), SKIPS(parent->fh, skip), parent->getFullWidth(), parent->getFullHeight(), + parent->ipf.transform (baseCrop, transCrop, cropx / skip, cropy / skip, trafx / skip, trafy / skip, skips(parent->fw, skip), skips(parent->fh, skip), parent->getFullWidth(), parent->getFullHeight(), parent->imgsrc->getMetaData()->getFocalLen(), parent->imgsrc->getMetaData()->getFocalLen35mm(), parent->imgsrc->getMetaData()->getFocusDist(), parent->imgsrc->getRotateDegree(), false); else @@ -714,7 +730,7 @@ void Crop::update (int todo) // blurmap for shadow & highlights if ((todo & M_BLURMAP) && params.sh.enabled) { - double radius = sqrt (double(SKIPS(parent->fw, skip) * SKIPS(parent->fw, skip) + SKIPS(parent->fh, skip) * SKIPS(parent->fh, skip))) / 2.0; + double radius = sqrt (double(skips(parent->fw, skip) * skips(parent->fw, skip) + skips(parent->fh, skip) * skips(parent->fh, skip))) / 2.0; double shradius = params.sh.radius; if (!params.sh.hq) { @@ -1114,11 +1130,11 @@ bool Crop::setCropSizes (int rcx, int rcy, int rcw, int rch, int skip, bool inte int orW, orH; parent->imgsrc->getSize (cp, orW, orH); - int cw = SKIPS(bw, skip); - int ch = SKIPS(bh, skip); + int cw = skips(bw, skip); + int ch = skips(bh, skip); - leftBorder = SKIPS(rqx1 - bx1, skip); - upperBorder = SKIPS(rqy1 - by1, skip); + leftBorder = skips(rqx1 - bx1, skip); + upperBorder = skips(rqy1 - by1, skip); if (settings->verbose) { printf ("setsizes starts (%d, %d, %d, %d, %d, %d)\n", orW, orH, trafw, trafh, cw, ch); @@ -1286,5 +1302,22 @@ void Crop::fullUpdate () parent->updaterThreadStart.unlock (); } +int Crop::get_skip() +{ + MyMutex::MyLock lock(cropMutex); + return skip; } +int Crop::getLeftBorder() +{ + MyMutex::MyLock lock(cropMutex); + return leftBorder; +} + +int Crop::getUpperBorder() +{ + MyMutex::MyLock lock(cropMutex); + return upperBorder; +} + +} diff --git a/rtengine/dcrop.h b/rtengine/dcrop.h index 450c659f0..fcdfcf001 100644 --- a/rtengine/dcrop.h +++ b/rtengine/dcrop.h @@ -16,8 +16,7 @@ * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ -#ifndef _CROP_H_ -#define _CROP_H_ +#pragma once #include "improccoordinator.h" #include "rtengine.h" @@ -57,19 +56,18 @@ protected: bool updating; /// Flag telling if an updater thread is currently processing bool newUpdatePending; /// Flag telling the updater thread that a new update is pending int skip; - int padding; /// Minimum space allowed around image in the display area int cropx, cropy, cropw, croph; /// size of the detail crop image ('skip' taken into account), with border int trafx, trafy, trafw, trafh; /// the size and position to get from the imagesource that is transformed to the requested crop area int rqcropx, rqcropy, rqcropw, rqcroph; /// size of the requested detail crop image (the image might be smaller) (without border) - int borderRequested; /// requested extra border size for image processing + const int borderRequested; /// requested extra border size for image processing int upperBorder, leftBorder; /// extra border size really allocated for image processing bool cropAllocated; DetailedCropListener* cropImageListener; MyMutex cropMutex; - ImProcCoordinator* parent; - bool isDetailWindow; + ImProcCoordinator* const parent; + const bool isDetailWindow; EditUniqueID getCurrEditID(); bool setCropSizes (int cropX, int cropY, int cropW, int cropH, int skip, bool internal); void freeAll (); @@ -78,19 +76,8 @@ public: Crop (ImProcCoordinator* parent, EditDataProvider *editDataProvider, bool isDetailWindow); virtual ~Crop (); - void mLock () - { - cropMutex.lock(); - } - void mUnlock () - { - cropMutex.lock(); - } void setEditSubscriber(EditSubscriber* newSubscriber); - bool hasListener () - { - return cropImageListener; - } + bool hasListener(); void update (int todo); void setWindow (int cropX, int cropY, int cropW, int cropH, int skip) { @@ -106,22 +93,8 @@ public: void setListener (DetailedCropListener* il); void destroy (); - int get_skip () - { - return skip; - } - int getPadding () - { - return padding; - } - int getLeftBorder () - { - return leftBorder; - } - int getUpperBorder () - { - return upperBorder; - } + int get_skip(); + int getLeftBorder(); + int getUpperBorder(); }; } -#endif From 242b7a86cf4700de4356f113f58dc157eb58edbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=B6ssie?= Date: Fri, 10 Feb 2017 20:20:48 +0100 Subject: [PATCH 04/16] Fix FreeBSD compile error (fixes #3566) --- rtgui/cachemanager.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtgui/cachemanager.cc b/rtgui/cachemanager.cc index 7421711eb..c0cc3e30f 100644 --- a/rtgui/cachemanager.cc +++ b/rtgui/cachemanager.cc @@ -35,8 +35,8 @@ namespace { -constexpr auto cacheDirMode = 511; -constexpr auto cacheDirs = { "profiles", "images", "aehistograms", "embprofiles", "data" }; +constexpr int cacheDirMode = 0777; +constexpr const char* cacheDirs[] = { "profiles", "images", "aehistograms", "embprofiles", "data" }; } From 79ff7f5997c2f0f99dfb01b2e22fe0bb14fba46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=B6ssie?= Date: Fri, 10 Feb 2017 21:22:42 +0100 Subject: [PATCH 05/16] Replace all `add_idle()`s with `IdleRegister` --- rtgui/batchqueue.cc | 6 +- rtgui/batchqueue.h | 59 +++++++------ rtgui/blackwhite.cc | 16 ++-- rtgui/blackwhite.h | 108 ++++++++++++----------- rtgui/cachemanager.cc | 1 + rtgui/colorappearance.cc | 30 ++++--- rtgui/colorappearance.h | 103 +++++++++++----------- rtgui/colortoning.cc | 17 ++-- rtgui/colortoning.h | 67 ++++++++------- rtgui/crop.cc | 113 ++++++++++++------------ rtgui/crop.h | 68 ++++++++------- rtgui/crophandler.cc | 172 ++++++++++++++++++------------------- rtgui/crophandler.h | 87 ++++++++++--------- rtgui/dirbrowser.cc | 1 + rtgui/dirpyrdenoise.cc | 49 ++++++----- rtgui/dirpyrdenoise.h | 97 +++++++++++---------- rtgui/editorpanel.cc | 77 +++++++++-------- rtgui/editorpanel.h | 179 ++++++++++++++++++++------------------- rtgui/filepanel.cc | 16 ++-- rtgui/filepanel.h | 58 ++++++------- rtgui/guiutils.h | 4 +- 21 files changed, 691 insertions(+), 637 deletions(-) diff --git a/rtgui/batchqueue.cc b/rtgui/batchqueue.cc index fbe2be4c6..7bd53c444 100644 --- a/rtgui/batchqueue.cc +++ b/rtgui/batchqueue.cc @@ -85,6 +85,8 @@ BatchQueue::BatchQueue (FileCatalog* aFileCatalog) : processing(nullptr), fileCa BatchQueue::~BatchQueue () { + idle_register.destroy(); + MYWRITERLOCK(l, entryRW); // The listener merges parameters with old values, so delete afterwards @@ -937,7 +939,7 @@ void BatchQueue::notifyListener (bool queueEmptied) } params->queueEmptied = queueEmptied; params->queueError = false; - add_idle (bqnotifylistenerUI, params); + idle_register.add(bqnotifylistenerUI, params); } } @@ -967,6 +969,6 @@ void BatchQueue::error (Glib::ustring msg) params->queueEmptied = false; params->queueError = true; params->queueErrorMessage = msg; - add_idle (bqnotifylistenerUI, params); + idle_register.add(bqnotifylistenerUI, params); } } diff --git a/rtgui/batchqueue.h b/rtgui/batchqueue.h index f7376680b..68373838e 100644 --- a/rtgui/batchqueue.h +++ b/rtgui/batchqueue.h @@ -36,37 +36,12 @@ public: }; class FileCatalog; -class BatchQueue : public ThumbBrowserBase, + +class BatchQueue final : + public ThumbBrowserBase, public rtengine::BatchProcessingListener, public LWButtonListener { - -protected: - int getMaxThumbnailHeight() const; - void saveThumbnailHeight (int height); - int getThumbnailHeight (); - - BatchQueueEntry* processing; // holds the currently processed image - FileCatalog* fileCatalog; - int sequence; // holds the current sequence index - - Glib::ustring nameTemplate; - - MyImageMenuItem* cancel; - MyImageMenuItem* head; - MyImageMenuItem* tail; - Gtk::MenuItem* selall; - Gtk::MenuItem* open; - Glib::RefPtr pmaccelgroup; - Gtk::Menu pmenu; - - BatchQueueListener* listener; - - Glib::ustring autoCompleteFileName (const Glib::ustring& fileName, const Glib::ustring& format); - Glib::ustring getTempFilenameForParams( const Glib::ustring &filename ); - bool saveBatchQueue (); - void notifyListener (bool queueEmptied); - public: explicit BatchQueue (FileCatalog* aFileCatalog); ~BatchQueue (); @@ -106,6 +81,34 @@ public: static Glib::ustring calcAutoFileNameBase (const Glib::ustring& origFileName, int sequence = 0); static int calcMaxThumbnailHeight(); + +protected: + int getMaxThumbnailHeight() const; + void saveThumbnailHeight (int height); + int getThumbnailHeight (); + + Glib::ustring autoCompleteFileName (const Glib::ustring& fileName, const Glib::ustring& format); + Glib::ustring getTempFilenameForParams( const Glib::ustring &filename ); + bool saveBatchQueue (); + void notifyListener (bool queueEmptied); + + BatchQueueEntry* processing; // holds the currently processed image + FileCatalog* fileCatalog; + int sequence; // holds the current sequence index + + Glib::ustring nameTemplate; + + MyImageMenuItem* cancel; + MyImageMenuItem* head; + MyImageMenuItem* tail; + Gtk::MenuItem* selall; + Gtk::MenuItem* open; + Glib::RefPtr pmaccelgroup; + Gtk::Menu pmenu; + + BatchQueueListener* listener; + + IdleRegister idle_register; }; #endif diff --git a/rtgui/blackwhite.cc b/rtgui/blackwhite.cc index dab459a14..67dd9f64a 100644 --- a/rtgui/blackwhite.cc +++ b/rtgui/blackwhite.cc @@ -364,23 +364,25 @@ BlackWhite::BlackWhite (): FoldableToolPanel(this, "blackwhite", M("TP_BWMIX_LAB } BlackWhite::~BlackWhite () { + idle_register.destroy(); + delete luminanceCEG; delete beforeCurveCEG; delete afterCurveCEG; } -int BWChangedUI (void* data) -{ - (static_cast(data))->BWComputed_ (); - return 0; -} - void BlackWhite::BWChanged (double redbw, double greenbw, double bluebw) { nextredbw = redbw; nextgreenbw = greenbw; nextbluebw = bluebw; - add_idle (BWChangedUI, this); + + const auto func = [](gpointer data) -> gboolean { + static_cast(data)->BWComputed_(); + return FALSE; + }; + + idle_register.add(func, this); } bool BlackWhite::BWComputed_ () diff --git a/rtgui/blackwhite.h b/rtgui/blackwhite.h index e38258b04..5f0ffec44 100644 --- a/rtgui/blackwhite.h +++ b/rtgui/blackwhite.h @@ -28,10 +28,64 @@ #include "mycurve.h" #include "colorprovider.h" -class BlackWhite : public ToolParamBlock, public AdjusterListener, public FoldableToolPanel, public rtengine::AutoBWListener, public CurveListener, public ColorProvider +class BlackWhite final : + public ToolParamBlock, + public AdjusterListener, + public FoldableToolPanel, + public rtengine::AutoBWListener, + public CurveListener, + public ColorProvider { +public: + + BlackWhite (); + ~BlackWhite (); + + void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); + void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); + void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr); + void setBatchMode (bool batchMode); + void autoOpenCurve (); + void setEditProvider (EditDataProvider *provider); + + void autoch_toggled (); + void neutral_pressed (); + + void updateRGBLabel (); + void adjusterChanged (Adjuster* a, double newval); + void setAdjusterBehavior (bool bwadd, bool bwgadd); + void trimValues (rtengine::procparams::ProcParams* pp); + void enabledcc_toggled (); + void enabledChanged (); + void methodChanged (); + void filterChanged (); + void settingChanged (); + virtual void colorForValue (double valX, double valY, enum ColorCaller::ElemType elemType, int callerId, ColorCaller* caller); + void BWChanged (double redbw, double greenbw, double bluebw); + bool BWComputed_ (); + void curveChanged (CurveEditor* ce); + void curveMode1Changed (); + bool curveMode1Changed_ (); + void curveMode1Changed2 (); + bool curveMode1Changed2_ (); + void algoChanged (); + + Glib::ustring getSettingString (); + Glib::ustring getFilterString (); + Glib::ustring getalgoString (); + +private: + void showLuminance(); + void hideLuminance(); + void showFilter(); + void hideFilter(); + void showEnabledCC(); + void hideEnabledCC(); + void showMixer(int nChannels, bool RGBIsSensitive = true); + void hideMixer(); + void showGamma(); + void hideGamma(); -protected: FlatCurveEditor* luminanceCurve; Gtk::HSeparator* luminanceSep; CurveEditorGroup* luminanceCEG; @@ -85,55 +139,7 @@ protected: double nextgreenbw; double nextbluebw; - void showLuminance(); - void hideLuminance(); - void showFilter(); - void hideFilter(); - void showEnabledCC(); - void hideEnabledCC(); - void showMixer(int nChannels, bool RGBIsSensitive = true); - void hideMixer(); - void showGamma(); - void hideGamma(); - -public: - - BlackWhite (); - ~BlackWhite (); - - void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); - void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); - void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr); - void setBatchMode (bool batchMode); - void autoOpenCurve (); - void setEditProvider (EditDataProvider *provider); - - void autoch_toggled (); - void neutral_pressed (); - - void updateRGBLabel (); - void adjusterChanged (Adjuster* a, double newval); - void setAdjusterBehavior (bool bwadd, bool bwgadd); - void trimValues (rtengine::procparams::ProcParams* pp); - void enabledcc_toggled (); - void enabledChanged (); - void methodChanged (); - void filterChanged (); - void settingChanged (); - virtual void colorForValue (double valX, double valY, enum ColorCaller::ElemType elemType, int callerId, ColorCaller* caller); - void BWChanged (double redbw, double greenbw, double bluebw); - bool BWComputed_ (); - void curveChanged (CurveEditor* ce); - void curveMode1Changed (); - bool curveMode1Changed_ (); - void curveMode1Changed2 (); - bool curveMode1Changed2_ (); - void algoChanged (); - - Glib::ustring getSettingString (); - Glib::ustring getFilterString (); - Glib::ustring getalgoString (); - + IdleRegister idle_register; }; #endif diff --git a/rtgui/cachemanager.cc b/rtgui/cachemanager.cc index 7421711eb..68ab74d95 100644 --- a/rtgui/cachemanager.cc +++ b/rtgui/cachemanager.cc @@ -19,6 +19,7 @@ #include "cachemanager.h" #include +#include #include #include diff --git a/rtgui/colorappearance.cc b/rtgui/colorappearance.cc index 528047a22..a198dd1f5 100644 --- a/rtgui/colorappearance.cc +++ b/rtgui/colorappearance.cc @@ -424,6 +424,8 @@ ColorAppearance::ColorAppearance () : FoldableToolPanel(this, "colorappearance", ColorAppearance::~ColorAppearance () { + idle_register.destroy(); + delete curveEditorG; delete curveEditorG2; delete curveEditorG3; @@ -1002,15 +1004,17 @@ void ColorAppearance::setDefaults (const ProcParams* defParams, const ParamsEdit } } -int autoCamChangedUI (void* data) -{ - (static_cast(data))->autoCamComputed_ (); - return 0; -} + void ColorAppearance::autoCamChanged (double ccam) { nextCcam = ccam; - add_idle (autoCamChangedUI, this); + + const auto func = [](gpointer data) -> gboolean { + static_cast(data)->autoCamComputed_(); + return FALSE; + }; + + idle_register.add(func, this); } bool ColorAppearance::autoCamComputed_ () @@ -1023,15 +1027,17 @@ bool ColorAppearance::autoCamComputed_ () return false; } -int adapCamChangedUI (void* data) -{ - (static_cast(data))->adapCamComputed_ (); - return 0; -} + void ColorAppearance::adapCamChanged (double cadap) { nextCadap = cadap; - add_idle (adapCamChangedUI, this); + + const auto func = [](gpointer data) -> gboolean { + static_cast(data)->adapCamComputed_(); + return FALSE; + }; + + idle_register.add(func, this); } bool ColorAppearance::adapCamComputed_ () diff --git a/rtgui/colorappearance.h b/rtgui/colorappearance.h index 1560e275e..e7b7e072d 100644 --- a/rtgui/colorappearance.h +++ b/rtgui/colorappearance.h @@ -28,10 +28,61 @@ #include "guiutils.h" #include "colorprovider.h" -class ColorAppearance : public ToolParamBlock, public AdjusterListener, public FoldableToolPanel, public rtengine::AutoCamListener, public CurveListener, public ColorProvider +class ColorAppearance final : + public ToolParamBlock, + public AdjusterListener, + public FoldableToolPanel, + public rtengine::AutoCamListener, + public CurveListener, + public ColorProvider { +public: + ColorAppearance (); + ~ColorAppearance (); + + void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); + void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); + void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr); + void setBatchMode (bool batchMode); + void adjusterChanged (Adjuster* a, double newval); + void adjusterAutoToggled (Adjuster* a, bool newval); +// void adjusterAdapToggled (Adjuster* a, bool newval); + void enabledChanged (); + void surroundChanged (); + void wbmodelChanged (); + void algoChanged (); + void surrsource_toggled (); + void gamut_toggled (); +// void badpix_toggled (); + void datacie_toggled (); + void tonecie_toggled (); +// void sharpcie_toggled (); + void autoCamChanged (double ccam); + bool autoCamComputed_ (); + void adapCamChanged (double cadap); + bool adapCamComputed_ (); + + void curveChanged (CurveEditor* ce); + void curveMode1Changed (); + bool curveMode1Changed_ (); + void curveMode2Changed (); + bool curveMode2Changed_ (); + void curveMode3Changed (); + bool curveMode3Changed_ (); + + void expandCurve (bool isExpanded); + bool isCurveExpanded (); + void autoOpenCurve (); + + void setAdjusterBehavior (bool degreeadd, bool adapscenadd, bool adaplumadd, bool badpixsladd, bool jlightadd, bool chromaadd, bool contrastadd, bool rstprotectionadd, bool qbrightadd, bool qcontrastadd, bool schromaadd, bool mchromaadd, bool colorhadd); + void trimValues (rtengine::procparams::ProcParams* pp); + void updateCurveBackgroundHistogram (LUTu & histToneCurve, LUTu & histLCurve, LUTu & histCCurve,/* LUTu & histCLurve, LUTu & histLLCurve,*/ LUTu & histLCAM, LUTu & histCCAM, LUTu & histRed, LUTu & histGreen, LUTu & histBlue, LUTu & histLuma, LUTu & histLRETI); + virtual void colorForValue (double valX, double valY, enum ColorCaller::ElemType elemType, int callerId, ColorCaller *caller); + +private: + bool bgTTipQuery(int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip); + bool srTTipQuery(int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip); -protected: Glib::RefPtr bgTTips; Glib::RefPtr srTTips; Glib::RefPtr bgPixbuf; @@ -83,56 +134,10 @@ protected: bool lastAutoAdapscen; bool lastsurr; bool lastgamut; -// bool lastbadpix; bool lastdatacie; bool lasttonecie; -// bool lastsharpcie; - bool bgTTipQuery(int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip); - bool srTTipQuery(int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip); -public: - - ColorAppearance (); - ~ColorAppearance (); - - void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); - void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); - void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr); - void setBatchMode (bool batchMode); - void adjusterChanged (Adjuster* a, double newval); - void adjusterAutoToggled (Adjuster* a, bool newval); -// void adjusterAdapToggled (Adjuster* a, bool newval); - void enabledChanged (); - void surroundChanged (); - void wbmodelChanged (); - void algoChanged (); - void surrsource_toggled (); - void gamut_toggled (); -// void badpix_toggled (); - void datacie_toggled (); - void tonecie_toggled (); -// void sharpcie_toggled (); - void autoCamChanged (double ccam); - bool autoCamComputed_ (); - void adapCamChanged (double cadap); - bool adapCamComputed_ (); - - void curveChanged (CurveEditor* ce); - void curveMode1Changed (); - bool curveMode1Changed_ (); - void curveMode2Changed (); - bool curveMode2Changed_ (); - void curveMode3Changed (); - bool curveMode3Changed_ (); - - void expandCurve (bool isExpanded); - bool isCurveExpanded (); - void autoOpenCurve (); - - void setAdjusterBehavior (bool degreeadd, bool adapscenadd, bool adaplumadd, bool badpixsladd, bool jlightadd, bool chromaadd, bool contrastadd, bool rstprotectionadd, bool qbrightadd, bool qcontrastadd, bool schromaadd, bool mchromaadd, bool colorhadd); - void trimValues (rtengine::procparams::ProcParams* pp); - void updateCurveBackgroundHistogram (LUTu & histToneCurve, LUTu & histLCurve, LUTu & histCCurve,/* LUTu & histCLurve, LUTu & histLLCurve,*/ LUTu & histLCAM, LUTu & histCCAM, LUTu & histRed, LUTu & histGreen, LUTu & histBlue, LUTu & histLuma, LUTu & histLRETI); - virtual void colorForValue (double valX, double valY, enum ColorCaller::ElemType elemType, int callerId, ColorCaller *caller); + IdleRegister idle_register; }; #endif diff --git a/rtgui/colortoning.cc b/rtgui/colortoning.cc index ceec0092f..d471ef787 100644 --- a/rtgui/colortoning.cc +++ b/rtgui/colortoning.cc @@ -324,6 +324,8 @@ ColorToning::ColorToning () : FoldableToolPanel(this, "colortoning", M("TP_COLOR ColorToning::~ColorToning() { + idle_register.destroy(); + delete colorCurveEditorG; delete opacityCurveEditorG; delete clCurveEditorG; @@ -650,19 +652,18 @@ void ColorToning::adjusterChanged (ThresholdAdjuster* a, double newBottom, doubl Glib::ustring::compose(Glib::ustring(M("TP_COLORTONING_HUE") + ": %1" + "\n" + M("TP_COLORTONING_STRENGTH") + ": %2"), int(newTop), int(newBottom))); } -int CTChanged_UI (void* data) -{ - (static_cast(data))->CTComp_ (); - return 0; -} - - void ColorToning::autoColorTonChanged(int bwct, int satthres, int satprot) { nextbw = bwct; nextsatth = satthres; nextsatpr = satprot; - add_idle (CTChanged_UI, this); + + const auto func = [](gpointer data) -> gboolean { + static_cast(data)->CTComp_(); + return FALSE; + }; + + idle_register.add(func, this); } bool ColorToning::CTComp_ () diff --git a/rtgui/colortoning.h b/rtgui/colortoning.h index e34447301..7f1290bf6 100644 --- a/rtgui/colortoning.h +++ b/rtgui/colortoning.h @@ -13,12 +13,43 @@ #include "thresholdadjuster.h" #include "colorprovider.h" -class ColorToning : public ToolParamBlock, public FoldableToolPanel, public rtengine::AutoColorTonListener, public CurveListener, public ColorProvider, - public ThresholdAdjusterListener, public AdjusterListener +class ColorToning final : + public ToolParamBlock, + public FoldableToolPanel, + public rtengine::AutoColorTonListener, + public CurveListener, + public ColorProvider, + public ThresholdAdjusterListener, + public AdjusterListener { +public: + ColorToning (); + ~ColorToning(); + void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); + void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); + void setBatchMode (bool batchMode); + void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr); + void trimValues (rtengine::procparams::ProcParams* pp); + void adjusterChanged (Adjuster* a, double newval); + void adjusterChanged (ThresholdAdjuster* a, double newBottom, double newTop); + void setAdjusterBehavior (bool splitAdd, bool satThresholdAdd, bool satOpacityAdd, bool strprotectAdd, bool balanceAdd); + void neutral_pressed (); + //void neutralCurves_pressed (); + void autoColorTonChanged (int bwct, int satthres, int satprot); + bool CTComp_ (); -protected: - //Gtk::HSeparator* splitSep; + void enabledChanged (); + void curveChanged (CurveEditor* ce); + void autosatChanged (); + void autoOpenCurve (); + void methodChanged (); + void twocolorChanged (bool changedbymethod); + void twoColorChangedByGui (); + void lumamodeChanged (); + + void colorForValue (double valX, double valY, enum ColorCaller::ElemType elemType, int callerId, ColorCaller* caller); + +private: Gtk::HSeparator* satLimiterSep; Gtk::HSeparator* colorSep; CurveEditorGroup* colorCurveEditorG; @@ -57,7 +88,6 @@ protected: Gtk::Image* irg; Gtk::Button* neutral; - //Gtk::Button* neutralCurves; Gtk::HBox* neutrHBox; Gtk::HBox* chromaHbox; Gtk::Label* chroLabel; @@ -75,32 +105,7 @@ protected: bool lastLumamode; sigc::connection lumamodeConn; -public: - ColorToning (); - ~ColorToning(); - void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); - void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); - void setBatchMode (bool batchMode); - void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr); - void trimValues (rtengine::procparams::ProcParams* pp); - void adjusterChanged (Adjuster* a, double newval); - void adjusterChanged (ThresholdAdjuster* a, double newBottom, double newTop); - void setAdjusterBehavior (bool splitAdd, bool satThresholdAdd, bool satOpacityAdd, bool strprotectAdd, bool balanceAdd); - void neutral_pressed (); - //void neutralCurves_pressed (); - void autoColorTonChanged (int bwct, int satthres, int satprot); - bool CTComp_ (); - - void enabledChanged (); - void curveChanged (CurveEditor* ce); - void autosatChanged (); - void autoOpenCurve (); - void methodChanged (); - void twocolorChanged (bool changedbymethod); - void twoColorChangedByGui (); - void lumamodeChanged (); - - void colorForValue (double valX, double valY, enum ColorCaller::ElemType elemType, int callerId, ColorCaller* caller); + IdleRegister idle_register; }; #endif diff --git a/rtgui/crop.cc b/rtgui/crop.cc index a69c71ed8..83efb6a37 100644 --- a/rtgui/crop.cc +++ b/rtgui/crop.cc @@ -25,6 +25,9 @@ using namespace rtengine::procparams; extern Options options; +namespace +{ + class RefreshSpinHelper { @@ -35,6 +38,22 @@ public: : crop(_crop), notify(_notify) {} }; +int refreshSpinsUI (void* data) +{ + RefreshSpinHelper* rsh = static_cast(data); + rsh->crop->refreshSpins (rsh->notify); + delete rsh; + return 0; +} + +int notifyListenerUI (void* data) +{ + static_cast(data)->notifyListener(); + return 0; +} + +} + Crop::Crop (): FoldableToolPanel(this, "crop", M("TP_CROP_LABEL"), false, true) { @@ -252,6 +271,11 @@ Crop::Crop (): FoldableToolPanel(this, "crop", M("TP_CROP_LABEL"), false, true) show_all (); } +Crop::~Crop() +{ + idle_register.destroy(); +} + void Crop::writeOptions () { @@ -508,32 +532,18 @@ void Crop::enabledChanged () } } -int notifyListenerUI (void* data) -{ - (static_cast(data))->notifyListener (); - return 0; -} - -int refreshSpinsUI (void* data) -{ - RefreshSpinHelper* rsh = static_cast(data); - rsh->crop->refreshSpins (rsh->notify); - delete rsh; - return 0; -} - void Crop::hFlipCrop () { nx = maxw - nx - nw; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::vFlipCrop () { ny = maxh - ny - nh; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::rotateCrop (int deg, bool hflip, bool vflip) @@ -573,7 +583,7 @@ void Crop::rotateCrop (int deg, bool hflip, bool vflip) } lastRotationDeg = deg; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::positionChanged () @@ -587,7 +597,7 @@ void Crop::positionChanged () int W = nw; int H = nh; cropMoved (X, Y, W, H); - add_idle (notifyListenerUI, this); + idle_register.add(notifyListenerUI, this); } void Crop::widthChanged () @@ -600,7 +610,7 @@ void Crop::widthChanged () int W = (int)w->get_value (); int H = nh; cropWidth2Resized (X, Y, W, H); - add_idle (notifyListenerUI, this); + idle_register.add(notifyListenerUI, this); } void Crop::heightChanged () @@ -613,7 +623,7 @@ void Crop::heightChanged () int W = nw; int H = (int)h->get_value (); cropHeight2Resized (X, Y, W, H); - add_idle (notifyListenerUI, this); + idle_register.add(notifyListenerUI, this); } // Fixed ratio toggle button @@ -665,7 +675,7 @@ void Crop::adjustCropToRatio() } // This will save the options - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, true)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, true)); } void Crop::refreshSize () @@ -737,28 +747,28 @@ void Crop::setDimensions (int mw, int mh) refreshSize (); } -struct setdimparams { - Crop* crop; - int x; - int y; -}; - -int sizeChangedUI (void* data) -{ - setdimparams* params = static_cast(data); - params->crop->setDimensions (params->x, params->y); - delete params; - return 0; -} - void Crop::sizeChanged (int x, int y, int ow, int oh) { + struct Params { + Crop* crop; + int x; + int y; + }; - setdimparams* params = new setdimparams; - params->x = x; - params->y = y; - params->crop = this; - add_idle (sizeChangedUI, params); + Params* const params = new Params{ + this, + x, + y + }; + + const auto func = [](gpointer data) -> gboolean { + Params* const params = static_cast(data); + params->crop->setDimensions(params->x, params->y); + delete params; + return FALSE; + }; + + idle_register.add(func, params); } bool Crop::refreshSpins (bool notify) @@ -822,7 +832,7 @@ void Crop::cropMoved (int &X, int &Y, int &W, int &H) nw = W; nh = H; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); // Glib::signal_idle().connect (sigc::mem_fun(*this, &Crop::refreshSpins)); } @@ -866,7 +876,7 @@ void Crop::cropWidth1Resized (int &X, int &Y, int &W, int &H) nw = W; nh = H; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::cropWidth2Resized (int &X, int &Y, int &W, int &H) @@ -906,7 +916,7 @@ void Crop::cropWidth2Resized (int &X, int &Y, int &W, int &H) nw = W; nh = H; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::cropHeight1Resized (int &X, int &Y, int &W, int &H) @@ -949,7 +959,7 @@ void Crop::cropHeight1Resized (int &X, int &Y, int &W, int &H) nw = W; nh = H; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::cropHeight2Resized (int &X, int &Y, int &W, int &H) @@ -989,7 +999,7 @@ void Crop::cropHeight2Resized (int &X, int &Y, int &W, int &H) nw = W; nh = H; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::cropTopLeftResized (int &X, int &Y, int &W, int &H) @@ -1031,7 +1041,7 @@ void Crop::cropTopLeftResized (int &X, int &Y, int &W, int &H) nw = W; nh = H; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::cropTopRightResized (int &X, int &Y, int &W, int &H) @@ -1071,7 +1081,7 @@ void Crop::cropTopRightResized (int &X, int &Y, int &W, int &H) nw = W; nh = H; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::cropBottomLeftResized (int &X, int &Y, int &W, int &H) @@ -1111,7 +1121,7 @@ void Crop::cropBottomLeftResized (int &X, int &Y, int &W, int &H) nw = W; nh = H; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::cropBottomRightResized (int &X, int &Y, int &W, int &H) @@ -1148,7 +1158,7 @@ void Crop::cropBottomRightResized (int &X, int &Y, int &W, int &H) nw = W; nh = H; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::cropInit (int &x, int &y, int &w, int &h) @@ -1266,13 +1276,12 @@ void Crop::cropResized (int &x, int &y, int& x2, int& y2) nw = W; nh = H; - add_idle (refreshSpinsUI, new RefreshSpinHelper (this, false)); + idle_register.add(refreshSpinsUI, new RefreshSpinHelper(this, false)); } void Crop::cropManipReady () { - - add_idle (notifyListenerUI, this); + idle_register.add(notifyListenerUI, this); } double Crop::getRatio () diff --git a/rtgui/crop.h b/rtgui/crop.h index 077903836..e0bad8b36 100644 --- a/rtgui/crop.h +++ b/rtgui/crop.h @@ -33,44 +33,20 @@ public: virtual void cropSelectRequested() = 0; }; -class CropRatio -{ - -public: +struct CropRatio { Glib::ustring label; double value; }; -class Crop : public ToolParamBlock, public CropGUIListener, public FoldableToolPanel, public rtengine::SizeListener +class Crop final : + public ToolParamBlock, + public CropGUIListener, + public FoldableToolPanel, + public rtengine::SizeListener { -protected: - Gtk::CheckButton* fixr; - MyComboBoxText* ratio; - MyComboBoxText* orientation; - MyComboBoxText* guide; - Gtk::Button* selectCrop; - CropPanelListener* clistener; - int opt; - MySpinButton* x; - MySpinButton* y; - MySpinButton* w; - MySpinButton* h; - MySpinButton* ppi; - Gtk::Label* sizecm; - Gtk::Label* sizein; - Gtk::VBox* ppibox; - Gtk::VBox* sizebox; - int maxw, maxh; - double nx, ny; - int nw, nh; - int lastRotationDeg; - sigc::connection xconn, yconn, wconn, hconn, fconn, rconn, oconn, gconn; - bool wDirty, hDirty, xDirty, yDirty, lastFixRatio; - void adjustCropToRatio(); - std::vector cropratio; - public: - Crop (); + Crop(); + ~Crop(); void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); @@ -116,6 +92,34 @@ public: void hFlipCrop (); void vFlipCrop (); void rotateCrop (int deg, bool hflip, bool vflip); + +private: + Gtk::CheckButton* fixr; + MyComboBoxText* ratio; + MyComboBoxText* orientation; + MyComboBoxText* guide; + Gtk::Button* selectCrop; + CropPanelListener* clistener; + int opt; + MySpinButton* x; + MySpinButton* y; + MySpinButton* w; + MySpinButton* h; + MySpinButton* ppi; + Gtk::Label* sizecm; + Gtk::Label* sizein; + Gtk::VBox* ppibox; + Gtk::VBox* sizebox; + int maxw, maxh; + double nx, ny; + int nw, nh; + int lastRotationDeg; + sigc::connection xconn, yconn, wconn, hconn, fconn, rconn, oconn, gconn; + bool wDirty, hDirty, xDirty, yDirty, lastFixRatio; + void adjustCropToRatio(); + std::vector cropratio; + + IdleRegister idle_register; }; #endif diff --git a/rtgui/crophandler.cc b/rtgui/crophandler.cc index bb59efe13..42c6eb756 100644 --- a/rtgui/crophandler.cc +++ b/rtgui/crophandler.cc @@ -38,14 +38,15 @@ CropHandler::CropHandler () displayHandler(nullptr) { - chi = new CropHandlerIdleHelper; - chi->destroyed = false; - chi->pending = 0; - chi->cropHandler = this; + idle_helper = new IdleHelper; + idle_helper->destroyed = false; + idle_helper->pending = 0; + idle_helper->cropHandler = this; } CropHandler::~CropHandler () { + idle_register.destroy(); if (ipc) { ipc->delSizeListener (this); @@ -61,10 +62,10 @@ CropHandler::~CropHandler () cimg.lock (); - if (chi->pending) { - chi->destroyed = true; + if (idle_helper->pending) { + idle_helper->destroyed = true; } else { - delete chi; + delete idle_helper; } cimg.unlock (); @@ -292,83 +293,6 @@ void CropHandler::getPosition (int& x, int& y) } -int createpixbufs (void* data) -{ - - CropHandlerIdleHelper* chi = static_cast(data); - - if (chi->destroyed) { - if (chi->pending == 1) { - delete chi; - } else { - chi->pending--; - } - - return 0; - } - - CropHandler* ch = chi->cropHandler; - - ch->cimg.lock (); - ch->cropPixbuf.clear (); - - if (!ch->enabled) { - delete [] ch->cropimg; - ch->cropimg = nullptr; - delete [] ch->cropimgtrue; - ch->cropimgtrue = nullptr; - ch->cimg.unlock (); - return 0; - } - - if (ch->cropimg) { - if (ch->cix == ch->cropX && ch->ciy == ch->cropY && ch->ciw == ch->cropW && ch->cih == ch->cropH && ch->cis == (ch->zoom >= 1000 ? 1 : ch->zoom)) { - // calculate final image size - int czoom = ch->zoom < 1000 ? 1000 : ch->zoom; - int imw = ch->cropimg_width * czoom / 1000; - int imh = ch->cropimg_height * czoom / 1000; - - if (imw > ch->ww) { - imw = ch->ww; - } - - if (imh > ch->wh) { - imh = ch->wh; - } - - Glib::RefPtr tmpPixbuf = Gdk::Pixbuf::create_from_data (ch->cropimg, Gdk::COLORSPACE_RGB, false, 8, ch->cropimg_width, 2 * ch->cropimg_height, 3 * ch->cropimg_width); - ch->cropPixbuf = Gdk::Pixbuf::create (Gdk::COLORSPACE_RGB, false, 8, imw, imh); - tmpPixbuf->scale (ch->cropPixbuf, 0, 0, imw, imh, 0, 0, czoom / 1000.0, czoom / 1000.0, Gdk::INTERP_NEAREST); - tmpPixbuf.clear (); - - Glib::RefPtr tmpPixbuftrue = Gdk::Pixbuf::create_from_data (ch->cropimgtrue, Gdk::COLORSPACE_RGB, false, 8, ch->cropimg_width, 2 * ch->cropimg_height, 3 * ch->cropimg_width); - ch->cropPixbuftrue = Gdk::Pixbuf::create (Gdk::COLORSPACE_RGB, false, 8, imw, imh); - tmpPixbuftrue->scale (ch->cropPixbuftrue, 0, 0, imw, imh, 0, 0, czoom / 1000.0, czoom / 1000.0, Gdk::INTERP_NEAREST); - tmpPixbuftrue.clear (); - } - - delete [] ch->cropimg; - ch->cropimg = nullptr; - delete [] ch->cropimgtrue; - ch->cropimgtrue = nullptr; - } - - ch->cimg.unlock (); - - if (ch->displayHandler) { - ch->displayHandler->cropImageUpdated (); - - if (ch->initial) { - ch->displayHandler->initialImageArrived (); - ch->initial = false; - } - } - - chi->pending--; - - return 0; -} - void CropHandler::setDetailedCrop (IImage8* im, IImage8* imtrue, rtengine::procparams::ColorManagementParams cmp, rtengine::procparams::CropParams cp, int ax, int ay, int aw, int ah, int askip) { @@ -408,8 +332,84 @@ void CropHandler::setDetailedCrop (IImage8* im, IImage8* imtrue, rtengine::procp ciw = aw; cih = ah; cis = askip; - chi->pending++; - add_idle (createpixbufs, chi); + idle_helper->pending++; + + const auto func = [](gpointer data) -> gboolean { + IdleHelper* const idle_helper = static_cast(data); + + if (idle_helper->destroyed) { + if (idle_helper->pending == 1) { + delete idle_helper; + } else { + idle_helper->pending--; + } + + return FALSE; + } + + CropHandler* ch = idle_helper->cropHandler; + + ch->cimg.lock (); + ch->cropPixbuf.clear (); + + if (!ch->enabled) { + delete [] ch->cropimg; + ch->cropimg = nullptr; + delete [] ch->cropimgtrue; + ch->cropimgtrue = nullptr; + ch->cimg.unlock (); + return FALSE; + } + + if (ch->cropimg) { + if (ch->cix == ch->cropX && ch->ciy == ch->cropY && ch->ciw == ch->cropW && ch->cih == ch->cropH && ch->cis == (ch->zoom >= 1000 ? 1 : ch->zoom)) { + // calculate final image size + int czoom = ch->zoom < 1000 ? 1000 : ch->zoom; + int imw = ch->cropimg_width * czoom / 1000; + int imh = ch->cropimg_height * czoom / 1000; + + if (imw > ch->ww) { + imw = ch->ww; + } + + if (imh > ch->wh) { + imh = ch->wh; + } + + Glib::RefPtr tmpPixbuf = Gdk::Pixbuf::create_from_data (ch->cropimg, Gdk::COLORSPACE_RGB, false, 8, ch->cropimg_width, 2 * ch->cropimg_height, 3 * ch->cropimg_width); + ch->cropPixbuf = Gdk::Pixbuf::create (Gdk::COLORSPACE_RGB, false, 8, imw, imh); + tmpPixbuf->scale (ch->cropPixbuf, 0, 0, imw, imh, 0, 0, czoom / 1000.0, czoom / 1000.0, Gdk::INTERP_NEAREST); + tmpPixbuf.clear (); + + Glib::RefPtr tmpPixbuftrue = Gdk::Pixbuf::create_from_data (ch->cropimgtrue, Gdk::COLORSPACE_RGB, false, 8, ch->cropimg_width, 2 * ch->cropimg_height, 3 * ch->cropimg_width); + ch->cropPixbuftrue = Gdk::Pixbuf::create (Gdk::COLORSPACE_RGB, false, 8, imw, imh); + tmpPixbuftrue->scale (ch->cropPixbuftrue, 0, 0, imw, imh, 0, 0, czoom / 1000.0, czoom / 1000.0, Gdk::INTERP_NEAREST); + tmpPixbuftrue.clear (); + } + + delete [] ch->cropimg; + ch->cropimg = nullptr; + delete [] ch->cropimgtrue; + ch->cropimgtrue = nullptr; + } + + ch->cimg.unlock (); + + if (ch->displayHandler) { + ch->displayHandler->cropImageUpdated (); + + if (ch->initial) { + ch->displayHandler->initialImageArrived (); + ch->initial = false; + } + } + + idle_helper->pending--; + + return FALSE; + }; + + idle_register.add(func, idle_helper); } cimg.unlock (); diff --git a/rtgui/crophandler.h b/rtgui/crophandler.h index 3bf7e58da..0492aea2a 100644 --- a/rtgui/crophandler.h +++ b/rtgui/crophandler.h @@ -36,56 +36,15 @@ public: virtual void setDisplayPosition (int x, int y) {} }; -class CropHandler; -struct CropHandlerIdleHelper { - CropHandler* cropHandler; - bool destroyed; - int pending; -}; - /** * This class handle the displayed part of the image, ask for the initial data and process it so it can display it. * Its position on the preview is handled not set by this class but by the CropHandlerListener (i.e. CropWindow) with which it works closely. */ -class CropHandler : public rtengine::DetailedCropListener, public rtengine::SizeListener +class CropHandler final : + public rtengine::DetailedCropListener, + public rtengine::SizeListener { - - friend int createpixbufs (void* data); - -protected: - int zoom; // scale factor (e.g. 5 if 1:5 scale) ; if 1:1 scale and bigger, factor is multiplied by 1000 (i.e. 1000 for 1:1 scale, 2000 for 2:1, etc...) - int ww, wh; // size of the crop's canvas on the screen ; might be bigger than the displayed image, but not smaller - int imx, imy, imw, imh; // this is a copy of the cropwindow's parameters - int cax, cay; // clamped crop anchor's coordinate, i.e. point of the image that coincide to the center of the display area, expressed in image coordinates; cannot be outside the image's bounds; but if cax==cay==-1, designate the center of the image - int cx, cy, cw, ch; // position and size of the requested crop ; position expressed in image coordinates, so cx and cy might be negative and cw and ch higher than the image's 1:1 size - int cropX, cropY, cropW, cropH; // cropPixbuf's displayed area (position and size), i.e. coordinates in 1:1 scale, i.e. cx, cy, cw & ch trimmed to the image's bounds - bool enabled; - unsigned char* cropimg; - unsigned char* cropimgtrue; - int cropimg_width, cropimg_height, cix, ciy, ciw, cih, cis; - bool initial; - bool isLowUpdatePriority; - - rtengine::StagedImageProcessor* ipc; - rtengine::DetailedCrop* crop; - - CropDisplayHandler* displayHandler; - CropHandlerIdleHelper* chi; - - void compDim (); - public: - - void update (); - - - rtengine::procparams::CropParams cropParams; - rtengine::procparams::ColorManagementParams colorParams; - Glib::RefPtr cropPixbuf; - Glib::RefPtr cropPixbuftrue; - - MyMutex cimg; - CropHandler (); ~CropHandler (); @@ -127,6 +86,46 @@ public: bool getWindow (int& cwx, int& cwy, int& cww, int& cwh, int& cskip); // SizeListener interface void sizeChanged (int w, int h, int ow, int oh); + + void update (); + + + rtengine::procparams::CropParams cropParams; + rtengine::procparams::ColorManagementParams colorParams; + Glib::RefPtr cropPixbuf; + Glib::RefPtr cropPixbuftrue; + + MyMutex cimg; + +private: + struct IdleHelper { + CropHandler* cropHandler; + bool destroyed; + int pending; + }; + + void compDim (); + + int zoom; // scale factor (e.g. 5 if 1:5 scale) ; if 1:1 scale and bigger, factor is multiplied by 1000 (i.e. 1000 for 1:1 scale, 2000 for 2:1, etc...) + int ww, wh; // size of the crop's canvas on the screen ; might be bigger than the displayed image, but not smaller + int imx, imy, imw, imh; // this is a copy of the cropwindow's parameters + int cax, cay; // clamped crop anchor's coordinate, i.e. point of the image that coincide to the center of the display area, expressed in image coordinates; cannot be outside the image's bounds; but if cax==cay==-1, designate the center of the image + int cx, cy, cw, ch; // position and size of the requested crop ; position expressed in image coordinates, so cx and cy might be negative and cw and ch higher than the image's 1:1 size + int cropX, cropY, cropW, cropH; // cropPixbuf's displayed area (position and size), i.e. coordinates in 1:1 scale, i.e. cx, cy, cw & ch trimmed to the image's bounds + bool enabled; + unsigned char* cropimg; + unsigned char* cropimgtrue; + int cropimg_width, cropimg_height, cix, ciy, ciw, cih, cis; + bool initial; + bool isLowUpdatePriority; + + rtengine::StagedImageProcessor* ipc; + rtengine::DetailedCrop* crop; + + CropDisplayHandler* displayHandler; + IdleHelper* idle_helper; + + IdleRegister idle_register; }; #endif diff --git a/rtgui/dirbrowser.cc b/rtgui/dirbrowser.cc index eafaa477a..7b5adade2 100644 --- a/rtgui/dirbrowser.cc +++ b/rtgui/dirbrowser.cc @@ -18,6 +18,7 @@ */ #include "dirbrowser.h" +#include #include #ifdef WIN32 diff --git a/rtgui/dirpyrdenoise.cc b/rtgui/dirpyrdenoise.cc index a5611cc45..bd673e5c3 100644 --- a/rtgui/dirpyrdenoise.cc +++ b/rtgui/dirpyrdenoise.cc @@ -327,22 +327,24 @@ DirPyrDenoise::DirPyrDenoise () : FoldableToolPanel(this, "dirpyrdenoise", M("TP DirPyrDenoise::~DirPyrDenoise () { + idle_register.destroy(); + delete NoiscurveEditorG; delete CCcurveEditorG; +} -} -int chromaChangedUI (void* data) -{ - (static_cast(data))->chromaComputed_ (); - return 0; -} void DirPyrDenoise::chromaChanged (double autchroma, double autred, double autblue) { nextchroma = autchroma; -// printf("CHROM=%f\n",nextchroma); nextred = autred; nextblue = autblue; - add_idle(chromaChangedUI, this); + + const auto func = [](gpointer data) -> gboolean { + static_cast(data)->chromaComputed_(); + return false; + }; + + idle_register.add(func, this); } bool DirPyrDenoise::chromaComputed_ () @@ -356,12 +358,6 @@ bool DirPyrDenoise::chromaComputed_ () updateNoiseLabel (); return false; } -int TilePrevChangedUI (void* data) -{ - (static_cast(data))->TilePrevComputed_ (); - return 0; -} - void DirPyrDenoise::noiseTilePrev (int tileX, int tileY, int prevX, int prevY, int sizeT, int sizeP) { @@ -372,10 +368,14 @@ void DirPyrDenoise::noiseTilePrev (int tileX, int tileY, int prevX, int prevY, i nextsizeT = sizeT; nextsizeP = sizeP; - add_idle(TilePrevChangedUI, this); - + const auto func = [](gpointer data) -> gboolean { + static_cast(data)->TilePrevComputed_(); + return false; + }; + idle_register.add(func, this); } + bool DirPyrDenoise::TilePrevComputed_ () { @@ -385,6 +385,7 @@ bool DirPyrDenoise::TilePrevComputed_ () updatePrevLabel (); return false; } + void DirPyrDenoise::updateTileLabel () { if (!batchMode) { @@ -422,19 +423,17 @@ void DirPyrDenoise::updatePrevLabel () } } - -int noiseChangedUI (void* data) -{ - (static_cast(data))->noiseComputed_ (); - return 0; -} - - void DirPyrDenoise::noiseChanged (double nresid, double highresid) { nextnresid = nresid; nexthighresid = highresid; - add_idle(noiseChangedUI, this); + + const auto func = [](gpointer data) -> gboolean { + static_cast(data)->noiseComputed_(); + return false; + }; + + idle_register.add(func, this); } bool DirPyrDenoise::noiseComputed_ () diff --git a/rtgui/dirpyrdenoise.h b/rtgui/dirpyrdenoise.h index e81bf263e..9745b374b 100644 --- a/rtgui/dirpyrdenoise.h +++ b/rtgui/dirpyrdenoise.h @@ -28,10 +28,58 @@ #include "guiutils.h" #include "options.h" -class DirPyrDenoise : public ToolParamBlock, public AdjusterListener, public FoldableToolPanel, public rtengine::AutoChromaListener, public CurveListener, public ColorProvider +class DirPyrDenoise final : + public ToolParamBlock, + public AdjusterListener, + public FoldableToolPanel, + public rtengine::AutoChromaListener, + public CurveListener, + public ColorProvider { +public: + DirPyrDenoise (); + ~DirPyrDenoise (); -protected: + void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); + void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); + void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr); + void setBatchMode (bool batchMode); + void curveChanged (CurveEditor* ce); + void setEditProvider (EditDataProvider *provider); + void autoOpenCurve (); + + void adjusterChanged (Adjuster* a, double newval); + void enabledChanged (); + void enhanceChanged (); + void medianChanged (); + void autochromaChanged (); + void chromaChanged (double autchroma, double autred, double autblue); + bool chromaComputed_ (); + void noiseChanged (double nresid, double highresid); + bool noiseComputed_ (); + void noiseTilePrev (int tileX, int tileY, int prevX, int prevY, int sizeT, int sizeP); + bool TilePrevComputed_ (); + +// void perform_toggled (); + void updateNoiseLabel (); + void LmethodChanged (); + void CmethodChanged (); + void C2methodChanged (); + void updateTileLabel (); + void updatePrevLabel (); + + void dmethodChanged (); + void medmethodChanged (); + void methodmedChanged (); + void rgbmethodChanged (); + void smethodChanged (); + virtual void colorForValue (double valX, double valY, enum ColorCaller::ElemType elemType, int callerId, ColorCaller* caller); + + void setAdjusterBehavior (bool lumaadd, bool lumdetadd, bool chromaadd, bool chromaredadd, bool chromablueadd, bool gammaadd, bool passesadd); + void trimValues (rtengine::procparams::ProcParams* pp); + Glib::ustring getSettingString (); + +private: CurveEditorGroup* NoiscurveEditorG; CurveEditorGroup* CCcurveEditorG; Adjuster* luma; @@ -92,50 +140,7 @@ protected: int nextsizeT; int nextsizeP; -public: - - DirPyrDenoise (); - ~DirPyrDenoise (); - - void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); - void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); - void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr); - void setBatchMode (bool batchMode); - void curveChanged (CurveEditor* ce); - void setEditProvider (EditDataProvider *provider); - void autoOpenCurve (); - - void adjusterChanged (Adjuster* a, double newval); - void enabledChanged (); - void enhanceChanged (); - void medianChanged (); - void autochromaChanged (); - void chromaChanged (double autchroma, double autred, double autblue); - bool chromaComputed_ (); - void noiseChanged (double nresid, double highresid); - bool noiseComputed_ (); - void noiseTilePrev (int tileX, int tileY, int prevX, int prevY, int sizeT, int sizeP); - bool TilePrevComputed_ (); - -// void perform_toggled (); - void updateNoiseLabel (); - void LmethodChanged (); - void CmethodChanged (); - void C2methodChanged (); - void updateTileLabel (); - void updatePrevLabel (); - - void dmethodChanged (); - void medmethodChanged (); - void methodmedChanged (); - void rgbmethodChanged (); - void smethodChanged (); - virtual void colorForValue (double valX, double valY, enum ColorCaller::ElemType elemType, int callerId, ColorCaller* caller); - - void setAdjusterBehavior (bool lumaadd, bool lumdetadd, bool chromaadd, bool chromaredadd, bool chromablueadd, bool gammaadd, bool passesadd); - void trimValues (rtengine::procparams::ProcParams* pp); - Glib::ustring getSettingString (); - + IdleRegister idle_register; }; #endif diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 0783efe14..8da176fc3 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -35,6 +35,44 @@ using namespace rtengine::procparams; +namespace +{ + +struct spparams { + double val; + Glib::ustring str; + MyProgressBar *pProgress; + Glib::RefPtr cssProvider; +}; + +int setprogressStrUI ( void *p ) +{ + spparams *s = static_cast (p); + + if ( ! s->str.empty() ) { + s->pProgress->set_text ( M (s->str) ); + } + + if ( s->val >= 0 ) { + s->pProgress->set_fraction ( s->val ); + + if (s->cssProvider) { + if ( s->val < 1.0 ) { + s->cssProvider->load_from_data ("ProgressBar { background-color: red }"); + } else { + s->cssProvider->load_from_data ("ProgressBar { background-color: grey }"); + } + + s->pProgress->get_style_context()->set_background (s->pProgress->get_window()); + } + } + + delete s; + return FALSE; +} + +} + class EditorPanel::ColorManagementToolbar { private: @@ -683,6 +721,7 @@ EditorPanel::EditorPanel (FilePanel* filePanel) EditorPanel::~EditorPanel () { + idle_register.destroy(); history->setHistoryBeforeLineListener (nullptr); // the order is important! @@ -1017,46 +1056,12 @@ void EditorPanel::setProgressState (bool inProcessing) g_idle_add (setProgressStateUIThread, p); } -struct spparams { - double val; - Glib::ustring str; - MyProgressBar *pProgress; - Glib::RefPtr cssProvider; - -}; - -int setprogressStrUI ( void *p ) -{ - spparams *s = static_cast (p); - - if ( ! s->str.empty() ) { - s->pProgress->set_text ( M (s->str) ); - } - - if ( s->val >= 0 ) { - s->pProgress->set_fraction ( s->val ); - - if (s->cssProvider) { - if ( s->val < 1.0 ) { - s->cssProvider->load_from_data ("ProgressBar { background-color: red }"); - } else { - s->cssProvider->load_from_data ("ProgressBar { background-color: grey }"); - } - - s->pProgress->get_style_context()->set_background (s->pProgress->get_window()); - } - } - - delete s; - return 0; -} - void EditorPanel::setProgress (double p) { spparams *s = new spparams; s->val = p; s->pProgress = progressLabel; - add_idle (setprogressStrUI, s); + idle_register.add(setprogressStrUI, s); } void EditorPanel::setProgressStr (Glib::ustring str) @@ -1065,7 +1070,7 @@ void EditorPanel::setProgressStr (Glib::ustring str) s->str = str; s->val = -1; s->pProgress = progressLabel; - add_idle (setprogressStrUI, s); + idle_register.add(setprogressStrUI, s); } // This is only called from the ThreadUI, so within the gtk thread diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index d87f12c23..609df5236 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -45,102 +45,17 @@ struct EditorPanelIdleHelper { }; class RTWindow; -class EditorPanel : public Gtk::VBox, +class EditorPanel final : + public Gtk::VBox, public PParamsChangeListener, public rtengine::ProgressListener, public ThumbnailListener, public HistoryBeforeLineListener, public rtengine::HistogramListener { -private: - - Glib::ustring lastSaveAsFileName; - bool realized; - -protected: - MyProgressBar *progressLabel; - Gtk::ToggleButton* info; - Gtk::ToggleButton* hidehp; - Gtk::ToggleButton* tbShowHideSidePanels; - Gtk::ToggleButton* tbTopPanel_1; - Gtk::ToggleButton* tbRightPanel_1; - Gtk::ToggleButton* tbBeforeLock; - //bool bAllSidePanelsVisible; - Gtk::ToggleButton* beforeAfter; - Gtk::Paned* hpanedl; - Gtk::Paned* hpanedr; - Gtk::Image *iHistoryShow, *iHistoryHide; - Gtk::Image *iTopPanel_1_Show, *iTopPanel_1_Hide; - Gtk::Image *iRightPanel_1_Show, *iRightPanel_1_Hide; - Gtk::Image *iShowHideSidePanels; - Gtk::Image *iShowHideSidePanels_exit; - Gtk::Image *iBeforeLockON, *iBeforeLockOFF; - Gtk::VBox *leftbox; - Gtk::VBox *vboxright; - - Gtk::Button* queueimg; - Gtk::Button* saveimgas; - Gtk::Button* sendtogimp; - Gtk::Button* navSync; - Gtk::Button* navNext; - Gtk::Button* navPrev; - - class ColorManagementToolbar; - std::unique_ptr colorMgmtToolBar; - - ImageAreaPanel* iareapanel; - PreviewHandler* previewHandler; - PreviewHandler* beforePreviewHandler; // for the before-after view - Navigator* navigator; - ImageAreaPanel* beforeIarea; // for the before-after view - Gtk::VBox* beforeBox; - Gtk::VBox* afterBox; - Gtk::Label* beforeLabel; - Gtk::Label* afterLabel; - Gtk::HBox* beforeAfterBox; - Gtk::HBox* beforeHeaderBox; - Gtk::HBox* afterHeaderBox; - - Gtk::Frame* ppframe; - ProfilePanel* profilep; - History* history; - HistogramPanel* histogramPanel; - ToolPanelCoordinator* tpc; - RTWindow* parent; - //SaveAsDialog* saveAsDialog; - BatchToolPanelCoordinator* btpCoordinator; - FilePanel* fPanel; - - bool firstProcessingDone; - - Thumbnail* openThm; // may get invalid on external delete event - Glib::ustring fname; // must be saved separately - - rtengine::InitialImage* isrc; - rtengine::StagedImageProcessor* ipc; - rtengine::StagedImageProcessor* beforeIpc; // for the before-after view - - EditorPanelIdleHelper* epih; - - void close (); - - BatchQueueEntry* createBatchQueueEntry (); - bool idle_imageSaved (ProgressConnector *pc, rtengine::IImage16* img, Glib::ustring fname, SaveFormat sf); - bool idle_saveImage (ProgressConnector *pc, Glib::ustring fname, SaveFormat sf); - bool idle_sendToGimp ( ProgressConnector *pc, Glib::ustring fname); - bool idle_sentToGimp (ProgressConnector *pc, rtengine::IImage16* img, Glib::ustring filename); - int err; - - time_t processingStartedTime; - - sigc::connection ShowHideSidePanelsconn; - - bool isProcessing; - - public: explicit EditorPanel (FilePanel* filePanel = nullptr); - virtual ~EditorPanel (); + ~EditorPanel (); void open (Thumbnail* tmb, rtengine::InitialImage* isrc); void setAspect (); @@ -214,7 +129,93 @@ public: void updateTabsUsesIcons (bool useIcons); void updateHistogramPosition (int oldPosition, int newPosition); - Gtk::Paned *catalogPane; + Gtk::Paned* catalogPane; + +private: + void close (); + + BatchQueueEntry* createBatchQueueEntry (); + bool idle_imageSaved (ProgressConnector *pc, rtengine::IImage16* img, Glib::ustring fname, SaveFormat sf); + bool idle_saveImage (ProgressConnector *pc, Glib::ustring fname, SaveFormat sf); + bool idle_sendToGimp ( ProgressConnector *pc, Glib::ustring fname); + bool idle_sentToGimp (ProgressConnector *pc, rtengine::IImage16* img, Glib::ustring filename); + + Glib::ustring lastSaveAsFileName; + bool realized; + + MyProgressBar *progressLabel; + Gtk::ToggleButton* info; + Gtk::ToggleButton* hidehp; + Gtk::ToggleButton* tbShowHideSidePanels; + Gtk::ToggleButton* tbTopPanel_1; + Gtk::ToggleButton* tbRightPanel_1; + Gtk::ToggleButton* tbBeforeLock; + //bool bAllSidePanelsVisible; + Gtk::ToggleButton* beforeAfter; + Gtk::Paned* hpanedl; + Gtk::Paned* hpanedr; + Gtk::Image *iHistoryShow, *iHistoryHide; + Gtk::Image *iTopPanel_1_Show, *iTopPanel_1_Hide; + Gtk::Image *iRightPanel_1_Show, *iRightPanel_1_Hide; + Gtk::Image *iShowHideSidePanels; + Gtk::Image *iShowHideSidePanels_exit; + Gtk::Image *iBeforeLockON, *iBeforeLockOFF; + Gtk::VBox *leftbox; + Gtk::VBox *vboxright; + + Gtk::Button* queueimg; + Gtk::Button* saveimgas; + Gtk::Button* sendtogimp; + Gtk::Button* navSync; + Gtk::Button* navNext; + Gtk::Button* navPrev; + + class ColorManagementToolbar; + std::unique_ptr colorMgmtToolBar; + + ImageAreaPanel* iareapanel; + PreviewHandler* previewHandler; + PreviewHandler* beforePreviewHandler; // for the before-after view + Navigator* navigator; + ImageAreaPanel* beforeIarea; // for the before-after view + Gtk::VBox* beforeBox; + Gtk::VBox* afterBox; + Gtk::Label* beforeLabel; + Gtk::Label* afterLabel; + Gtk::HBox* beforeAfterBox; + Gtk::HBox* beforeHeaderBox; + Gtk::HBox* afterHeaderBox; + + Gtk::Frame* ppframe; + ProfilePanel* profilep; + History* history; + HistogramPanel* histogramPanel; + ToolPanelCoordinator* tpc; + RTWindow* parent; + //SaveAsDialog* saveAsDialog; + BatchToolPanelCoordinator* btpCoordinator; + FilePanel* fPanel; + + bool firstProcessingDone; + + Thumbnail* openThm; // may get invalid on external delete event + Glib::ustring fname; // must be saved separately + + rtengine::InitialImage* isrc; + rtengine::StagedImageProcessor* ipc; + rtengine::StagedImageProcessor* beforeIpc; // for the before-after view + + EditorPanelIdleHelper* epih; + + int err; + + time_t processingStartedTime; + + sigc::connection ShowHideSidePanelsconn; + + bool isProcessing; + + IdleRegister idle_register; }; #endif diff --git a/rtgui/filepanel.cc b/rtgui/filepanel.cc index 8c7bae0fa..ed9b70e8d 100644 --- a/rtgui/filepanel.cc +++ b/rtgui/filepanel.cc @@ -22,12 +22,6 @@ #include "inspector.h" #include "placesbrowser.h" -int FilePanelInitUI (void* data) -{ - (static_cast(data))->init (); - return 0; -} - FilePanel::FilePanel () : parent(nullptr) { @@ -143,13 +137,21 @@ FilePanel::FilePanel () : parent(nullptr) fileCatalog->setFileSelectionChangeListener (tpc); fileCatalog->setFileSelectionListener (this); - add_idle (FilePanelInitUI, this); + + const auto func = [](gpointer data) -> gboolean { + static_cast(data)->init(); + return FALSE; + }; + + idle_register.add(func, this); show_all (); } FilePanel::~FilePanel () { + idle_register.destroy(); + rightNotebookSwitchConn.disconnect(); if (inspectorPanel) { diff --git a/rtgui/filepanel.h b/rtgui/filepanel.h index 68f021043..d54f5f75a 100644 --- a/rtgui/filepanel.h +++ b/rtgui/filepanel.h @@ -33,38 +33,12 @@ #include "progressconnector.h" class RTWindow; -class FilePanel : public Gtk::HPaned, + +class FilePanel final : + public Gtk::HPaned, public FileSelectionListener, public PParamsChangeListener { - -protected: - //DirBrowser* dirBrowser; - PlacesBrowser* placesBrowser; - RecentBrowser* recentBrowser; - // FileCatalog* fileCatalog; // filecatalog is the file browser with the button bar above it - - Inspector* inspectorPanel; - Gtk::VPaned* tpcPaned; - BatchToolPanelCoordinator* tpc; - History* history; - //FilterPanel* filterPanel; - RTWindow* parent; - Gtk::Notebook* rightNotebook; - sigc::connection rightNotebookSwitchConn; - - struct pendingLoad { - bool complete; - ProgressConnector *pc; - Thumbnail *thm; - }; - MyMutex pendingLoadMutex; - std::vector pendingLoads; - - int error; - - void on_NB_switch_page(Gtk::Widget* page, guint page_num); - public: FilePanel (); ~FilePanel (); @@ -107,6 +81,32 @@ public: bool handleShortcutKey (GdkEventKey* event); void updateTPVScrollbar (bool hide); void updateTabsUsesIcons (bool useIcons); + +private: + void on_NB_switch_page(Gtk::Widget* page, guint page_num); + + PlacesBrowser* placesBrowser; + RecentBrowser* recentBrowser; + + Inspector* inspectorPanel; + Gtk::VPaned* tpcPaned; + BatchToolPanelCoordinator* tpc; + History* history; + RTWindow* parent; + Gtk::Notebook* rightNotebook; + sigc::connection rightNotebookSwitchConn; + + struct pendingLoad { + bool complete; + ProgressConnector *pc; + Thumbnail *thm; + }; + MyMutex pendingLoadMutex; + std::vector pendingLoads; + + int error; + + IdleRegister idle_register; }; #endif diff --git a/rtgui/guiutils.h b/rtgui/guiutils.h index 422a99a45..12370a571 100644 --- a/rtgui/guiutils.h +++ b/rtgui/guiutils.h @@ -19,9 +19,7 @@ #ifndef __GUI_UTILS_ #define __GUI_UTILS_ -#include -#include -#include +#include #include From d5d8747e5ab436995fe258a1ec8d9f165cce369f Mon Sep 17 00:00:00 2001 From: heckflosse Date: Sat, 11 Feb 2017 17:48:59 +0100 Subject: [PATCH 06/16] decode compressed xtrans files --- rtengine/dcraw.cc | 8 + rtengine/dcraw.h | 62 +++ rtengine/rawimage.cc | 6 + rtengine/xtranscompressed.cc | 775 +++++++++++++++++++++++++++++++++++ 4 files changed, 851 insertions(+) create mode 100644 rtengine/xtranscompressed.cc diff --git a/rtengine/dcraw.cc b/rtengine/dcraw.cc index 8623d26cd..a35e3454d 100644 --- a/rtengine/dcraw.cc +++ b/rtengine/dcraw.cc @@ -11,6 +11,8 @@ /*RT*/#define DJGPP #include "opthelper.h" +#include +#include /* dcraw.c -- Dave Coffin's raw photo decoder Copyright 1997-2016 by Dave Coffin, dcoffin a cybercom o net @@ -6264,6 +6266,7 @@ void CLASS apply_tiff() tile_width = tiff_ifd[i].tile_width; tile_length = tiff_ifd[i].tile_length; shutter = tiff_ifd[i].shutter; + raw_size = tiff_ifd[i].bytes; raw = i; } } @@ -9016,6 +9019,9 @@ canon_a5: if (filters == 9) FORC(36) ((char *)xtrans)[c] = xtrans_abs[(c/6+top_margin) % 6][(c+left_margin) % 6]; + if(filters == 9 && raw_height * raw_width * 2 != raw_size) { + xtransCompressed = true; + } } else if (!strcmp(model,"KD-400Z")) { height = 1712; width = 2312; @@ -9865,6 +9871,8 @@ struct tiff_hdr { char desc[512], make[64], model[64], soft[32], date[20], artist[64]; }; +#include "xtranscompressed.cc" + /* RT: Delete from here */ /*RT*/#undef SQR /*RT*/#undef MAX diff --git a/rtengine/dcraw.h b/rtengine/dcraw.h index 0a68b02cc..1ea938cc7 100644 --- a/rtengine/dcraw.h +++ b/rtengine/dcraw.h @@ -89,8 +89,51 @@ protected: unsigned black, cblack[4102], maximum, mix_green, raw_color, zero_is_bad; unsigned zero_after_ff, is_raw, dng_version, is_foveon, data_error; unsigned tile_width, tile_length, gpsdata[32], load_flags; + bool xtransCompressed = false; + struct xtrans_params + { + char *q_table; /* quantization table */ + int q_point[5]; /* quantization points */ + int max_bits; + int min_value; + int raw_bits; + int total_values; + int maxDiff; + ushort line_width; + }; + + struct int_pair { + int value1; + int value2; + }; + + enum _xt_lines + { + _R0=0,_R1,_R2,_R3,_R4, + _G0,_G1,_G2,_G3,_G4,_G5,_G6,_G7, + _B0,_B1,_B2,_B3,_B4, + _ltotal + }; + + struct xtrans_block { + int cur_bit; // current bit being read (from left to right) + int cur_pos; // current position in a buffer + INT64 cur_buf_offset; // offset of this buffer in a file + unsigned max_read_size; // Amount of data to be read + int cur_buf_size; // buffer size + uchar *cur_buf; // currently read block + IMFILE *input; + struct int_pair grad_even[3][41]; // tables of gradients + struct int_pair grad_odd[3][41]; + ushort *linealloc; + ushort *linebuf[_ltotal]; + }; + + int fuji_total_lines, fuji_total_blocks, fuji_block_width, fuji_bits; + ushort raw_height, raw_width, height, width, top_margin, left_margin; ushort shrink, iheight, iwidth, fuji_width, thumb_width, thumb_height; + unsigned raw_size; ushort *raw_image; float * float_raw_image; ushort white[8][8], curve[0x10000], cr2_slice[3], sraw_mul[4]; @@ -226,6 +269,25 @@ void adobe_copy_pixel (unsigned row, unsigned col, ushort **rp); void lossless_dng_load_raw(); void packed_dng_load_raw(); void deflate_dng_load_raw(); +void init_xtrans(struct xtrans_params* info); +void fuji_fill_buffer(struct xtrans_block *info); +void init_xtrans_block(struct xtrans_block* info, const struct xtrans_params *params, INT64 raw_offset, unsigned dsize); +void copy_line_to_xtrans(struct xtrans_block* info, int cur_line, int cur_block, int cur_block_width); +void fuji_zerobits(struct xtrans_block* info, int *count); +void fuji_read_code(struct xtrans_block* info, int *data, int bits_to_read); +int bitDiff(int value1, int value2); +int fuji_decode_sample_even(struct xtrans_block* info, const struct xtrans_params * params, ushort* line_buf, int pos, struct int_pair* grads); +int fuji_decode_sample_odd(struct xtrans_block* info, const struct xtrans_params * params, ushort* line_buf, int pos, struct int_pair* grads); +void fuji_decode_interpolation_even(int line_width, ushort* line_buf, int pos); +void xtrans_extend_generic(ushort *linebuf[_ltotal], int line_width, int start, int end); +void xtrans_extend_red(ushort *linebuf[_ltotal], int line_width); +void xtrans_extend_green(ushort *linebuf[_ltotal], int line_width); +void xtrans_extend_blue(ushort *linebuf[_ltotal], int line_width); +void xtrans_decode_block(struct xtrans_block* info, const struct xtrans_params *params, int cur_line); +void xtrans_decode_strip(const struct xtrans_params* info_common, int cur_block, INT64 raw_offset, unsigned dsize); +void xtrans_compressed_load_raw(); +void xtrans_decode_loop(const struct xtrans_params* common_info, int count, INT64* raw_block_offsets, unsigned *block_sizes); +void parse_xtrans_header(); void pentax_load_raw(); void nikon_load_raw(); int nikon_is_compressed(); diff --git a/rtengine/rawimage.cc b/rtengine/rawimage.cc index dc91da64d..c10cc5abd 100644 --- a/rtengine/rawimage.cc +++ b/rtengine/rawimage.cc @@ -14,6 +14,8 @@ #include #endif +#include + namespace rtengine { @@ -435,6 +437,10 @@ int RawImage::loadRaw (bool loadData, bool closeFile, ProgressListener *plistene return 2; } + if(xtransCompressed) { + parse_xtrans_header(); + } + if (flip == 5) { this->rotate_deg = 270; } else if (flip == 3) { diff --git a/rtengine/xtranscompressed.cc b/rtengine/xtranscompressed.cc new file mode 100644 index 000000000..ed91df841 --- /dev/null +++ b/rtengine/xtranscompressed.cc @@ -0,0 +1,775 @@ +/* -*- C++ -*- + * File: xtranscompressed.cpp + * Copyright (C) 2016 Alexey Danilchenko + * + * Adopted to LibRaw by Alex Tutubalin, lexa@lexa.ru + * LibRaw Fujifilm/compressed decoder + + * Adopted to RawTherapee by Ingo Weyrich +LibRaw is free software; you can redistribute it and/or modify +it under the terms of the one of three licenses as you choose: + +1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1 + (See file LICENSE.LGPL provided in LibRaw distribution archive for details). + +2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + (See file LICENSE.CDDL provided in LibRaw distribution archive for details). + + */ + +void CLASS init_xtrans(struct xtrans_params* info) +{ + int cur_val, i; + char *qt; + + if (fuji_block_width % 3) + derror(); + + info->q_table = (char *) malloc(32768); + merror(info->q_table, "init_xtrans()"); + + info->line_width = (fuji_block_width*2)/3; + + info->q_point[0] = 0; + info->q_point[1] = 0x12; + info->q_point[2] = 0x43; + info->q_point[3] = 0x114; + info->q_point[4] = (1 << fuji_bits) - 1; + info->min_value = 0x40; + + cur_val = -info->q_point[4]; + for (qt = info->q_table; cur_val<=info->q_point[4]; ++qt, ++cur_val) + { + if (cur_val<= -info->q_point[3]) + *qt = -4; + else if (cur_val<= -info->q_point[2]) + *qt = -3; + else if (cur_val<= -info->q_point[1]) + *qt = -2; + else if (cur_val< 0) + *qt = -1; + else if (cur_val== 0) + *qt = 0; + else if (cur_val< info->q_point[1]) + *qt = 1; + else if (cur_val< info->q_point[2]) + *qt = 2; + else if (cur_val< info->q_point[3]) + *qt = 3; + else + *qt = 4; + } + + // populting gradients + if (info->q_point[4] == 0x3FFF) + { + info->total_values = 0x4000; + info->raw_bits = 14; + info->max_bits = 56; + info->maxDiff = 256; + } + else if (info->q_point[4] == 0xFFF) + { + info->total_values = 4096; + info->raw_bits = 12; + info->max_bits = 48; + info->maxDiff = 64; + } + else + derror(); +} + +#define XTRANS_BUF_SIZE 0x10000u + +void CLASS fuji_fill_buffer(struct xtrans_block *info) +{ + if (info->cur_pos >= info->cur_buf_size) + { + info->cur_pos = 0; + info->cur_buf_offset += info->cur_buf_size; +#ifdef _OPENMP +#pragma omp critical +#endif + { + fseek(info->input, info->cur_buf_offset, SEEK_SET); + info->cur_buf_size = fread(info->cur_buf, 1, std::min(info->max_read_size,XTRANS_BUF_SIZE), info->input); + if(info->cur_buf_size<1) // nothing read + ;//throw LIBRAW_EXCEPTION_IO_EOF; + info->max_read_size -= info->cur_buf_size; + + } + } +} + +#define _min(a,b) ((a) < (b) ? (a) : (b)) + +void CLASS init_xtrans_block(struct xtrans_block* info, const struct xtrans_params *params, INT64 raw_offset, unsigned dsize) +{ + info->linealloc = (ushort*)calloc(sizeof(ushort),_ltotal*(params->line_width+2)); + merror(info->linealloc, "init_xtrans_block()"); + + info->input = ifp; + INT64 fsize = info->input->size; + info->max_read_size = _min(unsigned(fsize-raw_offset),dsize+16); // Data size may be incorrect? + + info->linebuf[_R0] = info->linealloc; + for(int i = _R1; i<=_B4;i++) + info->linebuf[i] = info->linebuf[i-1] + params->line_width + 2; + + // init buffer + info->cur_buf = (uchar*)malloc(XTRANS_BUF_SIZE); + merror(info->cur_buf, "init_xtrans_block()"); + info->cur_bit = 0; + info->cur_pos = 0; + info->cur_buf_offset = raw_offset; + for(int j=0; j < 3; j++) + for (int i=0; i<41; i++) + { + info->grad_even[j][i].value1 = params->maxDiff; + info->grad_even[j][i].value2 = 1; + info->grad_odd[j][i].value1 = params->maxDiff; + info->grad_odd[j][i].value2 = 1; + } + + info->cur_buf_size = 0; + fuji_fill_buffer(info); +} + +void CLASS copy_line_to_xtrans(struct xtrans_block* info, int cur_line, int cur_block, int cur_block_width) +{ + ushort *lineBufB[3]; + ushort *lineBufG[6]; + ushort *lineBufR[3]; + unsigned pixel_count; + ushort* line_buf; + int index; + + int offset = fuji_block_width*cur_block + 6 * raw_width * cur_line; + ushort* raw_block_data = raw_image + offset; + int row_count = 0; + + for(int i = 0; i<3; i++) + { + lineBufR[i] = info->linebuf[_R2+i] + 1; + lineBufB[i] = info->linebuf[_B2+i] + 1; + } + for(int i = 0; i<6; i++) + lineBufG[i] = info->linebuf[_G2+i] + 1; + + while (row_count < 6) + { + pixel_count = 0; + while (pixel_count < cur_block_width) + { + switch (xtrans_abs[row_count][(pixel_count % 6)]) + { + case 0: // red + line_buf = lineBufR[row_count >> 1]; + break; + case 1: // green + line_buf = lineBufG[row_count]; + break; + case 2: // blue + line_buf = lineBufB[row_count >> 1]; + break; + } + + index = (((pixel_count*2/3) & 0x7FFFFFFE) | (pixel_count % 3) & 1) + ((pixel_count % 3) >> 1); + raw_block_data[pixel_count] = line_buf[index]; + + ++pixel_count; + } + ++row_count; + raw_block_data += raw_width; + } +} + +#define fuji_quant_gradient(i,v1,v2) (9*i->q_table[i->q_point[4]+(v1)] + i->q_table[i->q_point[4]+(v2)]) + +void CLASS fuji_zerobits(struct xtrans_block* info, int *count) +{ + uchar zero = 0; + *count = 0; + while (zero == 0) + { + zero = (info->cur_buf[info->cur_pos] >> (7 - info->cur_bit)) & 1; + info->cur_bit++; + info->cur_bit &= 7; + if (!info->cur_bit) + { + ++info->cur_pos; + fuji_fill_buffer(info); + } + if (zero) + break; + ++*count; + } +} + +void CLASS fuji_read_code(struct xtrans_block* info, int *data, int bits_to_read) +{ + uchar bits_left = bits_to_read; + uchar bits_left_in_byte = 8 - (info->cur_bit & 7); + *data = 0; + if (!bits_to_read) + return; + if (bits_to_read >= bits_left_in_byte) + { + do + { + *data <<= bits_left_in_byte; + bits_left -= bits_left_in_byte; + *data |= info->cur_buf[info->cur_pos] & ((1 << bits_left_in_byte) - 1); + ++info->cur_pos; + fuji_fill_buffer(info); + bits_left_in_byte = 8; + } + while (bits_left >= 8); + } + if (!bits_left) + { + info->cur_bit = (8 - (bits_left_in_byte & 7)) & 7; + return; + } + *data <<= bits_left; + bits_left_in_byte -= bits_left; + *data |= ((1 << bits_left) - 1) & ((unsigned)info->cur_buf[info->cur_pos] >> bits_left_in_byte); + info->cur_bit = (8 - (bits_left_in_byte & 7)) & 7; +} + +int CLASS bitDiff(int value1, int value2) +{ + int decBits = 0; + if ( value2 < value1 ) + while (decBits <= 12 && + (value2 << ++decBits) + < value1) + ; + return decBits; +} + +#define _abs(x) (((int)(x) ^ ((int)(x) >> 31)) - ((int)(x) >> 31)) + +int CLASS fuji_decode_sample_even(struct xtrans_block* info, const struct xtrans_params * params, ushort* line_buf, int pos, struct int_pair* grads) +{ + int interp_val = 0; + //ushort decBits; + int errcnt=0; + + int sample=0, code=0; + ushort* line_buf_cur = line_buf + pos; + int Rb = line_buf_cur[-2 - params->line_width]; + int Rc = line_buf_cur[-3 - params->line_width]; + int Rd = line_buf_cur[-1 - params->line_width]; + int Rf = line_buf_cur[-4 - 2*params->line_width]; + + int grad, gradient, diffRcRb, diffRfRb, diffRdRb; + + grad = fuji_quant_gradient(params, Rb - Rf, Rc - Rb); + gradient = _abs(grad); + diffRcRb = _abs(Rc - Rb); + diffRfRb = _abs(Rf - Rb); + diffRdRb = _abs(Rd - Rb); + + if ( diffRcRb > diffRfRb && diffRcRb > diffRdRb ) + interp_val = Rf + Rd + 2 * Rb; + else if ( diffRdRb > diffRcRb && diffRdRb > diffRfRb ) + interp_val = Rf + Rc + 2 * Rb; + else + interp_val = Rd + Rc + 2 * Rb; + + + fuji_zerobits(info, &sample); + + if (sample < params->max_bits - params->raw_bits - 1) + { + int decBits = bitDiff(grads[gradient].value1, grads[gradient].value2); + fuji_read_code(info, &code, decBits); + code += sample << decBits; + } + else + { + fuji_read_code(info, &code, params->raw_bits); + code++; + } + + if (code < 0 || code >= params->total_values) + errcnt++; + + if (code & 1) + code = -1 - code/2; + else + code /= 2; + + grads[gradient].value1 += _abs(code); + if (grads[gradient].value2 == params->min_value ) + { + grads[gradient].value1 >>= 1; + grads[gradient].value2 >>= 1; + } + grads[gradient].value2++; + if (grad < 0) + interp_val = (interp_val >> 2) - code; + else + interp_val = (interp_val >> 2) + code; + if ( interp_val < 0 ) + interp_val += params->total_values; + else if (interp_val > params->q_point[4]) + interp_val -= params->total_values; + + if ( interp_val >= 0 ) + line_buf_cur[0] = _min(interp_val, params->q_point[4]); + else + line_buf_cur[0] = 0; + return errcnt; +} + +int CLASS fuji_decode_sample_odd(struct xtrans_block* info, const struct xtrans_params * params, ushort* line_buf, int pos, struct int_pair* grads) +{ + int interp_val = 0; + int errcnt = 0; + + int sample=0, code=0; + ushort* line_buf_cur = line_buf + pos; + int Ra = line_buf_cur[-1]; + int Rb = line_buf_cur[-2 - params->line_width]; + int Rc = line_buf_cur[-3 - params->line_width]; + int Rd = line_buf_cur[-1 - params->line_width]; + int Rg = line_buf_cur[1]; + + int grad, gradient; + + grad = fuji_quant_gradient(params, Rb - Rc, Rc - Ra); + gradient = _abs(grad); + + if ((Rb > Rc && Rb > Rd) || (Rb < Rc && Rb < Rd)) + interp_val = (Rg + Ra + 2 * Rb) >> 2; + else + interp_val = (Ra + Rg) >> 1; + + fuji_zerobits(info, &sample); + + if (sample < params->max_bits - params->raw_bits - 1) + { + int decBits = bitDiff(grads[gradient].value1, grads[gradient].value2); + fuji_read_code(info, &code, decBits); + code += sample << decBits; + } + else + { + fuji_read_code(info, &code, params->raw_bits); + code++; + } + + if (code < 0 || code >= params->total_values) + errcnt++; + + if (code & 1) + code = -1 - code/2; + else + code /= 2; + + grads[gradient].value1 += _abs(code); + if (grads[gradient].value2 == params->min_value) + { + grads[gradient].value1 >>= 1; + grads[gradient].value2 >>= 1; + } + grads[gradient].value2++; + if (grad < 0) + interp_val -= code; + else + interp_val += code; + if ( interp_val < 0 ) + interp_val += params->total_values; + else if (interp_val > params->q_point[4]) + interp_val -= params->total_values; + + if ( interp_val >= 0 ) + line_buf_cur[0] = _min(interp_val, params->q_point[4]); + else + line_buf_cur[0] = 0; + return errcnt; +} + +void CLASS fuji_decode_interpolation_even(int line_width, ushort* line_buf, int pos) +{ + ushort* line_buf_cur = line_buf + pos; + int Rb = line_buf_cur[-2 - line_width]; + int Rc = line_buf_cur[-3 - line_width]; + int Rd = line_buf_cur[-1 - line_width]; + int Rf = line_buf_cur[-4 - 2*line_width]; + int diffRcRb = _abs(Rc - Rb); + int diffRfRb = _abs(Rf - Rb); + int diffRdRb = _abs(Rd - Rb); + if ( diffRcRb > diffRfRb && diffRcRb > diffRdRb ) + *line_buf_cur = (Rf + Rd + 2 * Rb) >> 2; + else if ( diffRdRb > diffRcRb && diffRdRb > diffRfRb ) + *line_buf_cur = (Rf + Rc + 2 * Rb) >> 2; + else + *line_buf_cur = (Rd + Rc + 2 * Rb) >> 2; +} + +void CLASS xtrans_extend_generic(ushort *linebuf[_ltotal], int line_width, int start, int end) +{ + for(int i = start; i<= end; i++) + { + linebuf[i][0] = linebuf[i-1][1]; + linebuf[i][line_width + 1] = linebuf[i-1][line_width]; + } +} + +void CLASS xtrans_extend_red(ushort *linebuf[_ltotal], int line_width) +{ + xtrans_extend_generic(linebuf,line_width,_R2,_R4); +} + +void CLASS xtrans_extend_green(ushort *linebuf[_ltotal], int line_width) +{ + xtrans_extend_generic(linebuf,line_width,_G2,_G7); +} + +void CLASS xtrans_extend_blue(ushort *linebuf[_ltotal], int line_width) +{ + xtrans_extend_generic(linebuf,line_width,_B2,_B4); +} + +void CLASS xtrans_decode_block(struct xtrans_block* info, const struct xtrans_params *params, int cur_line) +{ + int r_even_pos = 0, r_odd_pos = 1; + int g_even_pos = 0, g_odd_pos = 1; + int b_even_pos = 0, b_odd_pos = 1; + + int errcnt = 0; + + const int line_width = params->line_width; + + while (g_even_pos < line_width || g_odd_pos < line_width) + { + if (g_even_pos < line_width) + { + fuji_decode_interpolation_even(line_width, info->linebuf[_R2] + 1, r_even_pos); + r_even_pos += 2; + errcnt+=fuji_decode_sample_even(info, params, info->linebuf[_G2] + 1, g_even_pos, info->grad_even[0]); + g_even_pos += 2; + } + if (g_even_pos > 8) + { + errcnt+=fuji_decode_sample_odd(info, params, info->linebuf[_R2] + 1, r_odd_pos, info->grad_odd[0]); + r_odd_pos += 2; + errcnt+=fuji_decode_sample_odd(info, params, info->linebuf[_G2] + 1, g_odd_pos, info->grad_odd[0]); + g_odd_pos += 2; + } + } + + xtrans_extend_red(info->linebuf,line_width); + xtrans_extend_green(info->linebuf,line_width); + + g_even_pos = 0, g_odd_pos = 1; + + while (g_even_pos < line_width || g_odd_pos < line_width) + { + if (g_even_pos < line_width) + { + errcnt+=fuji_decode_sample_even(info, params, info->linebuf[_G3] + 1, g_even_pos, info->grad_even[1]); + g_even_pos += 2; + fuji_decode_interpolation_even(line_width,info->linebuf[_B2] + 1, b_even_pos); + b_even_pos += 2; + } + if (g_even_pos > 8) + { + errcnt+=fuji_decode_sample_odd(info,params, info->linebuf[_G3] + 1, g_odd_pos, info->grad_odd[1]); + g_odd_pos += 2; + errcnt+=fuji_decode_sample_odd(info,params, info->linebuf[_B2] + 1, b_odd_pos, info->grad_odd[1]); + b_odd_pos += 2; + } + } + + xtrans_extend_green(info->linebuf,line_width); + xtrans_extend_blue(info->linebuf,line_width); + + r_even_pos = 0, r_odd_pos = 1; + g_even_pos = 0, g_odd_pos = 1; + + while (g_even_pos < line_width || g_odd_pos < line_width) + { + if (g_even_pos < line_width) + { + if (r_even_pos & 3) + errcnt+=fuji_decode_sample_even(info, params, info->linebuf[_R3] + 1, r_even_pos, info->grad_even[2]); + else + fuji_decode_interpolation_even(line_width, info->linebuf[_R3] + 1, r_even_pos); + r_even_pos += 2; + fuji_decode_interpolation_even(line_width, info->linebuf[_G4] + 1, g_even_pos); + g_even_pos += 2; + } + if (g_even_pos > 8) + { + errcnt+=fuji_decode_sample_odd(info, params,info->linebuf[_R3] + 1, r_odd_pos, info->grad_odd[2]); + r_odd_pos += 2; + errcnt+=fuji_decode_sample_odd(info,params, info->linebuf[_G4] + 1, g_odd_pos, info->grad_odd[2]); + g_odd_pos += 2; + } + } + + xtrans_extend_red(info->linebuf,line_width); + xtrans_extend_green(info->linebuf,line_width); + + g_even_pos = 0, g_odd_pos = 1; + b_even_pos = 0, b_odd_pos = 1; + + while (g_even_pos < line_width || g_odd_pos < line_width) + { + if (g_even_pos < line_width) + { + errcnt+=fuji_decode_sample_even(info,params, info->linebuf[_G5] + 1, g_even_pos, info->grad_even[0]); + g_even_pos += 2; + if ((b_even_pos & 3) == 2) + fuji_decode_interpolation_even(line_width, info->linebuf[_B3] + 1, b_even_pos); + else + errcnt+=fuji_decode_sample_even(info, params,info->linebuf[_B3] + 1, b_even_pos, info->grad_even[0]); + b_even_pos += 2; + } + if (g_even_pos > 8) + { + errcnt+=fuji_decode_sample_odd(info, params,info->linebuf[_G5] + 1, g_odd_pos, info->grad_odd[0]); + g_odd_pos += 2; + errcnt+=fuji_decode_sample_odd(info, params,info->linebuf[_B3] + 1, b_odd_pos, info->grad_odd[0]); + b_odd_pos += 2; + } + } + + xtrans_extend_green(info->linebuf,line_width); + xtrans_extend_blue(info->linebuf,line_width); + + r_even_pos = 0, r_odd_pos = 1; + g_even_pos = 0, g_odd_pos = 1; + + while (g_even_pos < line_width || g_odd_pos < line_width) + { + if (g_even_pos < line_width) + { + if ((r_even_pos & 3) == 2) + fuji_decode_interpolation_even(line_width, info->linebuf[_R4] + 1, r_even_pos); + else + errcnt+=fuji_decode_sample_even(info,params, info->linebuf[_R4] + 1, r_even_pos, info->grad_even[1]); + r_even_pos += 2; + errcnt+=fuji_decode_sample_even(info,params, info->linebuf[_G6] + 1, g_even_pos, info->grad_even[1]); + g_even_pos += 2; + } + if (g_even_pos > 8) + { + errcnt+=fuji_decode_sample_odd(info,params, info->linebuf[_R4] + 1, r_odd_pos, info->grad_odd[1]); + r_odd_pos += 2; + errcnt+=fuji_decode_sample_odd(info,params, info->linebuf[_G6] + 1, g_odd_pos, info->grad_odd[1]); + g_odd_pos += 2; + } + } + + xtrans_extend_red(info->linebuf,line_width); + xtrans_extend_green(info->linebuf,line_width); + + g_even_pos = 0, g_odd_pos = 1; + b_even_pos = 0, b_odd_pos = 1; + + while (g_even_pos < line_width || g_odd_pos < line_width) + { + if (g_even_pos < line_width) + { + fuji_decode_interpolation_even(line_width, info->linebuf[_G7] + 1, g_even_pos); + g_even_pos += 2; + if (b_even_pos & 3) + errcnt+=fuji_decode_sample_even(info,params, info->linebuf[_B4] + 1, b_even_pos, info->grad_even[2]); + else + fuji_decode_interpolation_even(line_width, info->linebuf[_B4] + 1, b_even_pos); + b_even_pos += 2; + } + if (g_even_pos > 8) + { + errcnt+=fuji_decode_sample_odd(info, params,info->linebuf[_G7] + 1, g_odd_pos, info->grad_odd[2]); + g_odd_pos += 2; + errcnt+=fuji_decode_sample_odd(info, params,info->linebuf[_B4] + 1, b_odd_pos, info->grad_odd[2]); + b_odd_pos += 2; + } + } + + xtrans_extend_green(info->linebuf,line_width); + xtrans_extend_blue(info->linebuf,line_width); + + if(errcnt) + derror(); +} + +void CLASS xtrans_decode_strip(const struct xtrans_params* info_common, int cur_block, INT64 raw_offset, unsigned dsize) +{ + int cur_block_width, cur_line; + unsigned line_size; + struct xtrans_block info; + + init_xtrans_block(&info, info_common, raw_offset,dsize); + line_size = sizeof(ushort)*(info_common->line_width+2); + + cur_block_width = fuji_block_width; + if (cur_block+1 == fuji_total_blocks) + cur_block_width = raw_width % fuji_block_width; + + struct i_pair + { + int a,b; + }; + const i_pair mtable[6] = { {_R0,_R3},{_R1,_R4},{_G0,_G6},{_G1,_G7},{_B0,_B3},{_B1,_B4}}, + ztable[3]= {{_R2,3},{_G2,6},{_B2,3}}; + for (cur_line = 0; cur_line < fuji_total_lines; cur_line++) + { + xtrans_decode_block(&info, info_common, cur_line); + + // copy data from line buffers and advance + for(int i=0; i < 6; i++) + memcpy(info.linebuf[mtable[i].a], info.linebuf[mtable[i].b], line_size); + + copy_line_to_xtrans(&info, cur_line, cur_block, cur_block_width); + + for(int i=0; i < 3; i++) + { + memset(info.linebuf[ztable[i].a], 0, ztable[i].b*line_size); + info.linebuf[ztable[i].a][0] = info.linebuf[ztable[i].a-1][1]; + info.linebuf[ztable[i].a][info_common->line_width + 1] = info.linebuf[ztable[i].a-1][info_common->line_width]; + } + } + + // release data + free(info.linealloc); + free(info.cur_buf); +} + +static unsigned sgetn(int n, uchar *s) +{ + unsigned result = 0; + while (n-- > 0) + result = (result<<8) | (*s++); + return result; +} + +void CLASS xtrans_compressed_load_raw() +{ + struct xtrans_params common_info; + int cur_block; + unsigned line_size, *block_sizes; + INT64 raw_offset, *raw_block_offsets; + //struct xtrans_block info; + + init_xtrans(&common_info); + line_size = sizeof(ushort)*(common_info.line_width+2); + + // read block sizes + block_sizes = (unsigned*) malloc(sizeof(unsigned)* fuji_total_blocks); + merror(block_sizes, "xtrans_load_raw()"); + raw_block_offsets = (INT64*) malloc(sizeof(INT64)*fuji_total_blocks); + merror(raw_block_offsets, "xtrans_load_raw()"); + + raw_offset = sizeof(unsigned)*fuji_total_blocks; + if (raw_offset & 0xC) + raw_offset += 0x10 - (raw_offset & 0xC); + + raw_offset += data_offset; + + fseek (ifp, data_offset, SEEK_SET); + fread (block_sizes, 1, sizeof(unsigned)*fuji_total_blocks, ifp); + + raw_block_offsets[0] = raw_offset; + // calculating raw block offsets + for (cur_block = 0; cur_block < fuji_total_blocks; cur_block++) + { + unsigned bsize = sgetn(4, (uchar *)(block_sizes+cur_block)); + block_sizes[cur_block] = bsize; + } + + for (cur_block = 1; cur_block < fuji_total_blocks; cur_block++) + raw_block_offsets[cur_block] = raw_block_offsets[cur_block-1] + block_sizes[cur_block-1] ; + + xtrans_decode_loop(&common_info,fuji_total_blocks,raw_block_offsets,block_sizes); + + free(block_sizes); + free(raw_block_offsets); + free(common_info.q_table); +} + +void CLASS xtrans_decode_loop(const struct xtrans_params* common_info, int count, INT64* raw_block_offsets, unsigned *block_sizes) +{ + int cur_block; +#ifdef _OPENMP +#pragma omp parallel for private(cur_block) +#endif + for (cur_block = 0; cur_block < count ; cur_block++) + { + xtrans_decode_strip(common_info, cur_block, raw_block_offsets[cur_block], block_sizes[cur_block]); + } +} + + +void CLASS parse_xtrans_header() +{ + + uchar header[16]; + + ushort signature; + uchar version; + uchar h_raw_type; + uchar h_raw_bits; + ushort h_raw_height; + ushort h_raw_rounded_width; + ushort h_raw_width; + ushort h_block_size; + uchar h_blocks_in_row; + ushort h_total_lines; + + fseek(ifp, data_offset, SEEK_SET); + fread(header, 1, sizeof(header),ifp); + + signature = sgetn(2, header); + version = header[2]; + h_raw_type = header[3]; + h_raw_bits = header[4]; + h_raw_height = sgetn(2, header+5); + h_raw_rounded_width = sgetn(2, header+7); + h_raw_width = sgetn(2, header+9); + h_block_size = sgetn(2, header+11); + h_blocks_in_row = header[13]; + h_total_lines = sgetn(2, header+14); + + + // general validation + if (signature != 0x4953 + || version != 1 + || h_raw_height > 0x3000 + || h_raw_height < 6 + || h_raw_height % 6 + || h_raw_width > 0x3000 + || h_raw_width < 0x300 + || h_raw_width % 24 + || h_raw_rounded_width > 0x3000 + || h_raw_rounded_width < h_block_size + || h_raw_rounded_width % h_block_size + || h_raw_rounded_width - h_raw_width >= h_block_size + || h_block_size != 0x300 + || h_blocks_in_row > 0x10 + || h_blocks_in_row == 0 + || h_blocks_in_row != h_raw_rounded_width / h_block_size + || h_total_lines > 0x800 + || h_total_lines == 0 + || h_total_lines != h_raw_height / 6 + || (h_raw_bits != 12 && h_raw_bits != 14) + || h_raw_type != 16) { + xtransCompressed = false; + return; + } + // modify data + fuji_total_lines = h_total_lines; + fuji_total_blocks = h_blocks_in_row; + fuji_block_width = h_block_size; + fuji_bits = h_raw_bits; + raw_width = h_raw_width; + raw_height = h_raw_height; + data_offset += 16; + load_raw = &xtrans_compressed_load_raw; +} From f2bef65be14362367298bf096dd57c6d1fcda785 Mon Sep 17 00:00:00 2001 From: heckflosse Date: Sat, 11 Feb 2017 18:28:52 +0100 Subject: [PATCH 07/16] small cleanup --- rtengine/dcraw.cc | 3 +-- rtengine/rawimage.cc | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/rtengine/dcraw.cc b/rtengine/dcraw.cc index a35e3454d..3b6d98133 100644 --- a/rtengine/dcraw.cc +++ b/rtengine/dcraw.cc @@ -11,8 +11,7 @@ /*RT*/#define DJGPP #include "opthelper.h" -#include -#include + /* dcraw.c -- Dave Coffin's raw photo decoder Copyright 1997-2016 by Dave Coffin, dcoffin a cybercom o net diff --git a/rtengine/rawimage.cc b/rtengine/rawimage.cc index c10cc5abd..bfe7092f2 100644 --- a/rtengine/rawimage.cc +++ b/rtengine/rawimage.cc @@ -14,8 +14,6 @@ #include #endif -#include - namespace rtengine { From d8baa48e13f2e0d4fc655e87dab858aa212bff2f Mon Sep 17 00:00:00 2001 From: heckflosse Date: Sat, 11 Feb 2017 21:17:22 +0100 Subject: [PATCH 08/16] fixed compile issue in xtranscompressed --- rtengine/xtranscompressed.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtengine/xtranscompressed.cc b/rtengine/xtranscompressed.cc index ed91df841..18ccbe5bf 100644 --- a/rtengine/xtranscompressed.cc +++ b/rtengine/xtranscompressed.cc @@ -771,5 +771,5 @@ void CLASS parse_xtrans_header() raw_width = h_raw_width; raw_height = h_raw_height; data_offset += 16; - load_raw = &xtrans_compressed_load_raw; + load_raw = &CLASS xtrans_compressed_load_raw; } From 45578f32e67babb709b1871d8645e1a9edf39f2f Mon Sep 17 00:00:00 2001 From: Morgan Hardwood Date: Sat, 11 Feb 2017 23:24:44 +0100 Subject: [PATCH 09/16] Added NIKON D80 dual-illuminant DCP, closes #3669 --- rtdata/dcpprofiles/NIKON D80.dcp | Bin 0 -> 1045582 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rtdata/dcpprofiles/NIKON D80.dcp diff --git a/rtdata/dcpprofiles/NIKON D80.dcp b/rtdata/dcpprofiles/NIKON D80.dcp new file mode 100644 index 0000000000000000000000000000000000000000..965c358aa8e014a88270ebe5a66c424dc79072e0 GIT binary patch literal 1045582 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

YN%kJjs+28Ux3#Bdpa`%bK1E91p@p4qmt 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