diff --git a/rtdata/images/svg/histogram-ellipsis-small.svg b/rtdata/images/svg/histogram-ellipsis-small.svg new file mode 100644 index 000000000..4fdb17907 --- /dev/null +++ b/rtdata/images/svg/histogram-ellipsis-small.svg @@ -0,0 +1,135 @@ + + + + + + + + + + image/svg+xml + + + + + Lawrence Lee + + + + + + + + RawTherapee icon. + + + + + + + + + + + + + + + + + + + + diff --git a/rtdata/images/svg/histogram-bayer-on-small.svg b/rtdata/images/svg/histogram-type-histogram-raw-small.svg similarity index 100% rename from rtdata/images/svg/histogram-bayer-on-small.svg rename to rtdata/images/svg/histogram-type-histogram-raw-small.svg diff --git a/rtdata/images/svg/histogram-bayer-off-small.svg b/rtdata/images/svg/histogram-type-histogram-small.svg similarity index 59% rename from rtdata/images/svg/histogram-bayer-off-small.svg rename to rtdata/images/svg/histogram-type-histogram-small.svg index 5d6c439fb..1d2f6547e 100644 --- a/rtdata/images/svg/histogram-bayer-off-small.svg +++ b/rtdata/images/svg/histogram-type-histogram-small.svg @@ -1,6 +1,4 @@ - - + inkscape:version="1.0 (4035a4fb49, 2020-05-01)" + sodipodi:docname="histogram-type-histogram-small.svg"> + inkscape:snap-bbox-midpoints="false" + inkscape:document-rotation="0"> image/svg+xml - + - Maciej Dworak + Lawrence Lee @@ -100,40 +99,24 @@ inkscape:groupmode="layer" inkscape:label="Layer 1" transform="translate(0,-8)"> + + y="10" + x="2" + height="12" + width="12" + id="rect1467" + style="opacity:0.3;fill:#2a7fff;stroke-linecap:square;fill-opacity:1" /> - - - + style="opacity:1;fill:none;fill-opacity:0.3;stroke:#000000;stroke-width:0.999999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect849" + width="13" + height="13.000001" + x="1.5" + y="9.5" /> diff --git a/rtdata/images/svg/histogram-type-parade-small.svg b/rtdata/images/svg/histogram-type-parade-small.svg new file mode 100644 index 000000000..f299f8ab2 --- /dev/null +++ b/rtdata/images/svg/histogram-type-parade-small.svg @@ -0,0 +1,132 @@ + + + + + + + + + + image/svg+xml + + + + + Lawrence Lee + + + + + + + + RawTherapee icon. + + + + + + + + + + + + + + + + + + + + diff --git a/rtdata/images/svg/histogram-type-vectorscope-hc-small.svg b/rtdata/images/svg/histogram-type-vectorscope-hc-small.svg new file mode 100644 index 000000000..ef2e8b51f --- /dev/null +++ b/rtdata/images/svg/histogram-type-vectorscope-hc-small.svg @@ -0,0 +1,131 @@ + + + + + + + + + + image/svg+xml + + + + + Lawrence Lee + + + + + + + + RawTherapee icon. + + + + + + + + + + + + + + + + + + + + diff --git a/rtdata/images/svg/histogram-type-vectorscope-hs-small.svg b/rtdata/images/svg/histogram-type-vectorscope-hs-small.svg new file mode 100644 index 000000000..62bbf9586 --- /dev/null +++ b/rtdata/images/svg/histogram-type-vectorscope-hs-small.svg @@ -0,0 +1,135 @@ + + + + + + + + + + image/svg+xml + + + + + Lawrence Lee + + + + + + + + RawTherapee icon. + + + + + + + + + + + + + + + + + + + + + diff --git a/rtdata/images/svg/histogram-type-waveform-small.svg b/rtdata/images/svg/histogram-type-waveform-small.svg new file mode 100644 index 000000000..5147ab2fc --- /dev/null +++ b/rtdata/images/svg/histogram-type-waveform-small.svg @@ -0,0 +1,122 @@ + + + + + + + + + + image/svg+xml + + + + + Lawrence Lee + + + + + + + + RawTherapee icon. + + + + + + + + + + + + + + + + + + diff --git a/rtdata/languages/default b/rtdata/languages/default index 51d11b729..0263e99fd 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -246,11 +246,20 @@ GIMP_PLUGIN_INFO;Welcome to the RawTherapee GIMP plugin!\nOnce you are done edit HISTOGRAM_TOOLTIP_B;Show/Hide blue histogram. HISTOGRAM_TOOLTIP_BAR;Show/Hide RGB indicator bar. HISTOGRAM_TOOLTIP_CHRO;Show/Hide chromaticity histogram. +HISTOGRAM_TOOLTIP_CROSSHAIR;Show/Hide indicator crosshair. HISTOGRAM_TOOLTIP_G;Show/Hide green histogram. HISTOGRAM_TOOLTIP_L;Show/Hide CIELab luminance histogram. HISTOGRAM_TOOLTIP_MODE;Toggle between linear, log-linear and log-log scaling of the histogram. HISTOGRAM_TOOLTIP_R;Show/Hide red histogram. HISTOGRAM_TOOLTIP_RAW;Show/Hide raw histogram. +HISTOGRAM_TOOLTIP_SHOW_OPTIONS;Toggle visibility of the scope option buttons. +HISTOGRAM_TOOLTIP_TRACE_BRIGHTNESS;Adjust scope brightness. +HISTOGRAM_TOOLTIP_TYPE_HISTOGRAM;Histogram +HISTOGRAM_TOOLTIP_TYPE_HISTOGRAM_RAW;Raw Histogram +HISTOGRAM_TOOLTIP_TYPE_PARADE;RGB Parade +HISTOGRAM_TOOLTIP_TYPE_WAVEFORM;Waveform +HISTOGRAM_TOOLTIP_TYPE_VECTORSCOPE_HC;Hue-Chroma Vectorscope +HISTOGRAM_TOOLTIP_TYPE_VECTORSCOPE_HS;Hue-Saturation Vectorscope HISTORY_CHANGED;Changed HISTORY_CUSTOMCURVE;Custom curve HISTORY_FROMCLIPBOARD;From clipboard diff --git a/rtdata/themes/RawTherapee-GTK3-20_.css b/rtdata/themes/RawTherapee-GTK3-20_.css index 76f0004ee..f4f9ddb7f 100644 --- a/rtdata/themes/RawTherapee-GTK3-20_.css +++ b/rtdata/themes/RawTherapee-GTK3-20_.css @@ -720,6 +720,36 @@ flowboxchild:selected { margin: 0; } +/* Vertical version of slider. */ +#histScale { + min-height: 4em; + min-width: 0.4166666666666666em; + margin: 0.5833333333333333em 0 0 0; +} +#histScale trough { + padding: 0.583333333333333333em 0; +} +#histScale trough highlight { + margin: -0.583333333333333333em 0; + padding: 0.1em 0 0 0.1em; +} +#histScale.fine-tune trough highlight { + padding: 0.5em 0 0 0.5em; +} + +/* Copied from button.flat style. */ +button.radio#histButton { + background-image: none; +} + +button.radio#histButton:checked { + background-image: linear-gradient(#343434, #2E2E2E, #292929); +} + +button.radio#histButton:hover { + background-image: linear-gradient(shade(#343434,1.3), shade(#2E2E2E,1.3), shade(#292929,1.3)); +} + /*** end ***************************************************************************************/ #MyExpander { diff --git a/rtdata/themes/TooWaBlue-GTK3-20_.css b/rtdata/themes/TooWaBlue-GTK3-20_.css index ce4bb8d28..c4300413e 100644 --- a/rtdata/themes/TooWaBlue-GTK3-20_.css +++ b/rtdata/themes/TooWaBlue-GTK3-20_.css @@ -454,6 +454,47 @@ filechooser placessidebar list row:selected { margin: 0; } +/* Vertical version of slider. */ +#histScale { + min-height: 4em; + min-width: 1.833333333333333333em; + margin: -0.333333333333333333em 0; +} +#histScale trough { + padding: 0.583333333333333333em 0; +} +#histScale highlight { + background-image: linear-gradient(to right, shade (@accent-color2,1.22), shade(@accent-color2,.88)); + margin: -0.583333333333333333em 0; + padding: 0.333333333333333333em 0 0 0.333333333333333333em; +} +#histScale slider { +} +#histScale.fine-tune highlight { + padding: 0.5em 0 0 0.5em; +} + +/* Copied from button.flat style. */ +button.radio#histButton { + border: 0.083333333333333333em solid transparent; + box-shadow: none; + background-image: none; + background-color: transparent; +} +button.radio#histButton:hover { + border-color: @bg-button-border; + box-shadow: inset 0 0.083333333333333333em rgba(242, 242, 242, 0.1); + background-image: linear-gradient(to bottom, rgba(100,100,100,.3), rgba(30,30,30,.3)); + background-color: @bg-button-hover; +} +button.radio#histButton:active, +button.radio#histButton:checked { + border-color: @bg-button-border; + box-shadow: inset 0 0.1em rgba(242, 242, 242, 0.08); + background-image: linear-gradient(to bottom, rgba(100,100,100,.3), rgba(30,30,30,.3)); + background-color: @bg-button-active; +} + /*** end ***************************************************************************************/ /*** Separator *********************************************************************************/ diff --git a/rtengine/array2D.h b/rtengine/array2D.h index 512f7bcc1..ca4db3d06 100644 --- a/rtengine/array2D.h +++ b/rtengine/array2D.h @@ -64,8 +64,7 @@ constexpr unsigned int ARRAY2D_BYREFERENCE = 2; template -class array2D : - public rtengine::NonCopyable +class array2D { private: @@ -125,6 +124,25 @@ public: } } + array2D(const array2D& other) : + width(other.width), + buffer(other.buffer) + { + initRows(other.rows.size()); + } + + array2D& operator =(const array2D& other) + { + if (this != &other) { + free(); + width = other.width; + buffer = other.buffer; + initRows(other.rows.size()); + } + + return *this; + } + void fill(const T val, bool multiThread = false) { const ssize_t height = rows.size(); @@ -140,6 +158,7 @@ public: { buffer.clear(); rows.clear(); + width = 0; } // use with indices @@ -192,6 +211,24 @@ public: } } + array2D& operator+=(const array2D& rhs) + { + if (rhs.getWidth() == this->getWidth() && rhs.getHeight() == this->getHeight()) { + for (int i = 0; i < getHeight(); ++i) { +#ifdef _OPENMP + #pragma omp simd +#endif + + for (int j = 0; j < getWidth(); ++j) { + rows[i][j] += rhs[i][j]; + } + } + } + + return *this; + } + + int getWidth() const { return width; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 405820b20..f9c4b786c 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -17,16 +17,14 @@ * along with RawTherapee. If not, see . */ #include -#include -#include #include #include "improccoordinator.h" +#include "array2D.h" #include "cieimage.h" #include "color.h" #include "colortemp.h" -#include "jaggedarray.h" #include "curves.h" #include "dcp.h" #include "iccstore.h" @@ -47,6 +45,9 @@ namespace { + +constexpr int VECTORSCOPE_SIZE = 128; + using rtengine::Coord2D; Coord2D translateCoord(const rtengine::ImProcFunctions& ipf, int fw, int fh, int x, int y) { @@ -129,6 +130,21 @@ ImProcCoordinator::ImProcCoordinator() : histLRETI(256), + hist_lrgb_dirty(false), + hist_raw_dirty(false), + + vectorscopeScale(0), + vectorscope_hc_dirty(false), + vectorscope_hs_dirty(false), + vectorscope_hc(VECTORSCOPE_SIZE, VECTORSCOPE_SIZE), + vectorscope_hs(VECTORSCOPE_SIZE, VECTORSCOPE_SIZE), + waveformScale(0), + waveform_dirty(false), + waveformRed(0, 0), + waveformGreen(0, 0), + waveformBlue(0, 0), + waveformLuma(0, 0), + CAMBrightCurveJ(), CAMBrightCurveQ(), rCurve(), @@ -348,6 +364,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) } imgsrc->getRAWHistogram(histRedRaw, histGreenRaw, histBlueRaw); + hist_raw_dirty = !(hListener && hListener->updateHistogramRaw()); highDetailPreprocessComputed = highDetailNeeded; @@ -1638,9 +1655,21 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) imageListener->imageReady(params->crop); } + hist_lrgb_dirty = vectorscope_hc_dirty = vectorscope_hs_dirty = waveform_dirty = true; if (hListener) { - updateLRGBHistograms(); - hListener->histogramChanged(histRed, histGreen, histBlue, histLuma, histToneCurve, histLCurve, histCCurve, /*histCLurve, histLLCurve,*/ histLCAM, histCCAM, histRedRaw, histGreenRaw, histBlueRaw, histChroma, histLRETI); + if (hListener->updateHistogram()) { + updateLRGBHistograms(); + } + if (hListener->updateVectorscopeHC()) { + updateVectorscopeHC(); + } + if (hListener->updateVectorscopeHS()) { + updateVectorscopeHS(); + } + if (hListener->updateWaveform()) { + updateWaveforms(); + } + notifyHistogramChanged(); } } @@ -1741,8 +1770,42 @@ void ImProcCoordinator::setScale(int prevscale) } -void ImProcCoordinator::updateLRGBHistograms() +void ImProcCoordinator::notifyHistogramChanged() { + if (hListener) { + hListener->histogramChanged( + histRed, + histGreen, + histBlue, + histLuma, + histToneCurve, + histLCurve, + histCCurve, + histLCAM, + histCCAM, + histRedRaw, + histGreenRaw, + histBlueRaw, + histChroma, + histLRETI, + vectorscopeScale, + vectorscope_hc, + vectorscope_hs, + waveformScale, + waveformRed, + waveformGreen, + waveformBlue, + waveformLuma + ); + } +} + +bool ImProcCoordinator::updateLRGBHistograms() +{ + + if (!hist_lrgb_dirty) { + return false; + } int x1, y1, x2, y2; params->crop.mapToResized(pW, pH, scale, x1, x2, y1, y2); @@ -1800,6 +1863,159 @@ void ImProcCoordinator::updateLRGBHistograms() } } + hist_lrgb_dirty = false; + return true; + +} + +bool ImProcCoordinator::updateVectorscopeHC() +{ + if (!workimg || !vectorscope_hc_dirty) { + return false; + } + + int x1, y1, x2, y2; + params->crop.mapToResized(pW, pH, scale, x1, x2, y1, y2); + + constexpr int size = VECTORSCOPE_SIZE; + constexpr float norm_factor = size / (128.f * 655.36f); + vectorscope_hc.fill(0); + + vectorscopeScale = (x2 - x1) * (y2 - y1); + + const std::unique_ptr a(new float[vectorscopeScale]); + const std::unique_ptr b(new float[vectorscopeScale]); + const std::unique_ptr L(new float[vectorscopeScale]); + ipf.rgb2lab(*workimg, x1, y1, x2 - x1, y2 - y1, L.get(), a.get(), b.get(), params->icm); +#ifdef _OPENMP + #pragma omp parallel +#endif + { + array2D vectorscopeThr(size, size, ARRAY2D_CLEAR_DATA); +#ifdef _OPENMP + #pragma omp for nowait +#endif + for (int i = y1; i < y2; ++i) { + for (int j = x1, ofs_lab = (i - y1) * (x2 - x1); j < x2; ++j, ++ofs_lab) { + const int col = norm_factor * a[ofs_lab] + size / 2 + 0.5f; + const int row = norm_factor * b[ofs_lab] + size / 2 + 0.5f; + if (col >= 0 && col < size && row >= 0 && row < size) { + vectorscopeThr[row][col]++; + } + } + } +#ifdef _OPENMP + #pragma omp critical +#endif + { + vectorscope_hc += vectorscopeThr; + } + } + + vectorscope_hc_dirty = false; + return true; +} + +bool ImProcCoordinator::updateVectorscopeHS() +{ + if (!workimg || !vectorscope_hs_dirty) { + return false; + } + + int x1, y1, x2, y2; + params->crop.mapToResized(pW, pH, scale, x1, x2, y1, y2); + + constexpr int size = VECTORSCOPE_SIZE; + vectorscope_hs.fill(0); + + vectorscopeScale = (x2 - x1) * (y2 - y1); + +#ifdef _OPENMP + #pragma omp parallel +#endif + { + array2D vectorscopeThr(size, size, ARRAY2D_CLEAR_DATA); +#ifdef _OPENMP + #pragma omp for nowait +#endif + for (int i = y1; i < y2; ++i) { + int ofs = (i * pW + x1) * 3; + for (int j = x1; j < x2; ++j) { + const float red = 257.f * workimg->data[ofs++]; + const float green = 257.f * workimg->data[ofs++]; + const float blue = 257.f * workimg->data[ofs++]; + float h, s, l; + Color::rgb2hslfloat(red, green, blue, h, s, l); + const auto sincosval = xsincosf(2.f * RT_PI_F * h); + const int col = s * sincosval.y * (size / 2) + size / 2; + const int row = s * sincosval.x * (size / 2) + size / 2; + if (col >= 0 && col < size && row >= 0 && row < size) { + vectorscopeThr[row][col]++; + } + } + } +#ifdef _OPENMP + #pragma omp critical +#endif + { + vectorscope_hs += vectorscopeThr; + } + } + + vectorscope_hs_dirty = false; + return true; +} + +bool ImProcCoordinator::updateWaveforms() +{ + if (!workimg) { + // free memory + waveformRed.free(); + waveformGreen.free(); + waveformBlue.free(); + waveformLuma.free(); + return true; + } + + if (!waveform_dirty) { + return false; + } + + int x1, y1, x2, y2; + params->crop.mapToResized(pW, pH, scale, x1, x2, y1, y2); + int waveform_width = waveformRed.getWidth(); + + if (waveform_width != x2 - x1) { + // Resize waveform arrays. + waveform_width = x2 - x1; + waveformRed(waveform_width, 256); + waveformGreen(waveform_width, 256); + waveformBlue(waveform_width, 256); + waveformLuma(waveform_width, 256); + } + + // Start with zero. + waveformRed.fill(0); + waveformGreen.fill(0); + waveformBlue.fill(0); + waveformLuma.fill(0); + + constexpr float luma_factor = 255.f / 32768.f; + for (int i = y1; i < y2; i++) { + int ofs = (i * pW + x1) * 3; + float* L_row = nprevl->L[i] + x1; + + for (int j = 0; j < waveform_width; j++) { + waveformRed[workimg->data[ofs++]][j]++; + waveformGreen[workimg->data[ofs++]][j]++; + waveformBlue[workimg->data[ofs++]][j]++; + waveformLuma[LIM(L_row[j] * luma_factor, 0, 255)][j]++; + } + } + + waveformScale = y2 - y1; + waveform_dirty = false; + return true; } bool ImProcCoordinator::getAutoWB(double& temp, double& green, double equal, double tempBias) @@ -2244,4 +2460,61 @@ void ImProcCoordinator::setHighQualComputed() highQualityComputed = true; } +void ImProcCoordinator::requestUpdateWaveform() +{ + if (!hListener) { + return; + } + bool updated = updateWaveforms(); + if (updated) { + notifyHistogramChanged(); + } +} + +void ImProcCoordinator::requestUpdateHistogram() +{ + if (!hListener) { + return; + } + bool updated = updateLRGBHistograms(); + if (updated) { + notifyHistogramChanged(); + } +} + +void ImProcCoordinator::requestUpdateHistogramRaw() +{ + if (!hListener) { + return; + } + // Don't need to actually update histogram because it is always + // up-to-date. + if (hist_raw_dirty) { + hist_raw_dirty = false; + notifyHistogramChanged(); + } +} + +void ImProcCoordinator::requestUpdateVectorscopeHC() +{ + if (!hListener) { + return; + } + bool updated = updateVectorscopeHC(); + if (updated) { + notifyHistogramChanged(); + } +} + +void ImProcCoordinator::requestUpdateVectorscopeHS() +{ + if (!hListener) { + return; + } + bool updated = updateVectorscopeHS(); + if (updated) { + notifyHistogramChanged(); + } +} + } diff --git a/rtengine/improccoordinator.h b/rtengine/improccoordinator.h index d90ee68ae..83bee6955 100644 --- a/rtengine/improccoordinator.h +++ b/rtengine/improccoordinator.h @@ -54,7 +54,7 @@ class Crop; * but using this class' LUT and other precomputed parameters. The main preview area is displaying a non framed Crop object, * while detail windows are framed Crop objects. */ -class ImProcCoordinator final : public StagedImageProcessor +class ImProcCoordinator final : public StagedImageProcessor, public HistogramObservable { friend class Crop; @@ -126,6 +126,16 @@ protected: LUTu histBlue, histBlueRaw; LUTu histLuma, histToneCurve, histToneCurveBW, histLCurve, histCCurve; LUTu histLLCurve, histLCAM, histCCAM, histClad, bcabhist, histChroma, histLRETI; + bool hist_lrgb_dirty; + /// Used to simulate a lazy update of the raw histogram. + bool hist_raw_dirty; + int vectorscopeScale; + bool vectorscope_hc_dirty, vectorscope_hs_dirty; + array2D vectorscope_hc, vectorscope_hs; + /// Waveform's intensity. Same as height of reference image. + int waveformScale; + bool waveform_dirty; + array2D waveformRed, waveformGreen, waveformBlue, waveformLuma; LUTf CAMBrightCurveJ, CAMBrightCurveQ; @@ -195,8 +205,16 @@ protected: MyMutex minit; // to gain mutually exclusive access to ... to what exactly? + void notifyHistogramChanged(); void reallocAll(); - void updateLRGBHistograms(); + /// Updates L, R, G, and B histograms. Returns true unless not updated. + bool updateLRGBHistograms(); + /// Updates the H-C vectorscope. Returns true unless not updated. + bool updateVectorscopeHC(); + /// Updates the H-S vectorscope. Returns true unless not updated. + bool updateVectorscopeHS(); + /// Updates all waveforms. Returns true unless not updated. + bool updateWaveforms(); void setScale(int prevscale); void updatePreviewImage (int todo, bool panningRelatedChange); @@ -449,7 +467,13 @@ public: } void setHistogramListener (HistogramListener *h) override { + if (hListener) { + hListener->setObservable(nullptr); + } hListener = h; + if (h) { + h->setObservable(this); + } } void setAutoCamListener (AutoCamListener* acl) override { @@ -550,6 +574,11 @@ public: } denoiseInfoStore; + void requestUpdateHistogram() override; + void requestUpdateHistogramRaw() override; + void requestUpdateVectorscopeHC() override; + void requestUpdateVectorscopeHS() override; + void requestUpdateWaveform() override; }; } diff --git a/rtengine/improcfun.cc b/rtengine/improcfun.cc index 0fd3e954c..05cc115d9 100644 --- a/rtengine/improcfun.cc +++ b/rtengine/improcfun.cc @@ -5716,6 +5716,116 @@ void ImProcFunctions::rgb2lab(const Imagefloat &src, LabImage &dst, const Glib:: } } +void ImProcFunctions::rgb2lab(const Image8 &src, int x, int y, int w, int h, float L[], float a[], float b[], const procparams::ColorManagementParams &icm, bool consider_histogram_settings) const +{ // Adapted from ImProcFunctions::lab2rgb + const int src_width = src.getWidth(); + const int src_height = src.getHeight(); + + if (x < 0) { + x = 0; + } + + if (y < 0) { + y = 0; + } + + if (x + w > src_width) { + w = src_width - x; + } + + if (y + h > src_height) { + h = src_height - y; + } + + Glib::ustring profile; + + cmsHPROFILE oprof = nullptr; + + if (settings->HistogramWorking && consider_histogram_settings) { + profile = icm.workingProfile; + } else { + profile = icm.outputProfile; + + if (icm.outputProfile.empty() || icm.outputProfile == ColorManagementParams::NoICMString) { + profile = "sRGB"; + } + oprof = ICCStore::getInstance()->getProfile(profile); + } + + if (oprof) { + cmsUInt32Number flags = cmsFLAGS_NOOPTIMIZE | cmsFLAGS_NOCACHE; // NOCACHE is important for thread safety + + if (icm.outputBPC) { + flags |= cmsFLAGS_BLACKPOINTCOMPENSATION; + } + + lcmsMutex->lock(); + cmsHPROFILE LabIProf = cmsCreateLab4Profile(nullptr); + cmsHTRANSFORM hTransform = cmsCreateTransform (oprof, TYPE_RGB_8, LabIProf, TYPE_Lab_FLT, icm.outputIntent, flags); + cmsCloseProfile(LabIProf); + lcmsMutex->unlock(); + + // cmsDoTransform is relatively expensive +#ifdef _OPENMP + #pragma omp parallel +#endif + { + AlignedBuffer oBuf(3 * w); + float *outbuffer = oBuf.data; + int condition = y + h; + +#ifdef _OPENMP + #pragma omp for schedule(dynamic,16) +#endif + + for (int i = y; i < condition; i++) { + const int ix = 3 * (x + i * src_width); + int iy = 0; + float* rL = L + (i - y) * w; + float* ra = a + (i - y) * w; + float* rb = b + (i - y) * w; + + cmsDoTransform (hTransform, src.data + ix, outbuffer, w); + + for (int j = 0; j < w; j++) { + rL[j] = outbuffer[iy++] * 327.68f; + ra[j] = outbuffer[iy++] * 327.68f; + rb[j] = outbuffer[iy++] * 327.68f; + } + } + } // End of parallelization + + cmsDeleteTransform(hTransform); + } else { + TMatrix wprof = ICCStore::getInstance()->workingSpaceMatrix(profile); + const float wp[3][3] = { + {static_cast(wprof[0][0]), static_cast(wprof[0][1]), static_cast(wprof[0][2])}, + {static_cast(wprof[1][0]), static_cast(wprof[1][1]), static_cast(wprof[1][2])}, + {static_cast(wprof[2][0]), static_cast(wprof[2][1]), static_cast(wprof[2][2])} + }; + + const int x2 = x + w; + const int y2 = y + h; + constexpr float rgb_factor = 65355.f / 255.f; + +#ifdef _OPENMP + #pragma omp parallel for schedule(dynamic,16) if (multiThread) +#endif + + for (int i = y; i < y2; i++) { + int offset = (i - y) * w; + for (int j = x; j < x2; j++) { + float X, Y, Z; + // lab2rgb uses gamma2curve, which is gammatab_srgb. + const auto& igamma = Color::igammatab_srgb; + Color::rgbxyz(igamma[rgb_factor * src.r(i, j)], igamma[rgb_factor * src.g(i, j)], igamma[rgb_factor * src.b(i, j)], X, Y, Z, wp); + Color::XYZ2Lab(X, Y, Z, L[offset], a[offset], b[offset]); + offset++; + } + } + } +} + void ImProcFunctions::lab2rgb(const LabImage &src, Imagefloat &dst, const Glib::ustring &workingSpace) { TMatrix wiprof = ICCStore::getInstance()->workingSpaceInverseMatrix(workingSpace); diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index d3ee81701..e44780a13 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -449,6 +449,7 @@ public: void labColorCorrectionRegions(LabImage *lab); Image8* lab2rgb(LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm, bool consider_histogram_settings = true); + void rgb2lab(const Image8 &src, int x, int y, int w, int h, float L[], float a[], float b[], const procparams::ColorManagementParams &icm, bool consider_histogram_settings = true) const; Imagefloat* lab2rgbOut(LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm); // CieImage *ciec; void workingtrc(const Imagefloat* src, Imagefloat* dst, int cw, int ch, int mul, const Glib::ustring &profile, double gampos, double slpos, cmsHTRANSFORM &transform, bool normalizeIn = true, bool normalizeOut = true, bool keepTransForm = false) const; diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index 1671ae1f5..6f6baccd4 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -42,6 +42,9 @@ * */ +template +class array2D; + template class LUT; @@ -302,6 +305,8 @@ public: virtual void sizeChanged(int w, int h, int ow, int oh) = 0; }; +class HistogramObservable; + /** This listener is used when the histogram of the final image has changed. */ class HistogramListener { @@ -327,8 +332,43 @@ public: const LUTu& histGreenRaw, const LUTu& histBlueRaw, const LUTu& histChroma, - const LUTu& histLRETI + const LUTu& histLRETI, + int vectorscopeScale, + const array2D& vectorscopeHC, + const array2D& vectorscopeHS, + int waveformScale, + const array2D& waveformRed, + const array2D& waveformGreen, + const array2D& waveformBlue, + const array2D& waveformLuma ) = 0; + /** Tells which observable is notifying the listener. */ + virtual void setObservable(HistogramObservable* observable) = 0; + /** Returns if the listener wants the histogram to be updated. */ + virtual bool updateHistogram(void) const = 0; + /** Returns if the listener wants the raw histogram to be updated. */ + virtual bool updateHistogramRaw(void) const = 0; + /** Returns if the listener wants the H-C vectorscope to be updated. */ + virtual bool updateVectorscopeHC(void) const = 0; + /** Returns if the listener wants the H-S vectorscope to be updated. */ + virtual bool updateVectorscopeHS(void) const = 0; + /** Returns if the listener wants the waveform to be updated. */ + virtual bool updateWaveform(void) const = 0; +}; + +class HistogramObservable +{ +public: + /** Tells the observable to update the histogram data. */ + virtual void requestUpdateHistogram() = 0; + /** Tells the observable to update the raw histogram data. */ + virtual void requestUpdateHistogramRaw() = 0; + /** Tells the observable to update the H-C vectorscope data. */ + virtual void requestUpdateVectorscopeHC() = 0; + /** Tells the observable to update the H-S vectorscope data. */ + virtual void requestUpdateVectorscopeHS() = 0; + /** Tells the observable to update the waveform data. */ + virtual void requestUpdateWaveform() = 0; }; /** This listener is used when the auto exposure has been recomputed (e.g. when the clipping ratio changed). */ diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 9d6860e0f..34d677206 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -21,6 +21,7 @@ #include +#include "../rtengine/array2D.h" #include "../rtengine/imagesource.h" #include "../rtengine/iccstore.h" #include "batchqueue.h" @@ -47,6 +48,8 @@ using namespace rtengine::procparams; +using ScopeType = Options::ScopeType; + namespace { @@ -470,7 +473,8 @@ EditorPanel::EditorPanel (FilePanel* filePanel) iBeforeLockON (nullptr), iBeforeLockOFF (nullptr), previewHandler (nullptr), beforePreviewHandler (nullptr), beforeIarea (nullptr), beforeBox (nullptr), afterBox (nullptr), beforeLabel (nullptr), afterLabel (nullptr), beforeHeaderBox (nullptr), afterHeaderBox (nullptr), parent (nullptr), parentWindow (nullptr), openThm (nullptr), - selectedFrame(0), isrc (nullptr), ipc (nullptr), beforeIpc (nullptr), err (0), isProcessing (false) + selectedFrame(0), isrc (nullptr), ipc (nullptr), beforeIpc (nullptr), err (0), isProcessing (false), + histogram_observable(nullptr), histogram_scope_type(ScopeType::NONE) { epih = new EditorPanelIdleHelper; @@ -2245,16 +2249,94 @@ void EditorPanel::histogramChanged( const LUTu& histGreenRaw, const LUTu& histBlueRaw, const LUTu& histChroma, - const LUTu& histLRETI + const LUTu& histLRETI, + int vectorscopeScale, + const array2D& vectorscopeHC, + const array2D& vectorscopeHS, + int waveformScale, + const array2D& waveformRed, + const array2D& waveformGreen, + const array2D& waveformBlue, + const array2D& waveformLuma ) { if (histogramPanel) { - histogramPanel->histogramChanged(histRed, histGreen, histBlue, histLuma, histChroma, histRedRaw, histGreenRaw, histBlueRaw); + histogramPanel->histogramChanged(histRed, histGreen, histBlue, histLuma, histChroma, histRedRaw, histGreenRaw, histBlueRaw, vectorscopeScale, vectorscopeHC, vectorscopeHS, waveformScale, waveformRed, waveformGreen, waveformBlue, waveformLuma); } tpc->updateCurveBackgroundHistogram(histToneCurve, histLCurve, histCCurve, histLCAM, histCCAM, histRed, histGreen, histBlue, histLuma, histLRETI); } +void EditorPanel::setObservable(rtengine::HistogramObservable* observable) +{ + histogram_observable = observable; +} + +bool EditorPanel::updateHistogram(void) const +{ + return histogram_scope_type == ScopeType::HISTOGRAM + || histogram_scope_type == ScopeType::NONE; +} + +bool EditorPanel::updateHistogramRaw(void) const +{ + return histogram_scope_type == ScopeType::HISTOGRAM_RAW + || histogram_scope_type == ScopeType::NONE; +} + +bool EditorPanel::updateVectorscopeHC(void) const +{ + return + histogram_scope_type == ScopeType::VECTORSCOPE_HC + || histogram_scope_type == ScopeType::NONE; +} + +bool EditorPanel::updateVectorscopeHS(void) const +{ + return + histogram_scope_type == ScopeType::VECTORSCOPE_HS + || histogram_scope_type == ScopeType::NONE; +} + +bool EditorPanel::updateWaveform(void) const +{ + return histogram_scope_type == ScopeType::WAVEFORM + || histogram_scope_type == ScopeType::PARADE + || histogram_scope_type == ScopeType::NONE; +} + +void EditorPanel::scopeTypeChanged(ScopeType new_type) +{ + histogram_scope_type = new_type; + + if (!histogram_observable) { + return; + } + + // Make sure the new scope is updated since we only actively update the + // current scope. + switch (new_type) { + case ScopeType::HISTOGRAM: + histogram_observable->requestUpdateHistogram(); + break; + case ScopeType::HISTOGRAM_RAW: + histogram_observable->requestUpdateHistogramRaw(); + break; + case ScopeType::VECTORSCOPE_HC: + histogram_observable->requestUpdateVectorscopeHC(); + break; + case ScopeType::VECTORSCOPE_HS: + histogram_observable->requestUpdateVectorscopeHS(); + break; + case ScopeType::PARADE: + case ScopeType::WAVEFORM: + histogram_observable->requestUpdateWaveform(); + break; + case ScopeType::NONE: + break; + } +} + bool EditorPanel::CheckSidePanelsVisibility() { if (tbTopPanel_1) { @@ -2371,6 +2453,10 @@ void EditorPanel::updateHistogramPosition (int oldPosition, int newPosition) break; } + if (histogramPanel) { + histogramPanel->setPanelListener(this); + } + iareapanel->imageArea->setPointerMotionHListener (histogramPanel); } diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 826793507..a277ffd3a 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -32,6 +32,12 @@ #include "../rtengine/noncopyable.h" #include "../rtengine/rtengine.h" +namespace rtengine +{ +template +class array2D; +} + class BatchQueueEntry; class EditorPanel; class FilePanel; @@ -55,6 +61,7 @@ class EditorPanel final : public ThumbnailListener, public HistoryBeforeLineListener, public rtengine::HistogramListener, + public HistogramPanelListener, public rtengine::NonCopyable { public: @@ -126,8 +133,25 @@ public: const LUTu& histGreenRaw, const LUTu& histBlueRaw, const LUTu& histChroma, - const LUTu& histLRETI + const LUTu& histLRETI, + int vectorscopeScale, + const array2D& vectorscopeHC, + const array2D& vectorscopeHS, + int waveformScale, + const array2D& waveformRed, + const array2D& waveformGreen, + const array2D& waveformBlue, + const array2D& waveformLuma ) override; + void setObservable(rtengine::HistogramObservable* observable) override; + bool updateHistogram(void) const override; + bool updateHistogramRaw(void) const override; + bool updateVectorscopeHC(void) const override; + bool updateVectorscopeHS(void) const override; + bool updateWaveform(void) const override; + + // HistogramPanelListener + void scopeTypeChanged(Options::ScopeType new_type) override; // event handlers void info_toggled (); @@ -260,4 +284,7 @@ private: bool isProcessing; IdleRegister idle_register; + + rtengine::HistogramObservable* histogram_observable; + Options::ScopeType histogram_scope_type; }; diff --git a/rtgui/histogrampanel.cc b/rtgui/histogrampanel.cc index 62be4c1f4..d7e9bbc69 100644 --- a/rtgui/histogrampanel.cc +++ b/rtgui/histogrampanel.cc @@ -22,32 +22,51 @@ #include "options.h" #include #include +#include "../rtengine/array2D.h" #include "../rtengine/LUT.h" #include "rtimage.h" #include "../rtengine/color.h" using namespace rtengine; +constexpr float HistogramArea::MAX_BRIGHT; +constexpr float HistogramArea::MIN_BRIGHT; + +using ScopeType = Options::ScopeType; // // // HistogramPanel -HistogramPanel::HistogramPanel() : +HistogramPanel::HistogramPanel () : pointer_moved_delayed_call( [this](bool validPos, const Glib::ustring &profile, const Glib::ustring &profileW, int r, int g, int b) { + bool update_hist_area; + if (!validPos) { // do something to un-show vertical bars - histogramRGBArea->updateBackBuffer(-1, -1, -1); + if (histogramRGBArea) { + histogramRGBArea->updateBackBuffer(-1, -1, -1); + } + update_hist_area = histogramArea->updatePointer(-1, -1, -1); } else { // do something to show vertical bars - histogramRGBArea->updateBackBuffer(r, g, b, profile, profileW); + if (histogramRGBArea) { + histogramRGBArea->updateBackBuffer(r, g, b, profile, profileW); + } + update_hist_area = histogramArea->updatePointer(r, g, b, profile, profileW); + } + if (histogramRGBArea) { + histogramRGBArea->queue_draw(); + } + if (update_hist_area) { + histogramArea->queue_draw(); } - histogramRGBArea->queue_draw (); }, 50, 100 - ) + ), + panel_listener(nullptr) { setExpandAlignProperties(this, true, true, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); set_name("HistogramPanel"); @@ -55,30 +74,49 @@ HistogramPanel::HistogramPanel() : histogramArea = Gtk::manage (new HistogramArea (this)); setExpandAlignProperties(histogramArea, true, true, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); - histogramRGBArea = Gtk::manage (new HistogramRGBArea ()); - setExpandAlignProperties(histogramRGBArea, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_END); - histogramRGBArea->show(); + histogramRGBAreaHori.reset(new HistogramRGBAreaHori()); + setExpandAlignProperties(histogramRGBAreaHori.get(), true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_END); + + histogramRGBAreaVert.reset(new HistogramRGBAreaVert()); + setExpandAlignProperties(histogramRGBAreaVert.get(), false, true, Gtk::ALIGN_END, Gtk::ALIGN_FILL); + + switch (options.histogramScopeType) { + case ScopeType::NONE: + case ScopeType::HISTOGRAM_RAW: + case ScopeType::VECTORSCOPE_HC: + case ScopeType::VECTORSCOPE_HS: + histogramRGBArea = nullptr; + break; + case ScopeType::PARADE: + case ScopeType::WAVEFORM: + histogramRGBArea = histogramRGBAreaVert.get(); + break; + case ScopeType::HISTOGRAM: + histogramRGBArea = histogramRGBAreaHori.get(); + break; + } // connecting the two childs - histogramArea->signal_factor_changed().connect( sigc::mem_fun(*histogramRGBArea, &HistogramRGBArea::factorChanged) ); + histogramArea->signal_factor_changed().connect( sigc::mem_fun(*histogramRGBAreaHori, &HistogramRGBArea::factorChanged) ); + histogramArea->signal_factor_changed().connect( sigc::mem_fun(*histogramRGBAreaVert, &HistogramRGBArea::factorChanged) ); gfxGrid = Gtk::manage (new Gtk::Grid ()); - gfxGrid->set_orientation(Gtk::ORIENTATION_VERTICAL); gfxGrid->set_row_spacing(1); gfxGrid->set_column_spacing(1); - histogramRGBArea->setParent(gfxGrid); gfxGrid->add(*histogramArea); - - if (options.histogramBar) { - gfxGrid->add (*histogramRGBArea); - } + gfxGrid->attach_next_to( + *histogramRGBAreaVert, *histogramArea, + options.histogramPosition == 1 ? Gtk::POS_RIGHT : Gtk::POS_LEFT + ); + gfxGrid->attach_next_to(*histogramRGBAreaHori, *histogramArea, Gtk::POS_BOTTOM); + histogramRGBAreaHori->set_no_show_all(); + histogramRGBAreaVert->set_no_show_all(); redImage = new RTImage ("histogram-red-on-small.png"); greenImage = new RTImage ("histogram-green-on-small.png"); blueImage = new RTImage ("histogram-blue-on-small.png"); valueImage = new RTImage ("histogram-silver-on-small.png"); chroImage = new RTImage ("histogram-gold-on-small.png"); - rawImage = new RTImage ("histogram-bayer-on-small.png"); barImage = new RTImage ("histogram-bar-on-small.png"); redImage_g = new RTImage ("histogram-red-off-small.png"); @@ -86,21 +124,41 @@ HistogramPanel::HistogramPanel() : blueImage_g = new RTImage ("histogram-blue-off-small.png"); valueImage_g = new RTImage ("histogram-silver-off-small.png"); chroImage_g = new RTImage ("histogram-gold-off-small.png"); - rawImage_g = new RTImage ("histogram-bayer-off-small.png"); barImage_g = new RTImage ("histogram-bar-off-small.png"); mode0Image = new RTImage ("histogram-mode-linear-small.png"); mode1Image = new RTImage ("histogram-mode-logx-small.png"); mode2Image = new RTImage ("histogram-mode-logxy-small.png"); + Gtk::Image* histImage = Gtk::manage(new RTImage("histogram-type-histogram-small.png")); + Gtk::Image* histRawImage = Gtk::manage(new RTImage("histogram-type-histogram-raw-small.png")); + Gtk::Image* paradeImage = Gtk::manage(new RTImage("histogram-type-parade-small.png")); + Gtk::Image* waveImage = Gtk::manage(new RTImage("histogram-type-waveform-small.png")); + Gtk::Image* vectHcImage = Gtk::manage(new RTImage("histogram-type-vectorscope-hc-small.png")); + Gtk::Image* vectHsImage = Gtk::manage(new RTImage("histogram-type-vectorscope-hs-small.png")); + showRed = Gtk::manage (new Gtk::ToggleButton ()); showGreen = Gtk::manage (new Gtk::ToggleButton ()); showBlue = Gtk::manage (new Gtk::ToggleButton ()); showValue = Gtk::manage (new Gtk::ToggleButton ()); showChro = Gtk::manage (new Gtk::ToggleButton ()); - showRAW = Gtk::manage (new Gtk::ToggleButton ()); showMode = Gtk::manage (new Gtk::Button ()); showBAR = Gtk::manage (new Gtk::ToggleButton ()); + scopeOptions = Gtk::manage (new Gtk::ToggleButton ()); + + Gtk::RadioButtonGroup scopeTypeGroup; + scopeHistBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup)); + scopeHistRawBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup)); + scopeParadeBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup)); + scopeWaveBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup)); + scopeVectHcBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup)); + scopeVectHsBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup)); + scopeHistBtn->set_mode(false); + scopeHistRawBtn->set_mode(false); + scopeParadeBtn->set_mode(false); + scopeWaveBtn->set_mode(false); + scopeVectHcBtn->set_mode(false); + scopeVectHsBtn->set_mode(false); showRed->set_name("histButton"); showRed->set_can_focus(false); @@ -112,40 +170,68 @@ HistogramPanel::HistogramPanel() : showValue->set_can_focus(false); showChro->set_name("histButton"); showChro->set_can_focus(false); - showRAW->set_name("histButton"); - showRAW->set_can_focus(false); showMode->set_name("histButton"); showMode->set_can_focus(false); + scopeOptions->set_name("histButton"); + scopeOptions->set_can_focus(false); showBAR->set_name("histButton"); showBAR->set_can_focus(false); + scopeHistBtn->set_name("histButton"); + scopeHistBtn->set_can_focus(false); + scopeHistRawBtn->set_name("histButton"); + scopeHistRawBtn->set_can_focus(false); + scopeParadeBtn->set_name("histButton"); + scopeParadeBtn->set_can_focus(false); + scopeWaveBtn->set_name("histButton"); + scopeWaveBtn->set_can_focus(false); + scopeVectHcBtn->set_name("histButton"); + scopeVectHcBtn->set_can_focus(false); + scopeVectHsBtn->set_name("histButton"); + scopeVectHsBtn->set_can_focus(false); showRed->set_relief (Gtk::RELIEF_NONE); showGreen->set_relief (Gtk::RELIEF_NONE); showBlue->set_relief (Gtk::RELIEF_NONE); showValue->set_relief (Gtk::RELIEF_NONE); showChro->set_relief (Gtk::RELIEF_NONE); - showRAW->set_relief (Gtk::RELIEF_NONE); showMode->set_relief (Gtk::RELIEF_NONE); + scopeOptions->set_relief (Gtk::RELIEF_NONE); showBAR->set_relief (Gtk::RELIEF_NONE); + scopeHistBtn->set_relief (Gtk::RELIEF_NONE); + scopeHistRawBtn->set_relief (Gtk::RELIEF_NONE); + scopeParadeBtn->set_relief (Gtk::RELIEF_NONE); + scopeWaveBtn->set_relief (Gtk::RELIEF_NONE); + scopeVectHcBtn->set_relief (Gtk::RELIEF_NONE); + scopeVectHsBtn->set_relief (Gtk::RELIEF_NONE); showRed->set_tooltip_text (M("HISTOGRAM_TOOLTIP_R")); showGreen->set_tooltip_text (M("HISTOGRAM_TOOLTIP_G")); showBlue->set_tooltip_text (M("HISTOGRAM_TOOLTIP_B")); showValue->set_tooltip_text (M("HISTOGRAM_TOOLTIP_L")); showChro->set_tooltip_text (M("HISTOGRAM_TOOLTIP_CHRO")); - showRAW->set_tooltip_text (M("HISTOGRAM_TOOLTIP_RAW")); showMode->set_tooltip_text (M("HISTOGRAM_TOOLTIP_MODE")); - showBAR->set_tooltip_text (M("HISTOGRAM_TOOLTIP_BAR")); + scopeOptions->set_tooltip_text(M("HISTOGRAM_TOOLTIP_SHOW_OPTIONS")); + scopeHistBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_HISTOGRAM")); + scopeHistRawBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_HISTOGRAM_RAW")); + scopeParadeBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_PARADE")); + scopeWaveBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_WAVEFORM")); + scopeVectHcBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_VECTORSCOPE_HC")); + scopeVectHsBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_VECTORSCOPE_HS")); buttonGrid = Gtk::manage (new Gtk::Grid ()); - buttonGrid->set_orientation(Gtk::ORIENTATION_VERTICAL); + buttonGrid->set_orientation(Gtk::ORIENTATION_HORIZONTAL); + persistentButtons = Gtk::manage(new Gtk::Box()); + persistentButtons->set_orientation(Gtk::ORIENTATION_VERTICAL); + optionButtons = Gtk::manage(new Gtk::Box()); + optionButtons->set_orientation(Gtk::ORIENTATION_VERTICAL); + showRed->set_active (options.histogramRed); showGreen->set_active (options.histogramGreen); showBlue->set_active (options.histogramBlue); showValue->set_active (options.histogramLuma); showChro->set_active (options.histogramChroma); - showRAW->set_active (options.histogramRAW); // no showMode->set_active(), as it's not a ToggleButton + scopeOptions->set_active(options.histogramShowOptionButtons); showBAR->set_active (options.histogramBar); showRed->set_image (showRed->get_active() ? *redImage : *redImage_g); @@ -153,56 +239,134 @@ HistogramPanel::HistogramPanel() : showBlue->set_image (showBlue->get_active() ? *blueImage : *blueImage_g); showValue->set_image (showValue->get_active() ? *valueImage : *valueImage_g); showChro->set_image (showChro->get_active() ? *chroImage : *chroImage_g); - showRAW->set_image (showRAW->get_active() ? *rawImage : *rawImage_g); if (options.histogramDrawMode == 0) showMode->set_image(*mode0Image); else if (options.histogramDrawMode == 1) showMode->set_image(*mode1Image); else showMode->set_image(*mode2Image); + scopeHistBtn->set_image(*histImage); + scopeHistRawBtn->set_image(*histRawImage); + scopeParadeBtn->set_image(*paradeImage); + scopeWaveBtn->set_image(*waveImage); + scopeVectHcBtn->set_image(*vectHcImage); + scopeVectHsBtn->set_image(*vectHsImage); + switch(options.histogramScopeType) { + case ScopeType::HISTOGRAM: + scopeHistBtn->set_active(); + break; + case ScopeType::HISTOGRAM_RAW: + scopeHistRawBtn->set_active(); + break; + case ScopeType::PARADE: + scopeParadeBtn->set_active(); + break; + case ScopeType::WAVEFORM: + scopeWaveBtn->set_active(); + break; + case ScopeType::VECTORSCOPE_HS: + scopeVectHsBtn->set_active(); + break; + case ScopeType::VECTORSCOPE_HC: + scopeVectHcBtn->set_active(); + break; + case ScopeType::NONE: + break; + } + scopeOptions->set_image(*Gtk::manage(new RTImage("histogram-ellipsis-small.png"))); showBAR->set_image (showBAR->get_active() ? *barImage : *barImage_g); - raw_toggled(); // Make sure the luma/chroma toggles are enabled or disabled - - setExpandAlignProperties(showRed , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - setExpandAlignProperties(showGreen, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - setExpandAlignProperties(showBlue , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - setExpandAlignProperties(showValue, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - setExpandAlignProperties(showChro , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - setExpandAlignProperties(showRAW , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - setExpandAlignProperties(showMode , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); - setExpandAlignProperties(showBAR , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); + setExpandAlignProperties(showRed , false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); + setExpandAlignProperties(showGreen, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); + setExpandAlignProperties(showBlue , false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); + setExpandAlignProperties(showValue, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); + setExpandAlignProperties(showChro , false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); + setExpandAlignProperties(showMode , false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); + setExpandAlignProperties(scopeOptions, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); + setExpandAlignProperties(showBAR , false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); + setExpandAlignProperties(scopeOptions, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); + setExpandAlignProperties(scopeHistBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); + setExpandAlignProperties(scopeHistRawBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); + setExpandAlignProperties(scopeParadeBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); + setExpandAlignProperties(scopeWaveBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); + setExpandAlignProperties(scopeVectHcBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); + setExpandAlignProperties(scopeVectHsBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); + setExpandAlignProperties(persistentButtons, false, true, Gtk::ALIGN_START, Gtk::ALIGN_FILL); + setExpandAlignProperties(optionButtons, false, true, Gtk::ALIGN_START, Gtk::ALIGN_FILL); showRed->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::red_toggled), showRed ); showGreen->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::green_toggled), showGreen ); showBlue->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::blue_toggled), showBlue ); showValue->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::value_toggled), showValue ); showChro->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::chro_toggled), showChro ); - showRAW->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::raw_toggled), showRAW ); showMode->signal_released().connect( sigc::mem_fun(*this, &HistogramPanel::mode_released), showMode ); + scopeOptions->signal_toggled().connect(sigc::mem_fun(*this, &HistogramPanel::scopeOptionsToggled)); showBAR->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::bar_toggled), showBAR ); + scopeHistBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeHistBtn)); + scopeHistRawBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeHistRawBtn)); + scopeParadeBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeParadeBtn)); + scopeWaveBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeWaveBtn)); + scopeVectHcBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeVectHcBtn)); + scopeVectHsBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeVectHsBtn)); - buttonGrid->add (*showRed); - buttonGrid->add (*showGreen); - buttonGrid->add (*showBlue); - buttonGrid->add (*showValue); - buttonGrid->add (*showChro); - buttonGrid->add (*showRAW); - buttonGrid->add (*showMode); - buttonGrid->add (*showBAR); + brightnessWidget = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_VERTICAL)); + brightnessWidget->set_inverted(); + brightnessWidget->set_range(log(HistogramArea::MIN_BRIGHT), log(HistogramArea::MAX_BRIGHT)); + brightnessWidget->set_draw_value(false); + brightnessWidget->signal_value_changed().connect(sigc::mem_fun(*this, &HistogramPanel::brightnessWidgetValueChanged)); + brightnessWidget->set_name("histScale"); + brightnessWidget->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TRACE_BRIGHTNESS")); + setExpandAlignProperties(brightnessWidget, true, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_START); + + optionButtons->add(*showRed); + optionButtons->add(*showGreen); + optionButtons->add(*showBlue); + optionButtons->add(*showValue); + optionButtons->add(*showChro); + optionButtons->add(*showMode); + optionButtons->add(*showBAR); + optionButtons->add(*brightnessWidget); + + Gtk::VSeparator* separator = Gtk::manage(new Gtk::VSeparator()); + setExpandAlignProperties(separator, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + persistentButtons->add(*scopeHistBtn); + persistentButtons->add(*scopeHistRawBtn); + persistentButtons->add(*scopeParadeBtn); + persistentButtons->add(*scopeWaveBtn); + persistentButtons->add(*scopeVectHsBtn); + persistentButtons->add(*scopeVectHcBtn); + persistentButtons->add(*separator); + persistentButtons->add(*scopeOptions); // Put the button vbox next to the window's border to be less disturbing if (options.histogramPosition == 1) { + buttonGrid->add(*persistentButtons); + buttonGrid->add(*optionButtons); + add (*buttonGrid); add (*gfxGrid); } else { + buttonGrid->add(*optionButtons); + buttonGrid->add(*persistentButtons); + add (*gfxGrid); add (*buttonGrid); } show_all (); + optionButtons->set_no_show_all(); + optionButtons->set_visible(options.histogramShowOptionButtons); + type_changed(); + updateHistAreaOptions(); + if (histogramRGBArea) { + updateHistRGBAreaOptions(); + } + + brightness_changed_connection = histogramArea->getBrighnessChangedSignal().connect(sigc::mem_fun(*this, &HistogramPanel::brightnessUpdated)); rconn = signal_size_allocate().connect( sigc::mem_fun(*this, &HistogramPanel::resized) ); + + histogramArea->setBrightness(options.histogramTraceBrightness); } HistogramPanel::~HistogramPanel () @@ -214,7 +378,6 @@ HistogramPanel::~HistogramPanel () delete blueImage; delete valueImage; delete chroImage; - delete rawImage; delete mode0Image; delete mode1Image; delete mode2Image; @@ -225,24 +388,51 @@ HistogramPanel::~HistogramPanel () delete blueImage_g; delete valueImage_g; delete chroImage_g; - delete rawImage_g; delete barImage_g; } +void HistogramPanel::showRGBBar() +{ + histogramRGBAreaHori->set_visible( + histogramRGBArea == histogramRGBAreaHori.get() && showBAR->get_active()); + histogramRGBAreaVert->set_visible( + histogramRGBArea == histogramRGBAreaVert.get() && showBAR->get_active()); + histogramRGBAreaHori->setShow(false); + histogramRGBAreaVert->setShow(false); + + if (!histogramRGBArea) { + return; + } + + setHistRGBInvalid(); + histogramRGBArea->setShow(showBAR->get_active()); +} + void HistogramPanel::resized (Gtk::Allocation& req) { + static int old_height = 0; + static int old_width = 0; - histogramArea->updateBackBuffer (); - histogramArea->queue_draw (); + bool size_changed = + old_height != req.get_height() || old_width != req.get_width(); + + if (!histogramArea->updatePending() && size_changed) { + histogramArea->updateBackBuffer (); + histogramArea->queue_draw (); + } // set histogramRGBArea invalid; - histogramRGBArea->updateBackBuffer(-1, -1, -1); - histogramRGBArea->queue_draw (); + if (histogramRGBArea && size_changed) { + histogramRGBArea->updateBackBuffer(-1, -1, -1); + histogramRGBArea->queue_draw (); + } // Store current height of the histogram options.histogramHeight = get_height(); + old_height = req.get_height(); + old_width = req.get_width(); } void HistogramPanel::red_toggled () @@ -275,21 +465,6 @@ void HistogramPanel::chro_toggled () rgbv_toggled(); } -void HistogramPanel::raw_toggled () -{ - if (showRAW->get_active()) { - showRAW->set_image(*rawImage); - showValue->set_sensitive(false); - showChro->set_sensitive(false); - } else { - showRAW->set_image(*rawImage_g); - showValue->set_sensitive(true); - showChro->set_sensitive(true); - } - - rgbv_toggled(); -} - void HistogramPanel::mode_released () { options.histogramDrawMode = (options.histogramDrawMode + 1) % 3; @@ -302,21 +477,141 @@ void HistogramPanel::mode_released () rgbv_toggled(); } +void HistogramPanel::brightnessWidgetValueChanged(void) +{ + ConnectionBlocker blocker(brightness_changed_connection); + histogramArea->setBrightness(exp(brightnessWidget->get_value())); + options.histogramTraceBrightness = histogramArea->getBrightness(); +} + +void HistogramPanel::brightnessUpdated(float brightness) +{ + brightnessWidget->set_value(log(brightness)); + options.histogramTraceBrightness = histogramArea->getBrightness(); +} + +void HistogramPanel::scopeOptionsToggled() +{ + options.histogramShowOptionButtons = scopeOptions->get_active(); + optionButtons->set_visible(scopeOptions->get_active()); +} + +void HistogramPanel::type_selected(Gtk::RadioButton* button) +{ + ScopeType new_type = ScopeType::NONE; + + if (button == scopeHistBtn) { + new_type = ScopeType::HISTOGRAM; + } else if (button == scopeHistRawBtn) { + new_type = ScopeType::HISTOGRAM_RAW; + } else if (button == scopeParadeBtn) { + new_type = ScopeType::PARADE; + } else if (button == scopeWaveBtn) { + new_type = ScopeType::WAVEFORM; + } else if (button == scopeVectHcBtn) { + new_type = ScopeType::VECTORSCOPE_HC; + } else if (button == scopeVectHsBtn) { + new_type = ScopeType::VECTORSCOPE_HS; + } + + if (new_type == options.histogramScopeType) { + return; + } + + options.histogramScopeType = new_type; + + type_changed(); + updateHistAreaOptions(); + if (histogramRGBArea) { + updateHistRGBAreaOptions(); + } + histogramArea->setDirty(true); + histogramArea->queue_draw(); +} + +void HistogramPanel::type_changed() +{ + switch (options.histogramScopeType) { + case ScopeType::HISTOGRAM: + showRed->show(); + showGreen->show(); + showBlue->show(); + showValue->show(); + showChro->show(); + showMode->show(); + showBAR->show(); + showBAR->set_tooltip_text(M("HISTOGRAM_TOOLTIP_BAR")); + brightnessWidget->hide(); + histogramRGBArea = histogramRGBAreaHori.get(); + break; + case ScopeType::HISTOGRAM_RAW: + showRed->show(); + showGreen->show(); + showBlue->show(); + showValue->hide(); + showChro->hide(); + showMode->show(); + showBAR->hide(); + brightnessWidget->hide(); + histogramRGBArea = nullptr; + break; + case ScopeType::PARADE: + case ScopeType::WAVEFORM: + showRed->show(); + showGreen->show(); + showBlue->show(); + showValue->show(); + showChro->hide(); + showMode->hide(); + showBAR->show(); + showBAR->set_tooltip_text(M("HISTOGRAM_TOOLTIP_BAR")); + brightnessWidget->show(); + histogramRGBArea = histogramRGBAreaVert.get(); + break; + case ScopeType::VECTORSCOPE_HC: + case ScopeType::VECTORSCOPE_HS: + showRed->hide(); + showGreen->hide(); + showBlue->hide(); + showValue->hide(); + showChro->hide(); + showMode->hide(); + showBAR->show(); + showBAR->set_tooltip_text(M("HISTOGRAM_TOOLTIP_CROSSHAIR")); + brightnessWidget->show(); + histogramRGBArea = nullptr; + break; + case ScopeType::NONE: + break; + } + + if (panel_listener) { + updateHistAreaOptions(); + panel_listener->scopeTypeChanged(options.histogramScopeType); + } + + showRGBBar(); +} + void HistogramPanel::bar_toggled () { showBAR->set_image(showBAR->get_active() ? *barImage : *barImage_g); rgbv_toggled(); + showRGBBar(); } void HistogramPanel::rgbv_toggled () { // Update Display - histogramArea->updateOptions (showRed->get_active(), showGreen->get_active(), showBlue->get_active(), showValue->get_active(), showChro->get_active(), showRAW->get_active(), options.histogramDrawMode); + updateHistAreaOptions(); + histogramArea->updateBackBuffer (); histogramArea->queue_draw (); - histogramRGBArea->updateOptions (showRed->get_active(), showGreen->get_active(), showBlue->get_active(), showValue->get_active(), showChro->get_active(), showRAW->get_active(), showBAR->get_active()); - histogramRGBArea->updateBackBuffer (0, 0, 0); - histogramRGBArea->queue_draw (); + if (histogramRGBArea) { + updateHistRGBAreaOptions(); + histogramRGBArea->updateBackBuffer(-1, -1, -1); + histogramRGBArea->queue_draw (); + } } void HistogramPanel::setHistRGBInvalid () @@ -342,11 +637,27 @@ void HistogramPanel::reorder (Gtk::PositionType align) removeIfThere(this, gfxGrid, false); add (*gfxGrid); gfxGrid->unreference(); + + gfxGrid->remove(*histogramRGBAreaVert); + gfxGrid->add(*histogramRGBAreaVert); + + optionButtons->reference(); + removeIfThere(buttonGrid, optionButtons, false); + buttonGrid->add(*optionButtons); + optionButtons->unreference(); } else { buttonGrid->reference(); removeIfThere(this, buttonGrid, false); add (*buttonGrid); buttonGrid->unreference(); + + gfxGrid->remove(*histogramRGBAreaVert); + gfxGrid->attach_next_to(*histogramRGBAreaVert, *histogramArea, Gtk::POS_LEFT); + + persistentButtons->reference(); + removeIfThere(buttonGrid, persistentButtons, false); + buttonGrid->add(*persistentButtons); + persistentButtons->unreference(); } } @@ -361,6 +672,41 @@ void HistogramPanel::toggleButtonMode () showMode->set_image(*mode2Image); } +void HistogramPanel::setPanelListener(HistogramPanelListener* listener) +{ + panel_listener = listener; + + if (listener) { + listener->scopeTypeChanged(options.histogramScopeType); + } +} + +void HistogramPanel::updateHistAreaOptions() +{ + histogramArea->updateOptions( + showRed->get_active(), + showGreen->get_active(), + showBlue->get_active(), + showValue->get_active(), + showChro->get_active(), + options.histogramDrawMode, + options.histogramScopeType, + showBAR->get_active() + ); +} + +void HistogramPanel::updateHistRGBAreaOptions() +{ + histogramRGBArea->updateOptions( + showRed->get_active(), + showGreen->get_active(), + showBlue->get_active(), + showValue->get_active(), + showChro->get_active(), + showBAR->get_active() + ); +} + // // // @@ -378,7 +724,7 @@ double HistogramScaling::log(double vsize, double val) HistogramRGBArea::HistogramRGBArea () : val(0), r(0), g(0), b(0), valid(false), needRed(options.histogramRed), needGreen(options.histogramGreen), needBlue(options.histogramBlue), - needLuma(options.histogramLuma), needChroma(options.histogramChroma), rawMode(options.histogramRAW), + needLuma(options.histogramLuma), needChroma(options.histogramChroma), showMode(options.histogramBar), barDisplayed(options.histogramBar), parent(nullptr) { get_style_context()->add_class("drawingarea"); @@ -402,46 +748,41 @@ HistogramRGBArea::~HistogramRGBArea () } -Gtk::SizeRequestMode HistogramRGBArea::get_request_mode_vfunc () const +void HistogramRGBArea::getPreferredThickness(int& min_thickness, int& natural_thickness) const { - return Gtk::SIZE_REQUEST_HEIGHT_FOR_WIDTH; + int minimumLength = 0; + int naturalLength = 0; + getPreferredLength(minimumLength, naturalLength); + getPreferredThicknessForLength(minimumLength, min_thickness, natural_thickness); } -void HistogramRGBArea::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const -{ - int minimumWidth = 0; - int naturalWidth = 0; - get_preferred_width_vfunc(minimumWidth, naturalWidth); - get_preferred_height_for_width_vfunc (minimumWidth, minimum_height, natural_height); -} - -void HistogramRGBArea::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const +void HistogramRGBArea::getPreferredLength(int& min_length, int& natural_length) const { int s = RTScalable::getScale(); - minimum_width = 60 * s; - natural_width = 200 * s; + min_length = 60 * s; + natural_length = 200 * s; } -void HistogramRGBArea::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const +void HistogramRGBArea::getPreferredThicknessForLength(int length, int& min_thickness, int& natural_thickness) const { - int bHeight = width / 30; + int bThickness = length / 30; int s = RTScalable::getScale(); - if (bHeight > (10 * s)) { - bHeight = 10 * s; - } else if (bHeight < (5 * s)) { - bHeight = 5 * s; + if (bThickness > (10 * s)) { + bThickness = 10 * s; + } else if (bThickness < (5 * s)) { + bThickness = 5 * s; } - minimum_height = bHeight; - natural_height = bHeight; + min_thickness = bThickness; + natural_thickness = bThickness; } // unused? -void HistogramRGBArea::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const +void HistogramRGBArea::getPreferredLengthForThickness(int thickness, int& min_length, int& natural_length) const { - get_preferred_width_vfunc (minimum_width, natural_width); + getPreferredLength(min_length, natural_length); } bool HistogramRGBArea::getShow() @@ -449,9 +790,18 @@ bool HistogramRGBArea::getShow() return(showMode); } +void HistogramRGBArea::setShow(bool show) +{ + showMode = show; +} + void HistogramRGBArea::updateBackBuffer (int r, int g, int b, const Glib::ustring &profile, const Glib::ustring &profileW) { - if (!get_realized () || !showMode || rawMode) { + if (!get_realized () || !showMode || !( + options.histogramScopeType == ScopeType::HISTOGRAM + || options.histogramScopeType == ScopeType::PARADE + || options.histogramScopeType == ScopeType::WAVEFORM + )) { return; } @@ -479,75 +829,44 @@ void HistogramRGBArea::updateBackBuffer (int r, int g, int b, const Glib::ustrin cc->set_line_width (1.0 * s); if ( r != -1 && g != -1 && b != -1 ) { - double xpos; if (needRed) { // Red cc->set_source_rgb(1.0, 0.0, 0.0); - if (options.histogramDrawMode < 2) { - xpos = padding + r * (winw - padding * 2.0) / 255.0 + 0.5*s; - } else { - xpos = padding + HistogramScaling::log (255, r) * (winw - padding * 2.0) / 255.0 + 0.5*s; - } - cc->move_to(xpos, 0.0); - cc->line_to(xpos, winh - 0.0); - cc->stroke(); + drawBar(cc, r, 255.0, winw, winh, s); } if (needGreen) { // Green cc->set_source_rgb(0.0, 1.0, 0.0); - if (options.histogramDrawMode < 2) { - xpos = padding + g * (winw - padding * 2.0) / 255.0 + 0.5*s; - } else { - xpos = padding + HistogramScaling::log (255, g) * (winw - padding * 2.0) / 255.0 + 0.5*s; - } - cc->move_to(xpos, 0.0); - cc->line_to(xpos, winh - 0.0); - cc->stroke(); + drawBar(cc, g, 255.0, winw, winh, s); } if (needBlue) { // Blue cc->set_source_rgb(0.0, 0.4, 1.0); - if (options.histogramDrawMode < 2) { - xpos = padding + b * (winw - padding * 2.0) / 255.0 + 0.5*s; - } else { - xpos = padding + HistogramScaling::log (255, b) * (winw - padding * 2.0) / 255.0 + 0.5*s; - } - cc->move_to(xpos, 0.0); - cc->line_to(xpos, winh - 0.0); - cc->stroke(); + drawBar(cc, b, 255.0, winw, winh, s); } - if(needLuma || needChroma) { + if( + (needLuma || needChroma) + && (options.histogramScopeType == ScopeType::HISTOGRAM + || options.histogramScopeType == ScopeType::PARADE + || options.histogramScopeType == ScopeType::WAVEFORM) + ) { float Lab_L, Lab_a, Lab_b; rtengine::Color::rgb2lab01(profile, profileW, r / 255.f, g / 255.f, b / 255.f, Lab_L, Lab_a, Lab_b, options.rtSettings.HistogramWorking); if (needLuma) { // Luma cc->set_source_rgb(1.0, 1.0, 1.0); - if (options.histogramDrawMode < 2) { - xpos = padding + static_cast(Lab_L) * (winw - padding * 2.0) / 100.0 + 0.5*s; - } else { - xpos = padding + HistogramScaling::log(100, Lab_L) * (winw - padding * 2.0) / 100.0 + 0.5*s; - } - cc->move_to(xpos, 0.0); - cc->line_to(xpos, winh - 0.0); - cc->stroke(); + drawBar(cc, Lab_L, 100.0, winw, winh, s); } - if (needChroma) { + if (needChroma && options.histogramScopeType == ScopeType::HISTOGRAM) { // Chroma double chromaval = sqrt(Lab_a * Lab_a + Lab_b * Lab_b) / 1.8; cc->set_source_rgb(0.9, 0.9, 0.0); - if (options.histogramDrawMode < 2) { - xpos = padding + chromaval * (winw - padding * 2.0) / 100.0 + 0.5*s; - } else { - xpos = padding + HistogramScaling::log(100, chromaval) * (winw - padding * 2.0) / 100.0 + 0.5*s; - } - cc->move_to(xpos, 0.0); - cc->line_to(xpos, winh - 0.0); - cc->stroke(); + drawBar(cc, chromaval, 100.0, winw, winh, s); } } } @@ -594,7 +913,7 @@ void HistogramRGBArea::update (int valh, int rh, int gh, int bh) ); } -void HistogramRGBArea::updateOptions (bool r, bool g, bool b, bool l, bool c, bool raw, bool bar) +void HistogramRGBArea::updateOptions (bool r, bool g, bool b, bool l, bool c, bool bar) { options.histogramRed = needRed = r; @@ -602,18 +921,8 @@ void HistogramRGBArea::updateOptions (bool r, bool g, bool b, bool l, bool c, bo options.histogramBlue = needBlue = b; options.histogramLuma = needLuma = l; options.histogramChroma = needChroma = c; - options.histogramRAW = rawMode = raw; options.histogramBar = showMode = bar; - // Show/hide the RGB bar widget - if (bar && !barDisplayed) { - parent->add(*this); - barDisplayed = true; - } else if (!bar && barDisplayed) { - removeIfThere(parent, this, false); - barDisplayed = false; - } - } void HistogramRGBArea::on_realize () @@ -658,16 +967,105 @@ void HistogramRGBArea::factorChanged (double newFactor) factor = newFactor; } +void HistogramRGBAreaHori::drawBar(Cairo::RefPtr cc, double value, double max_value, int winw, int winh, double scale) +{ + double pos; + if (options.histogramDrawMode < 2) { + pos = padding + value * (winw - padding * 2.0) / max_value + 0.5 * scale; + } else { + pos = padding + HistogramScaling::log (max_value, value) * (winw - padding * 2.0) / max_value + 0.5 * scale; + } + cc->move_to(pos, 0.0); + cc->line_to(pos, winh - 0.0); + cc->stroke(); +} + +Gtk::SizeRequestMode HistogramRGBAreaHori::get_request_mode_vfunc () const +{ + return Gtk::SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +void HistogramRGBAreaHori::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const +{ + getPreferredThickness(minimum_height, natural_height); +} + +void HistogramRGBAreaHori::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const +{ + getPreferredLength(minimum_width, natural_width); +} + +void HistogramRGBAreaHori::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const +{ + getPreferredThicknessForLength(width, minimum_height, natural_height); +} + +void HistogramRGBAreaHori::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const +{ + getPreferredLengthForThickness(height, minimum_width, natural_width); +} + +void HistogramRGBAreaVert::drawBar(Cairo::RefPtr cc, double value, double max_value, int winw, int winh, double scale) +{ + double pos; + if (options.histogramDrawMode < 2 || options.histogramScopeType == ScopeType::PARADE || options.histogramScopeType == ScopeType::WAVEFORM) { + pos = padding + value * (winh - padding * 2.0 - 1) / max_value + 0.5 * scale; + } else { + pos = padding + HistogramScaling::log (max_value, value) * (winh - padding * 2.0) / max_value + 0.5 * scale; + } + cc->move_to(0.0, winh - pos); + cc->line_to(winw, winh - pos); + cc->stroke(); +} + +Gtk::SizeRequestMode HistogramRGBAreaVert::get_request_mode_vfunc () const +{ + return Gtk::SIZE_REQUEST_WIDTH_FOR_HEIGHT; +} + +void HistogramRGBAreaVert::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const +{ + getPreferredLength(minimum_height, natural_height); +} + +void HistogramRGBAreaVert::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const +{ + minimum_width = 10 * RTScalable::getScale(); + natural_width = minimum_width; +} + +void HistogramRGBAreaVert::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const +{ + getPreferredLengthForThickness(width, minimum_height, natural_height); +} + +void HistogramRGBAreaVert::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const +{ + get_preferred_width_vfunc(minimum_width, natural_width); +} + // // // // HistogramArea HistogramArea::HistogramArea (DrawModeListener *fml) : + vectorscope_scale(0), + vect_hc(0, 0), vect_hs(0, 0), + vect_hc_buffer_dirty(true), vect_hs_buffer_dirty(true), + waveform_scale(0), + rwave(0, 0), gwave(0, 0),bwave(0, 0), lwave(0, 0), + parade_buffer_r_dirty(true), parade_buffer_g_dirty(true), parade_buffer_b_dirty(true), + wave_buffer_dirty(true), wave_buffer_luma_dirty(true), valid(false), drawMode(options.histogramDrawMode), myDrawModeListener(fml), + scopeType(options.histogramScopeType), oldwidth(-1), oldheight(-1), + trace_brightness(1.0), needRed(options.histogramRed), needGreen(options.histogramGreen), needBlue(options.histogramBlue), - needLuma(options.histogramLuma), needChroma(options.histogramChroma), rawMode(options.histogramRAW), - isPressed(false), movingPosition(0.0) + needLuma(options.histogramLuma), needChroma(options.histogramChroma), + isPressed(false), movingPosition(0.0), + needPointer(options.histogramBar), + pointer_red(-1), pointer_green(-1), pointer_blue(-1), + pointer_a(0), pointer_b(0) { rhist(256); @@ -703,7 +1101,7 @@ Gtk::SizeRequestMode HistogramArea::get_request_mode_vfunc () const void HistogramArea::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const { - int s = (int)RTScalable::getScale(); + int s = RTScalable::getScale(); minimum_height = 100 * s; natural_height = 200 * s; } @@ -711,7 +1109,7 @@ void HistogramArea::get_preferred_height_vfunc (int &minimum_height, int &natura void HistogramArea::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const { - int s = (int)RTScalable::getScale(); + int s = RTScalable::getScale(); minimum_width = 200 * s; natural_width = 400 * s; } @@ -728,18 +1126,23 @@ void HistogramArea::get_preferred_width_for_height_vfunc (int height, int &minim get_preferred_width_vfunc (minimum_width, natural_width); } -void HistogramArea::updateOptions (bool r, bool g, bool b, bool l, bool c, bool raw, int mode) +void HistogramArea::updateOptions (bool r, bool g, bool b, bool l, bool c, int mode, ScopeType type, bool pointer) { + wave_buffer_dirty = wave_buffer_dirty || needRed != r || needGreen != g || needBlue != b; options.histogramRed = needRed = r; options.histogramGreen = needGreen = g; options.histogramBlue = needBlue = b; options.histogramLuma = needLuma = l; options.histogramChroma = needChroma = c; - options.histogramRAW = rawMode = raw; options.histogramDrawMode = drawMode = mode; + options.histogramScopeType = scopeType = type; + options.histogramBar = needPointer = pointer; +} - updateBackBuffer (); +bool HistogramArea::updatePending(void) +{ + return haih->pending > 0 && !haih->destroyed; } void HistogramArea::update( @@ -750,18 +1153,53 @@ void HistogramArea::update( const LUTu& histChroma, const LUTu& histRedRaw, const LUTu& histGreenRaw, - const LUTu& histBlueRaw + const LUTu& histBlueRaw, + int vectorscopeScale, + const array2D& vectorscopeHC, + const array2D& vectorscopeHS, + int waveformScale, + const array2D& waveformRed, + const array2D& waveformGreen, + const array2D& waveformBlue, + const array2D& waveformLuma ) { if (histRed) { - rhist = histRed; - ghist = histGreen; - bhist = histBlue; - lhist = histLuma; - chist = histChroma; - rhistRaw = histRedRaw; - ghistRaw = histGreenRaw; - bhistRaw = histBlueRaw; + switch (scopeType) { + case ScopeType::HISTOGRAM: + rhist = histRed; + ghist = histGreen; + bhist = histBlue; + lhist = histLuma; + chist = histChroma; + break; + case ScopeType::HISTOGRAM_RAW: + rhistRaw = histRedRaw; + ghistRaw = histGreenRaw; + bhistRaw = histBlueRaw; + break; + case ScopeType::PARADE: + case ScopeType::WAVEFORM: + waveform_scale = waveformScale; + rwave = waveformRed; + gwave = waveformGreen; + bwave = waveformBlue; + lwave = waveformLuma; + parade_buffer_r_dirty = parade_buffer_g_dirty = parade_buffer_b_dirty = wave_buffer_dirty = wave_buffer_luma_dirty = true; + break; + case ScopeType::VECTORSCOPE_HS: + vectorscope_scale = vectorscopeScale; + vect_hs = vectorscopeHS; + vect_hs_buffer_dirty = true; + break; + case ScopeType::VECTORSCOPE_HC: + vectorscope_scale = vectorscopeScale; + vect_hc = vectorscopeHC; + vect_hc_buffer_dirty = true; + break; + case ScopeType::NONE: + break; + } valid = true; } else { valid = false; @@ -796,7 +1234,6 @@ void HistogramArea::update( void HistogramArea::updateBackBuffer () { - if (!get_realized ()) { return; } @@ -830,40 +1267,53 @@ void HistogramArea::updateBackBuffer () cr->set_dash (ch_ds, 0); // determine the number of h-gridlines based on current h - int nrOfHGridPartitions = (int)rtengine::min (16.0, pow (2.0, floor ((h - 100) / 250) + 2)); + int nrOfHGridPartitions = static_cast(rtengine::min (16.0, pow (2.0, floor ((h - 100) / 250) + 2))); int nrOfVGridPartitions = 8; // always show 8 stops (lines at 1,3,7,15,31,63,127) // draw vertical gridlines - for (int i = 0; i <= nrOfVGridPartitions; i++) { - double xpos = padding + 0.5; - if (options.histogramDrawMode < 2) { - xpos += (pow(2.0,i) - 1) * (w - padding * 2.0) / 255.0; - } else { - xpos += HistogramScaling::log (255, pow(2.0,i) - 1) * (w - padding * 2.0) / 255.0; + if (options.histogramScopeType == ScopeType::HISTOGRAM || options.histogramScopeType == ScopeType::HISTOGRAM_RAW) { + for (int i = 0; i <= nrOfVGridPartitions; i++) { + double xpos = padding + 0.5; + if (options.histogramDrawMode < 2) { + xpos += (pow(2.0,i) - 1) * (w - padding * 2.0) / 255.0; + } else { + xpos += HistogramScaling::log (255, pow(2.0,i) - 1) * (w - padding * 2.0) / 255.0; + } + cr->move_to (xpos, 0.); + cr->line_to (xpos, h); + cr->stroke (); } - cr->move_to (xpos, 0.); - cr->line_to (xpos, h); - cr->stroke (); } // draw horizontal gridlines - if (options.histogramDrawMode == 0) { + if (options.histogramScopeType == ScopeType::PARADE || options.histogramScopeType == ScopeType::WAVEFORM) { + for (int i = 0; i <= nrOfVGridPartitions; i++) { + const double ypos = h - padding - (pow(2.0,i) - 1) * (h - 2 * padding - 1) / 255.0; + cr->move_to(0, ypos); + cr->line_to(w, ypos); + cr->stroke(); + } + } else if (options.histogramScopeType == ScopeType::VECTORSCOPE_HC || options.histogramScopeType == ScopeType::VECTORSCOPE_HS) { + // Vectorscope has no gridlines. + } else if (options.histogramDrawMode == 0) { for (int i = 1; i < nrOfHGridPartitions; i++) { - cr->move_to (padding, i * (double)h / nrOfHGridPartitions + 0.5); - cr->line_to (w - padding, i * (double)h / nrOfHGridPartitions + 0.5); + cr->move_to (padding, i * static_cast(h) / nrOfHGridPartitions + 0.5); + cr->line_to (w - padding, i * static_cast(h) / nrOfHGridPartitions + 0.5); cr->stroke (); } } else { for (int i = 1; i < nrOfHGridPartitions; i++) { - cr->move_to (padding, h - HistogramScaling::log (h, i * (double)h / nrOfHGridPartitions) + 0.5); - cr->line_to (w - padding, h - HistogramScaling::log (h, i * (double)h / nrOfHGridPartitions) + 0.5); + cr->move_to (padding, h - HistogramScaling::log (h, i * static_cast(h) / nrOfHGridPartitions) + 0.5); + cr->line_to (w - padding, h - HistogramScaling::log (h, i * static_cast(h) / nrOfHGridPartitions) + 0.5); cr->stroke (); } } cr->unset_dash(); - if (valid) { + if (valid && (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW)) { + bool rawMode = scopeType == ScopeType::HISTOGRAM_RAW; + // For RAW mode use the other hists LUTu& rh = rawMode ? rhistRaw : rhist; LUTu& gh = rawMode ? ghistRaw : ghist; @@ -970,6 +1420,12 @@ void HistogramArea::updateBackBuffer () drawMarks(cr, bhchanged, realhistheight, w, ui, oi); } + } else if (scopeType == ScopeType::PARADE && rwave.getWidth() > 0) { + drawParade(cr, w, h); + } else if (scopeType == ScopeType::WAVEFORM && rwave.getWidth() > 0) { + drawWaveform(cr, w, h); + } else if (scopeType == ScopeType::VECTORSCOPE_HC || scopeType == ScopeType::VECTORSCOPE_HS) { + drawVectorscope(cr, w, h); } // Draw the frame's border @@ -981,6 +1437,24 @@ void HistogramArea::updateBackBuffer () setDirty(false); } +bool HistogramArea::updatePointer(int r, int g, int b, const Glib::ustring &profile, const Glib::ustring &profileW) +{ + if (!needPointer || !(scopeType == ScopeType::VECTORSCOPE_HC || scopeType == ScopeType::VECTORSCOPE_HS)) { + return false; + } + if (pointer_red == r && pointer_green == g && pointer_blue == b) { + return false; + } + + float L; + pointer_red = r; + pointer_green = g; + pointer_blue = b; + Color::rgb2lab01(profile, profileW, r / 255.f, g / 255.f, b / 255.f, L, pointer_a, pointer_b, options.rtSettings.HistogramWorking); + updateBackBuffer(); + return true; +} + void HistogramArea::on_realize () { @@ -998,15 +1472,15 @@ void HistogramArea::drawCurve(Cairo::RefPtr &cr, scale = scale <= 0.0 ? 0.001 : scale; // avoid division by zero and negative values for (int i = 0; i < 256; i++) { - double val = data[i] * (double)vsize / scale; + double val = data[i] * static_cast(vsize) / scale; if (drawMode > 0) { // scale y for single and double log-scale - val = HistogramScaling::log ((double)vsize, val); + val = HistogramScaling::log (static_cast(vsize), val); } double iscaled = i; if (drawMode == 2) { // scale x for double log-scale - iscaled = HistogramScaling::log (255.0, (double)i); + iscaled = HistogramScaling::log (255.0, static_cast(i)); } double posX = padding + iscaled * (hsize - padding * 2.0) / 255.0; @@ -1034,10 +1508,379 @@ void HistogramArea::drawMarks(Cairo::RefPtr &cr, cr->fill(); } +void HistogramArea::drawParade(Cairo::RefPtr &cr, int w, int h) +{ + // Arbitrary scale factor divided by current scale. + const float scale = trace_brightness * 32.f * 255.f / waveform_scale; + const int wave_width = rwave.getWidth(); + const int wave_height = rwave.getHeight(); + + // See Cairo documentation on stride. + const int cairo_stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_ARGB32, rwave.getWidth()); + const auto buffer_size = static_cast::size_type>(wave_height) * cairo_stride; + + if (parade_buffer_r_dirty && needRed) { + parade_buffer_r.assign(buffer_size, 0); + assert(parade_buffer_r.size() % 4 == 0); + + for (int val = 0; val < wave_height; val++) { + const int* const r_row = rwave[val]; + std::uint32_t* const buffer_r_row = reinterpret_cast(parade_buffer_r.data() + (255 - val) * cairo_stride); + for (int col = 0; col < wave_width; col++) { + const unsigned char r = std::min(scale * r_row[col], 0xff); + if (r != 0) { + buffer_r_row[col] = (r << 16) | (r << 24); + } + } + } + + parade_buffer_r_dirty = false; + } + + if (parade_buffer_g_dirty && needGreen) { + parade_buffer_g.assign(buffer_size, 0); + assert(parade_buffer_g.size() % 4 == 0); + + for (int val = 0; val < wave_height; val++) { + const int* const g_row = gwave[val]; + std::uint32_t* const buffer_g_row = reinterpret_cast(parade_buffer_g.data() + (255 - val) * cairo_stride); + for (int col = 0; col < wave_width; col++) { + const unsigned char g = std::min(scale * g_row[col], 0xff); + if (g != 0) { + buffer_g_row[col] = (g << 8) | (g << 24); + } + } + } + + parade_buffer_g_dirty = false; + } + + if (parade_buffer_b_dirty && needBlue) { + parade_buffer_b.assign(buffer_size, 0); + assert(parade_buffer_b.size() % 4 == 0); + + for (int val = 0; val < wave_height; val++) { + const int* const b_row = bwave[val]; + std::uint32_t* const buffer_b_row = reinterpret_cast(parade_buffer_b.data() + (255 - val) * cairo_stride); + for (int col = 0; col < wave_width; col++) { + const unsigned char b = std::min(scale * b_row[col], 0xff); + if (b != 0) { + const unsigned char green = b / 2; // Make blue easier to see. + buffer_b_row[col] = b | (green << 8) | (b << 24); + } + } + } + + parade_buffer_b_dirty = false; + } + + if (wave_buffer_luma_dirty && needLuma) { + wave_buffer_luma.assign(buffer_size, 0); + assert(wave_buffer_luma.size() % 4 == 0); + + for (int val = 0; val < wave_height; val++) { + const int* const l_row = lwave[val]; + std::uint32_t* const buffer_row = + reinterpret_cast(wave_buffer_luma.data() + (255 - val) * cairo_stride); + for (int col = 0; col < wave_width; col++) { + const unsigned char l = std::min(scale * l_row[col], 0xff); + buffer_row[col] = l | (l << 8) | (l << 16) | (l << 24); + } + } + + wave_buffer_luma_dirty = false; + } + + std::vector buffers; + if (needLuma) { + buffers.push_back(wave_buffer_luma.data()); + } + if (needRed) { + buffers.push_back(parade_buffer_r.data()); + } + if (needGreen) { + buffers.push_back(parade_buffer_g.data()); + } + if (needBlue) { + buffers.push_back(parade_buffer_b.data()); + } + + auto orig_matrix = cr->get_matrix(); + const double display_wave_width = static_cast(w) / buffers.size(); + for (unsigned i = 0; i < buffers.size(); i++) { + Cairo::RefPtr surface; + cr->translate(i * display_wave_width, padding); + cr->scale(display_wave_width / wave_width, (h - 2 * padding) / wave_height); + surface = Cairo::ImageSurface::create( + buffers[i], Cairo::FORMAT_ARGB32, wave_width, wave_height, cairo_stride); + cr->set_source(surface, 0, 0); + cr->set_operator(Cairo::OPERATOR_OVER); + cr->paint(); + surface->finish(); + cr->set_matrix(orig_matrix); + } +} + +void HistogramArea::drawVectorscope(Cairo::RefPtr &cr, int w, int h) +{ + if (scopeType != ScopeType::VECTORSCOPE_HC && scopeType != ScopeType::VECTORSCOPE_HS) { + return; + } + + const auto& vect = (scopeType == ScopeType::VECTORSCOPE_HC) ? vect_hc : vect_hs; + auto& vect_buffer = (scopeType == ScopeType::VECTORSCOPE_HC) ? vect_hc_buffer : vect_hs_buffer; + auto& vect_buffer_dirty = (scopeType == ScopeType::VECTORSCOPE_HC) ? vect_hc_buffer_dirty : vect_hs_buffer_dirty; + + const int vect_width = vect.getWidth(); + const int vect_height = vect.getHeight(); + // Arbitrary scale factor multiplied by vectorscope area and divided by + // current scale. + const float scale = trace_brightness * 8.f * vect_width * vect_height / vectorscope_scale; + + // See Cairo documentation on stride. + const int cairo_stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_ARGB32, vect_width); + + if (vect_buffer_dirty && vectorscope_scale > 0) { + if (vect_buffer.size() != static_cast(cairo_stride) * vect_height) { + vect_buffer.resize(static_cast(cairo_stride) * vect_height); + } + + assert(vect_buffer.size() % 4 == 0); + + for (int y = 0; y < vect_height; y++) { + const int* const vect_row = vect[y]; + std::uint32_t* const buffer_row = + reinterpret_cast(vect_buffer.data() + (vect_height - 1 - y) * cairo_stride); + for (int x = 0; x < vect_width; x++) { + const unsigned char value = std::min(scale * vect_row[x], 0xff); + buffer_row[x] = value | (value << 8) | (value << 16) | (value << 24); + } + } + + vect_buffer_dirty = false; + } + + const bool fit_width = + vect_width * (h - 2 * padding) > vect_height * (w - 2 * padding); + const float scope_scale = fit_width ? + (w - 2 * padding) / vect_width : (h - 2 * padding) / vect_height; + const float scope_size = (vectorscope_scale > 0) ? + scope_scale * std::max(vect_width, vect_height) : std::min(w, h) - 2 * padding; + const float o_x = (w - scope_scale * vect_width) / 2; + const float o_y = (h - scope_scale * vect_height) / 2; + const double s = RTScalable::getScale(); + auto orig_matrix = cr->get_matrix(); + const double line_length = scope_size / 2.0; + std::valarray ch_ds(1); + + cr->translate(w / 2.0, h / 2.0); + cr->set_line_width (1.0 * s); + cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL); + ch_ds[0] = 4; + + if (scopeType == ScopeType::VECTORSCOPE_HS) { // Hue-Saturation. + // RYGCBM lines. + cr->set_line_width (2.0 * s); + constexpr double color_labels[6][3] = { + {1, 0, 0}, // R + {0, 1, 0}, // G + {0, 0, 1}, // B + {0, 1, 1}, // C + {1, 0, 1}, // M + {1, 1, 0}, // Y + }; + for (int i = 0; i < 3; i++) { + auto gradient = Cairo::LinearGradient::create(-line_length, 0, line_length, 0); + const double (&color_1)[3] = color_labels[i]; + const double (&color_2)[3] = color_labels[i + 3]; + cr->set_source(gradient); + gradient->add_color_stop_rgba(0, color_2[0], color_2[1], color_2[2], 0.5); + gradient->add_color_stop_rgba(0.5, 1, 1, 1, 0.25); + gradient->add_color_stop_rgba(1, color_1[0], color_1[1], color_1[2], 0.5); + cr->move_to(-line_length, 0); + cr->line_to(line_length, 0); + cr->rotate_degrees(-120); + cr->stroke(); + } + cr->set_line_width (1.0 * s); + cr->set_source_rgba (1, 1, 1, 0.25); + // 100% saturation circle. + cr->arc(0, 0, scope_size / 2.0, 0, 2 * RT_PI); + cr->stroke(); + // 25%, 50%, and 75% saturation. + cr->set_dash(ch_ds, 0); + for (int i = 1; i < 4; i++) { + cr->arc(0, 0, i * scope_size / 8.0, 0, 2 * RT_PI); + cr->stroke(); + } + // HSV skin tone line derived from -I axis of YIQ. + cr->rotate(-0.134900 * RT_PI); + cr->move_to(0, 0); + cr->line_to(line_length, 0); + cr->stroke(); + cr->unset_dash(); + } else if (scopeType == ScopeType::VECTORSCOPE_HC) { // Hue-Chroma. + // a and b axes. + Cairo::RefPtr gradient; + cr->set_line_width (2.0 * s); + gradient = Cairo::LinearGradient::create(0, -line_length, 0, line_length); + cr->set_source(gradient); + gradient->add_color_stop_rgba(0, 1, 1, 0, 0.5); // "yellow" + gradient->add_color_stop_rgba(0.5, 1, 1, 1, 0.25); // neutral + gradient->add_color_stop_rgba(1, 0, 0, 1, 0.5); // "blue" + cr->move_to(0, 0); + cr->line_to(0, line_length); + cr->move_to(0, 0); + cr->line_to(0, -line_length); + cr->stroke(); + gradient = Cairo::LinearGradient::create(-line_length, 0, line_length, 0); + cr->set_source(gradient); + gradient->add_color_stop_rgba(0, 0, 1, 0, 0.5); // "green" + gradient->add_color_stop_rgba(0.5, 1, 1, 1, 0.25); // neutral + gradient->add_color_stop_rgba(1, 1, 0, 1, 0.5); // "magenta" + cr->move_to(0, 0); + cr->line_to(line_length, 0); + cr->move_to(0, 0); + cr->line_to(-line_length, 0); + cr->stroke(); + cr->set_source_rgba (1, 1, 1, 0.25); + cr->set_line_width (1.0 * s); + // 25%, 50%, 75%, and 100% of standard chroma range. + cr->set_dash(ch_ds, 0); + for (int i = 1; i <= 4; i++) { + cr->arc(0, 0, i * scope_size / 8.0, 0, 2 * RT_PI); + cr->stroke(); + } + // CIELAB skin tone line, approximated by 50% saturation and + // value along the HSV skin tone line. + cr->rotate(-0.321713 * RT_PI); + cr->move_to(0, 0); + cr->line_to(line_length, 0); + cr->stroke(); + cr->unset_dash(); + } + cr->set_matrix(orig_matrix); + + // Vectorscope trace. + if (vectorscope_scale > 0) { + Cairo::RefPtr surface = Cairo::ImageSurface::create( + vect_buffer.data(), Cairo::FORMAT_ARGB32, vect_width, vect_height, cairo_stride); + cr->translate(o_x, o_y); + cr->scale(scope_scale, scope_scale); + cr->set_source(surface, 0, 0); + cr->set_operator(Cairo::OPERATOR_OVER); + cr->paint(); + surface->finish(); + cr->set_matrix(orig_matrix); + + if (needPointer && pointer_red >= 0 && pointer_green >= 0 && pointer_blue >= 0) { + float cx, cy; + if (scopeType == ScopeType::VECTORSCOPE_HS) { + float H, S, L; + Color::rgb2hslfloat(pointer_red * 257.f, pointer_green * 257.f, pointer_blue * 257.f, H, S, L); + cx = (w + scope_size * S * std::cos(H * 2 * RT_PI_F)) / 2; + cy = (h - scope_size * S * std::sin(H * 2 * RT_PI_F)) / 2; + } else { + constexpr float ab_factor = 1.f / 256.f; + cx = w / 2.f + scope_size * pointer_a * ab_factor; + cy = h / 2.f - scope_size * pointer_b * ab_factor; + } + const float crosshair_size = 20.f * s; + cr->set_source_rgba(1, 1, 1, 0.5); + cr->move_to(cx - crosshair_size, cy); + cr->line_to(cx + crosshair_size, cy); + cr->move_to(cx, cy - crosshair_size); + cr->line_to(cx, cy + crosshair_size); + cr->stroke(); + cr->arc(cx, cy, 3 * s, 0, 2 * RT_PI); + cr->set_source_rgb(1, 1, 1); + cr->fill_preserve(); + cr->set_source_rgb(0, 0, 0); + cr->set_line_width (1.0 * s); + cr->stroke(); + } + } +} + +void HistogramArea::drawWaveform(Cairo::RefPtr &cr, int w, int h) +{ + // Arbitrary scale factor divided by current scale. + const float scale = trace_brightness * 32.f * 255.f / waveform_scale; + const int wave_width = rwave.getWidth(); + const int wave_height = rwave.getHeight(); + + // See Cairo documentation on stride. + const int cairo_stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_ARGB32, rwave.getWidth()); + const auto buffer_size = static_cast::size_type>(wave_height) * cairo_stride; + + if (wave_buffer_dirty && (needRed || needGreen || needBlue)) { + wave_buffer.assign(buffer_size, 0); + assert(wave_buffer.size() % 4 == 0); + + for (int val = 0; val < wave_height; val++) { + const int* const r_row = rwave[val]; + const int* const g_row = gwave[val]; + const int* const b_row = bwave[val]; + std::uint32_t* const buffer_row = reinterpret_cast(wave_buffer.data() + (255 - val) * cairo_stride); + for (int col = 0; col < wave_width; col++) { + const unsigned char r = needRed ? std::min(scale * r_row[col], 0xff) : 0; + const unsigned char g = needGreen ? std::min(scale * g_row[col], 0xff) : 0; + const unsigned char b = needBlue ? std::min(scale * b_row[col], 0xff) : 0; + const unsigned char value = rtengine::max(r, g, b); + if (value != 0) { + // Ensures correct order regardless of endianness. + buffer_row[col] = b | (g << 8) | (r << 16) | (value << 24); + } + } + } + + wave_buffer_dirty = false; + } + + if (wave_buffer_luma_dirty && needLuma) { + wave_buffer_luma.assign(buffer_size, 0); + assert(wave_buffer_luma.size() % 4 == 0); + + for (int val = 0; val < wave_height; val++) { + const int* const l_row = lwave[val]; + std::uint32_t* const buffer_row = + reinterpret_cast(wave_buffer_luma.data() + (255 - val) * cairo_stride); + for (int col = 0; col < wave_width; col++) { + const unsigned char l = std::min(scale * l_row[col], 0xff); + buffer_row[col] = l | (l << 8) | (l << 16) | (l << 24); + } + } + + wave_buffer_luma_dirty = false; + } + + Cairo::RefPtr surface; + auto orig_matrix = cr->get_matrix(); + cr->translate(0, padding); + cr->scale(static_cast(w) / wave_width, (h - 2 * padding) / wave_height); + if (needLuma) { + surface = Cairo::ImageSurface::create( + wave_buffer_luma.data(), Cairo::FORMAT_ARGB32, wave_width, wave_height, cairo_stride); + cr->set_source(surface, 0, 0); + cr->set_operator(Cairo::OPERATOR_OVER); + cr->paint(); + surface->finish(); + } + if (needRed || needGreen || needBlue) { + surface = Cairo::ImageSurface::create( + wave_buffer.data(), Cairo::FORMAT_ARGB32, wave_width, wave_height, cairo_stride); + cr->set_source(surface, 0, 0); + cr->set_operator(Cairo::OPERATOR_OVER); + cr->paint(); + surface->finish(); + } + cr->set_matrix(orig_matrix); +} + bool HistogramArea::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) { - if (get_width() != oldwidth || get_height() != oldheight || isDirty ()) { + if (!updatePending() && (get_width() != oldwidth || get_height() != oldheight || isDirty())) { updateBackBuffer (); } @@ -1054,7 +1897,10 @@ bool HistogramArea::on_button_press_event (GdkEventButton* event) isPressed = true; movingPosition = event->x; - if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { + if ( + event->type == GDK_2BUTTON_PRESS && event->button == 1 + && (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW) + ) { drawMode = (drawMode + 1) % 3; options.histogramDrawMode = (options.histogramDrawMode + 1) % 3; @@ -1078,8 +1924,18 @@ bool HistogramArea::on_button_release_event (GdkEventButton* event) bool HistogramArea::on_motion_notify_event (GdkEventMotion* event) { - if (isPressed) - { + if ( + drawMode == 0 + && (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW) + ) { + return false; + } + + if (!isPressed) { + return true; + } + + if (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW) { // Adjust log scale. double mod = 1 + (event->x - movingPosition) / get_width(); factor /= mod; @@ -1092,11 +1948,45 @@ bool HistogramArea::on_motion_notify_event (GdkEventMotion* event) setDirty(true); queue_draw (); + } else if ( + scopeType == ScopeType::PARADE + || scopeType == ScopeType::WAVEFORM + || scopeType == ScopeType::VECTORSCOPE_HC + || scopeType == ScopeType::VECTORSCOPE_HS + ) { // Adjust brightness. + constexpr float RANGE = MAX_BRIGHT / MIN_BRIGHT; + double dx = (event->x - movingPosition) / get_width(); + float new_brightness = LIM(trace_brightness * pow(RANGE, dx), MIN_BRIGHT, MAX_BRIGHT); + setBrightness(new_brightness); + movingPosition = event->x; } return true; } +float HistogramArea::getBrightness(void) +{ + return trace_brightness; +} + +void HistogramArea::setBrightness(float brightness) +{ + brightness = LIM(brightness, MIN_BRIGHT, MAX_BRIGHT); + if (brightness != trace_brightness) { + parade_buffer_r_dirty = parade_buffer_g_dirty = parade_buffer_b_dirty = wave_buffer_dirty = wave_buffer_luma_dirty = vect_hc_buffer_dirty = vect_hs_buffer_dirty = true; + trace_brightness = brightness; + setDirty(true); + queue_draw(); + + signal_brightness_changed.emit(trace_brightness); + } +} + +HistogramArea::SignalBrightnessChanged HistogramArea::getBrighnessChangedSignal(void) +{ + return signal_brightness_changed; +} + HistogramArea::type_signal_factor_changed HistogramArea::signal_factor_changed() { return sigFactorChanged; diff --git a/rtgui/histogrampanel.h b/rtgui/histogrampanel.h index 740b0a12c..393df51a5 100644 --- a/rtgui/histogrampanel.h +++ b/rtgui/histogrampanel.h @@ -18,6 +18,8 @@ */ #pragma once +#include + #include #include @@ -26,8 +28,10 @@ #include "delayed.h" #include "guiutils.h" +#include "options.h" #include "pointermotionlistener.h" +#include "../rtengine/array2D.h" #include "../rtengine/LUT.h" #include "../rtengine/noncopyable.h" @@ -54,7 +58,7 @@ public: double log (double vsize, double val); }; -class HistogramRGBArea final : public Gtk::DrawingArea, public BackBuffer, private HistogramScaling, public rtengine::NonCopyable +class HistogramRGBArea : public Gtk::DrawingArea, public BackBuffer, protected HistogramScaling, public rtengine::NonCopyable { private: typedef const double (*TMatrix)[3]; @@ -74,7 +78,6 @@ protected: bool needBlue; bool needLuma; bool needChroma; - bool rawMode; bool showMode; bool barDisplayed; @@ -84,32 +87,58 @@ protected: HistogramRGBAreaIdleHelper* harih; + /** Draw an indicator bar for the value. */ + virtual void drawBar(Cairo::RefPtr cc, double value, double max_value, int winw, int winh, double scale) = 0; + + void getPreferredThickness(int& min_thickness, int& natural_length) const; + void getPreferredLength(int& min_length, int& natural_length) const; + void getPreferredThicknessForLength(int length, int& min_thickness, int& natural_length) const; + void getPreferredLengthForThickness(int thickness, int& min_length, int& natural_length) const; + public: HistogramRGBArea(); ~HistogramRGBArea() override; void updateBackBuffer (int r, int g, int b, const Glib::ustring &profile = "", const Glib::ustring &profileW = ""); bool getShow (); + void setShow(bool show); void setParent (Gtk::Grid* p) { parent = p; }; void update (int val, int rh, int gh, int bh); - void updateOptions (bool r, bool g, bool b, bool l, bool c, bool raw, bool show); + void updateOptions (bool r, bool g, bool b, bool l, bool c, bool show); void on_realize() override; bool on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) override; bool on_button_press_event (GdkEventButton* event) override; void factorChanged (double newFactor); +}; + +class HistogramRGBAreaHori final : public HistogramRGBArea +{ private: + void drawBar(Cairo::RefPtr cc, double value, double max_value, int winw, int winh, double scale) override; + Gtk::SizeRequestMode get_request_mode_vfunc () const override; void get_preferred_height_vfunc (int& minimum_height, int& natural_height) const override; void get_preferred_width_vfunc (int &minimum_width, int &natural_width) const override; void get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const override; void get_preferred_width_for_height_vfunc (int h, int &minimum_width, int &natural_width) const override; +}; +class HistogramRGBAreaVert final : public HistogramRGBArea +{ +private: + void drawBar(Cairo::RefPtr cc, double value, double max_value, int winw, int winh, double scale) override; + + Gtk::SizeRequestMode get_request_mode_vfunc () const override; + void get_preferred_height_vfunc (int& minimum_height, int& natural_height) const override; + void get_preferred_width_vfunc (int &minimum_width, int &natural_width) const override; + void get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const override; + void get_preferred_width_for_height_vfunc (int h, int &minimum_width, int &natural_width) const override; }; class DrawModeListener @@ -123,7 +152,10 @@ class HistogramArea final : public Gtk::DrawingArea, public BackBuffer, private { public: typedef sigc::signal type_signal_factor_changed; + typedef sigc::signal SignalBrightnessChanged; + static constexpr float MIN_BRIGHT = 0.1; + static constexpr float MAX_BRIGHT = 3; private: IdleRegister idle_register; type_signal_factor_changed sigFactorChanged; @@ -131,26 +163,49 @@ private: protected: LUTu rhist, ghist, bhist, lhist, chist; LUTu rhistRaw, ghistRaw, bhistRaw, lhistRaw; //lhistRaw is unused? + int vectorscope_scale; + array2D vect_hc, vect_hs; + std::vector vect_hc_buffer, vect_hs_buffer; + bool vect_hc_buffer_dirty, vect_hs_buffer_dirty; + int waveform_scale; + array2D rwave, gwave, bwave, lwave; + std::vector parade_buffer_r; + std::vector parade_buffer_g; + std::vector parade_buffer_b; + bool parade_buffer_r_dirty, parade_buffer_g_dirty, parade_buffer_b_dirty; + std::vector wave_buffer; + std::vector wave_buffer_luma; + bool wave_buffer_dirty, wave_buffer_luma_dirty; bool valid; int drawMode; DrawModeListener *myDrawModeListener; + Options::ScopeType scopeType; int oldwidth, oldheight; + /// Intensity of waveform and vectorscope trace. + float trace_brightness; bool needRed, needGreen, needBlue, needLuma, needChroma; - bool rawMode; bool isPressed; double movingPosition; + bool needPointer; double padding = 5.0; HistogramAreaIdleHelper* haih; + int pointer_red, pointer_green, pointer_blue; + float pointer_a, pointer_b; + + SignalBrightnessChanged signal_brightness_changed; + public: explicit HistogramArea(DrawModeListener *fml = nullptr); ~HistogramArea() override; void updateBackBuffer (); + /// Update pointer values. Returns true if widget needs redrawing. + bool updatePointer(int r, int g, int b, const Glib::ustring &profile = "", const Glib::ustring &profileW = ""); void update( const LUTu& histRed, const LUTu& histGreen, @@ -159,19 +214,35 @@ public: const LUTu& histChroma, const LUTu& histRedRaw, const LUTu& histGreenRaw, - const LUTu& histBlueRaw + const LUTu& histBlueRaw, + int vectorscopeScale, + const array2D& vectorscopeHC, + const array2D& vectorscopeHS, + int waveformScale, + const array2D& waveformRed, + const array2D& waveformGreen, + const array2D& waveformBlue, + const array2D& waveformLuma ); - void updateOptions (bool r, bool g, bool b, bool l, bool c, bool raw, int mode); + void updateOptions (bool r, bool g, bool b, bool l, bool c, int mode, Options::ScopeType type, bool pointer); + bool updatePending(); void on_realize() override; bool on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) override; bool on_button_press_event (GdkEventButton* event) override; bool on_button_release_event (GdkEventButton* event) override; bool on_motion_notify_event (GdkEventMotion* event) override; + float getBrightness(void); + /** Set the trace brightness, with 1 being normal. */ + void setBrightness(float brightness); + SignalBrightnessChanged getBrighnessChangedSignal(void); type_signal_factor_changed signal_factor_changed(); private: void drawCurve(Cairo::RefPtr &cr, const LUTu & data, double scale, int hsize, int vsize); void drawMarks(Cairo::RefPtr &cr, const LUTu & data, double scale, int hsize, int & ui, int & oi); + void drawParade(Cairo::RefPtr &cr, int hsize, int vsize); + void drawVectorscope(Cairo::RefPtr &cr, int hsize, int vsize); + void drawWaveform(Cairo::RefPtr &cr, int hsize, int vsize); Gtk::SizeRequestMode get_request_mode_vfunc () const override; void get_preferred_height_vfunc (int& minimum_height, int& natural_height) const override; void get_preferred_width_vfunc (int &minimum_width, int &natural_width) const override; @@ -179,6 +250,12 @@ private: void get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const override; }; +class HistogramPanelListener +{ +public: + virtual void scopeTypeChanged(Options::ScopeType new_type) = 0; +}; + class HistogramPanel final : public Gtk::Grid, public PointerMotionListener, public DrawModeListener, public rtengine::NonCopyable { private: @@ -188,22 +265,33 @@ protected: Gtk::Grid* gfxGrid; Gtk::Grid* buttonGrid; + Gtk::Box* persistentButtons; + Gtk::Box* optionButtons; HistogramArea* histogramArea; HistogramRGBArea* histogramRGBArea; + std::unique_ptr histogramRGBAreaHori; + std::unique_ptr histogramRGBAreaVert; Gtk::ToggleButton* showRed; Gtk::ToggleButton* showGreen; Gtk::ToggleButton* showBlue; Gtk::ToggleButton* showValue; - Gtk::ToggleButton* showRAW; Gtk::ToggleButton* showBAR; Gtk::ToggleButton* showChro; Gtk::Button* showMode; + Gtk::ToggleButton* scopeOptions; + Gtk::Scale* brightnessWidget; + + Gtk::RadioButton* scopeHistBtn; + Gtk::RadioButton* scopeHistRawBtn; + Gtk::RadioButton* scopeParadeBtn; + Gtk::RadioButton* scopeWaveBtn; + Gtk::RadioButton* scopeVectHcBtn; + Gtk::RadioButton* scopeVectHsBtn; Gtk::Image *redImage; Gtk::Image *greenImage; Gtk::Image *blueImage; Gtk::Image *valueImage; - Gtk::Image *rawImage; Gtk::Image *barImage; Gtk::Image *chroImage; @@ -211,7 +299,6 @@ protected: Gtk::Image *greenImage_g; Gtk::Image *blueImage_g; Gtk::Image *valueImage_g; - Gtk::Image *rawImage_g; Gtk::Image *barImage_g; Gtk::Image *chroImage_g; @@ -219,8 +306,14 @@ protected: Gtk::Image *mode1Image; Gtk::Image *mode2Image; + HistogramPanelListener* panel_listener; + + sigc::connection brightness_changed_connection; sigc::connection rconn; void setHistInvalid (); + void showRGBBar(); + void updateHistAreaOptions(); + void updateHistRGBAreaOptions(); public: @@ -235,9 +328,18 @@ public: const LUTu& histChroma, const LUTu& histRedRaw, const LUTu& histGreenRaw, - const LUTu& histBlueRaw) + const LUTu& histBlueRaw, + int vectorscopeScale, + const array2D& vectorscopeHC, + const array2D& vectorscopeHS, + int waveformScale, + const array2D& waveformRed, + const array2D& waveformGreen, + const array2D& waveformBlue, + const array2D& waveformLuma + ) { - histogramArea->update(histRed, histGreen, histBlue, histLuma, histChroma, histRedRaw, histGreenRaw, histBlueRaw); + histogramArea->update(histRed, histGreen, histBlue, histLuma, histChroma, histRedRaw, histGreenRaw, histBlueRaw, vectorscopeScale, vectorscopeHC, vectorscopeHS, waveformScale, waveformRed, waveformGreen, waveformBlue, waveformLuma); } // pointermotionlistener interface void pointerMoved (bool validPos, const Glib::ustring &profile, const Glib::ustring &profileW, int x, int y, int r, int g, int b, bool isRaw = false) override; @@ -250,13 +352,19 @@ public: void green_toggled (); void blue_toggled (); void value_toggled (); - void raw_toggled (); void chro_toggled (); void bar_toggled (); void mode_released (); + void brightnessWidgetValueChanged(); + void brightnessUpdated(float brightness); + void scopeOptionsToggled(); + void type_selected(Gtk::RadioButton* button); + void type_changed (); void rgbv_toggled (); void resized (Gtk::Allocation& req); // drawModeListener interface void toggleButtonMode () override; + + void setPanelListener(HistogramPanelListener* listener); }; diff --git a/rtgui/options.cc b/rtgui/options.cc index 99e1b9f08..46d9b9ca5 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -446,10 +446,12 @@ void Options::setDefaults() histogramBlue = true; histogramLuma = false; histogramChroma = false; - histogramRAW = false; histogramBar = true; histogramHeight = 200; histogramDrawMode = 0; + histogramScopeType = ScopeType::HISTOGRAM; + histogramShowOptionButtons = false; + histogramTraceBrightness = 1; curvebboxpos = 1; complexity = 2; prevdemo = PD_Sidecar; @@ -1417,7 +1419,10 @@ void Options::readFromFile(Glib::ustring fname) } if (keyFile.has_key("GUI", "HistogramRAW")) { - histogramRAW = keyFile.get_boolean("GUI", "HistogramRAW"); + // Legacy option, replaced by HistogramScopeType. + if (keyFile.get_boolean("GUI", "HistogramRAW")) { + histogramScopeType = ScopeType::HISTOGRAM_RAW; + } } if (keyFile.has_key("GUI", "HistogramBar")) { @@ -1432,6 +1437,18 @@ void Options::readFromFile(Glib::ustring fname) histogramDrawMode = keyFile.get_integer("GUI", "HistogramDrawMode"); } + if (keyFile.has_key("GUI", "HistogramScopeType")) { + histogramScopeType = static_cast(keyFile.get_integer("GUI", "HistogramScopeType")); + } + + if (keyFile.has_key("GUI", "HistogramShowOptionButtons")) { + histogramShowOptionButtons = keyFile.get_boolean("GUI", "HistogramShowOptionButtons"); + } + + if (keyFile.has_key("GUI", "HistogramTraceBrightness")) { + histogramTraceBrightness = keyFile.get_double("GUI", "HistogramTraceBrightness"); + } + if (keyFile.has_key("GUI", "NavigatorRGBUnit")) { navRGBUnit = (NavigatorUnit)keyFile.get_integer("GUI", "NavigatorRGBUnit"); } @@ -2251,10 +2268,12 @@ void Options::saveToFile(Glib::ustring fname) keyFile.set_boolean("GUI", "HistogramBlue", histogramBlue); keyFile.set_boolean("GUI", "HistogramLuma", histogramLuma); keyFile.set_boolean("GUI", "HistogramChroma", histogramChroma); - keyFile.set_boolean("GUI", "HistogramRAW", histogramRAW); keyFile.set_boolean("GUI", "HistogramBar", histogramBar); keyFile.set_integer("GUI", "HistogramHeight", histogramHeight); keyFile.set_integer("GUI", "HistogramDrawMode", histogramDrawMode); + keyFile.set_integer("GUI", "HistogramScopeType", rtengine::toUnderlying(histogramScopeType)); + keyFile.set_boolean("GUI", "HistogramShowOptionButtons", histogramShowOptionButtons); + keyFile.set_double("GUI", "HistogramTraceBrightness", histogramTraceBrightness); keyFile.set_integer("GUI", "NavigatorRGBUnit", (int)navRGBUnit); keyFile.set_integer("GUI", "NavigatorHSVUnit", (int)navHSVUnit); keyFile.set_boolean("GUI", "ShowFilmStripToolBar", showFilmStripToolBar); diff --git a/rtgui/options.h b/rtgui/options.h index 02d62292c..d2e0c0543 100644 --- a/rtgui/options.h +++ b/rtgui/options.h @@ -168,13 +168,23 @@ private: const Glib::ustring& entryName, Glib::ustring& destination); public: - enum class NavigatorUnit { PERCENT, R0_255, R0_1, _COUNT }; + + enum class ScopeType { + NONE = -1, + HISTOGRAM, + HISTOGRAM_RAW, + PARADE, + VECTORSCOPE_HC, + VECTORSCOPE_HS, + WAVEFORM + }; + bool savesParamsAtExit; SaveFormat saveFormat, saveFormatBatch; Glib::ustring savePathTemplate; @@ -308,10 +318,13 @@ public: int histogramPosition; // 0=disabled, 1=left pane, 2=right pane bool histogramRed, histogramGreen, histogramBlue; - bool histogramLuma, histogramChroma, histogramRAW; + bool histogramLuma, histogramChroma; bool histogramBar; int histogramHeight; int histogramDrawMode; + ScopeType histogramScopeType; + bool histogramShowOptionButtons; + float histogramTraceBrightness; bool FileBrowserToolbarSingleRow; bool hideTPVScrollbar; int whiteBalanceSpotSize;