diff --git a/data/darktableconfig.xml.in b/data/darktableconfig.xml.in
index 233c91d70feb..0e2f2bdbb4ff 100644
--- a/data/darktableconfig.xml.in
+++ b/data/darktableconfig.xml.in
@@ -3362,6 +3362,13 @@
+
+ plugins/darkroom/histogram/show_luma
+ bool
+ false
+
+
+
plugins/darkroom/histogram/vectorscope/harmony_type
diff --git a/data/themes/darktable.css b/data/themes/darktable.css
index f1ab12955fe6..ae832ad977e3 100644
--- a/data/themes/darktable.css
+++ b/data/themes/darktable.css
@@ -224,6 +224,7 @@ Why that? Just because it's a developer choice and most classes use underscore i
@define-color graph_red rgb(237,30,20);
@define-color graph_green rgb(28,235,26);
@define-color graph_blue rgb(14,14,233);
+@define-color graph_white @grey_95;
@define-color colorlabel_red rgb(230,0,0);
@define-color colorlabel_green rgb(0,230,0);
@@ -745,6 +746,11 @@ dialog .sidebar row:selected:hover label,
background-color: alpha(@graph_blue, 0.2);
}
+#luma-toggle-button
+{
+ background-color: alpha(white, 0.2);
+}
+
/* set now them active state */
#red-channel-button:checked
{
@@ -761,6 +767,11 @@ dialog .sidebar row:selected:hover label,
background-color: alpha(@graph_blue, 0.66);
}
+#luma-toggle-button:checked
+{
+ background-color: alpha(white, 0.66);
+}
+
/* set now hover state */
#red-channel-button:hover
{
@@ -777,6 +788,27 @@ dialog .sidebar row:selected:hover label,
background-color: alpha(@graph_blue, 0.5);
}
+#luma-toggle-button:hover
+{
+ background-color: alpha(white, 0.5);
+}
+
+/* inactive state */
+#red-channel-button:disabled
+{
+ background-color: alpha(@graph_red, 0.1);
+}
+
+#green-channel-button:disabled
+{
+ background-color: alpha(@graph_green, 0.1);
+}
+
+#blue-channel-button:disabled
+{
+ background-color: alpha(@graph_blue, 0.1);
+}
+
/*------------------
- Dialog windows -
------------------*/
diff --git a/src/bauhaus/bauhaus.c b/src/bauhaus/bauhaus.c
index 28bc786ecc6c..adb95386de7b 100644
--- a/src/bauhaus/bauhaus.c
+++ b/src/bauhaus/bauhaus.c
@@ -811,6 +811,8 @@ void dt_bauhaus_load_theme()
&bh->graph_colors[1]);
gtk_style_context_lookup_color(ctx, "graph_blue",
&bh->graph_colors[2]);
+ gtk_style_context_lookup_color(ctx, "graph_white",
+ &bh->graph_colors[3]);
gtk_style_context_lookup_color(ctx, "colorlabel_red",
&bh->colorlabels[DT_COLORLABELS_RED]);
gtk_style_context_lookup_color(ctx, "colorlabel_yellow",
diff --git a/src/bauhaus/bauhaus.h b/src/bauhaus/bauhaus.h
index e9ddd0943839..1279f08ec551 100644
--- a/src/bauhaus/bauhaus.h
+++ b/src/bauhaus/bauhaus.h
@@ -152,7 +152,7 @@ typedef struct dt_bauhaus_t
// colors for graphs
GdkRGBA graph_bg, graph_exterior, graph_border, graph_fg, graph_grid, graph_fg_active, graph_overlay, inset_histogram;
- GdkRGBA graph_colors[3]; // primaries
+ GdkRGBA graph_colors[4]; // additive primaries and luma white
GdkRGBA colorlabels[DT_COLORLABELS_LAST];
} dt_bauhaus_t;
diff --git a/src/common/histogram.c b/src/common/histogram.c
index 464b53419280..4a883c59c861 100644
--- a/src/common/histogram.c
+++ b/src/common/histogram.c
@@ -1,6 +1,6 @@
/*
This file is part of darktable,
- Copyright (C) 2014-2023 darktable developers.
+ Copyright (C) 2014-2026 darktable developers.
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -70,6 +70,28 @@ static inline void _bin_raw(const dt_dev_histogram_collection_params_t *const pa
//------------------------------------------------------------------------------
+static inline void _bin_luma(const dt_dev_histogram_collection_params_t *const params,
+ const void *const restrict pixel,
+ uint32_t *const restrict histogram,
+ const int j,
+ const dt_iop_order_iccprofile_info_t *const profile)
+{
+ const dt_histogram_roi_t *roi = params->roi;
+ float *in = (float *)pixel + 4 * (roi->width * j + roi->crop_x);
+ const float max_bin = params->bins_count - 1;
+
+ for(int i = 0; i < roi->width - roi->crop_right - roi->crop_x; i++)
+ {
+ const float luma = profile->matrix_in[1][0] * in[i*4]
+ + profile->matrix_in[1][1] * in[i*4+1]
+ + profile->matrix_in[1][2] * in[i*4+2];
+ const size_t bin = CLAMP(max_bin * luma, 0.0f, max_bin);
+ histogram[bin]++;
+ }
+}
+
+//------------------------------------------------------------------------------
+
static inline void _bin_rgb(const dt_dev_histogram_collection_params_t *const params,
const void *const restrict pixel,
uint32_t *const restrict histogram,
@@ -225,15 +247,23 @@ void dt_histogram_helper(dt_dev_histogram_collection_params_t *histogram_params,
break;
case IOP_CS_RGB:
- histogram_stats->ch = 3u;
- if(compensate_middle_grey && profile_info)
- // for rgbcurve (compensated)
+ if(histogram_stats->ch == 1u)
+ // for histogram utility module
_hist_worker(histogram_params, histogram_stats, pixel, histogram,
- _bin_rgb_compensated, profile_info);
+ _bin_luma, profile_info);
else
- // used by levels, rgbcurve (uncompensated), rgblevels
- _hist_worker(histogram_params, histogram_stats, pixel, histogram,
- _bin_rgb, profile_info);
+ {
+ histogram_stats->ch = 3u;
+ if(compensate_middle_grey && profile_info)
+ // for rgbcurve (compensated)
+ _hist_worker(histogram_params, histogram_stats, pixel, histogram,
+ _bin_rgb_compensated, profile_info);
+ else
+ // used by levels, rgbcurve (uncompensated), rgblevels, and
+ // histogram utility module
+ _hist_worker(histogram_params, histogram_stats, pixel, histogram,
+ _bin_rgb, profile_info);
+ }
break;
case IOP_CS_LAB:
@@ -257,7 +287,7 @@ void dt_histogram_helper(dt_dev_histogram_collection_params_t *histogram_params,
if(*histogram && histogram_max)
{
// RGB, Lab, and LCh
- if(cst == IOP_CS_RGB || IOP_CS_LAB)
+ if(cst == IOP_CS_RGB || cst == IOP_CS_LAB)
{
uint32_t *hist = *histogram;
@@ -271,9 +301,15 @@ void dt_histogram_helper(dt_dev_histogram_collection_params_t *histogram_params,
m[2] = hist[2];
}
- for(int k = 4; k < 4 * histogram_stats->bins_count; k += 4)
- for_each_channel(ch,aligned(hist:64) aligned(m:16))
- m[ch] = MAX(m[ch], hist[k+ch]);
+ // intentionally skip first bucket to show bit more y-axis range
+ // for underexposed images
+ if(histogram_stats->ch == 1u)
+ for(int k = 1; k < histogram_stats->bins_count; k++)
+ m[0] = MAX(m[0], hist[k]);
+ else
+ for(int k = 4; k < 4 * histogram_stats->bins_count; k += 4)
+ for_each_channel(ch,aligned(hist:64) aligned(m:16))
+ m[ch] = MAX(m[ch], hist[k+ch]);
}
else
// raw max not implemented, as is only seen in exposure
diff --git a/src/develop/pixelpipe.h b/src/develop/pixelpipe.h
index 0dbe3c3ab2ca..ac5c27ba8635 100644
--- a/src/develop/pixelpipe.h
+++ b/src/develop/pixelpipe.h
@@ -110,7 +110,7 @@ typedef struct dt_dev_histogram_stats_t
size_t buf_size;
/** count of pixels sampled during histogram capture. */
uint32_t pixels;
- /** count of channels: 1 for RAW, 3 for rgb/Lab. */
+ /** count of channels: 1 for RAW/luma, 3 for rgb/Lab. */
uint32_t ch;
} dt_dev_histogram_stats_t;
diff --git a/src/libs/histogram.c b/src/libs/histogram.c
index fed945e8c177..3d7fcf24578d 100644
--- a/src/libs/histogram.c
+++ b/src/libs/histogram.c
@@ -41,10 +41,10 @@ static void _lib_histogram_get_sector_angles(dt_lib_module_t *self,
float *angles,
int *n);
-static const gchar *rgb_names[DT_SCOPES_RGB_N] =
+static const gchar *channel_names[DT_SCOPES_CH_N] =
{ N_("red"),
N_("green"),
- N_("blue")
+ N_("blue"),
};
const char *name(dt_lib_module_t *self)
@@ -442,7 +442,7 @@ static void _mode_toggle(GtkWidget *button, dt_scopes_t *s)
lib_histogram_update_tooltip(s);
dt_scopes_call(prior_mode, mode_leave);
- gtk_widget_set_visible(s->button_box_rgb,
+ gtk_widget_set_visible(s->button_box_channels,
dt_scopes_func_exists(s->cur_mode, draw_scope_channels));
dt_scopes_call(s->cur_mode, update_buttons);
dt_scopes_call(s->cur_mode, mode_enter);
@@ -457,12 +457,12 @@ static void _mode_toggle(GtkWidget *button, dt_scopes_t *s)
static void _channel_toggle(GtkWidget *button, dt_scopes_t *s)
{
- for(int i = 0; i < DT_SCOPES_RGB_N; i++)
- if(s->channel_buttons[i] == button)
+ for(int i = 0; i < DT_SCOPES_CH_N; i++)
+ if(s->channel_btns[i] == button)
{
char conf[48];
g_snprintf(conf, sizeof(conf),
- "plugins/darkroom/histogram/show_%s", rgb_names[i]);
+ "plugins/darkroom/histogram/show_%s", channel_names[i]);
s->channels[i]
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
dt_conf_set_bool(conf, s->channels[i]);
@@ -537,7 +537,7 @@ static void _eventbox_enter_notify_callback(GtkEventControllerMotion *controller
if(s->cur_mode != &s->modes[i])
dt_scopes_call(&s->modes[i], mode_leave);
dt_scopes_call(s->cur_mode, mode_enter);
- gtk_widget_set_visible(s->button_box_rgb,
+ gtk_widget_set_visible(s->button_box_channels,
dt_scopes_func_exists(s->cur_mode, draw_scope_channels));
gtk_widget_show(s->button_box_left);
gtk_widget_show(s->button_box_right);
@@ -675,11 +675,11 @@ void gui_init(dt_lib_module_t *self)
dt_pthread_mutex_init(&s->lock, NULL);
- s->channels[DT_SCOPES_RGB_RED]
+ s->channels[DT_SCOPES_CH_RED]
= dt_conf_get_bool("plugins/darkroom/histogram/show_red");
- s->channels[DT_SCOPES_RGB_GREEN]
+ s->channels[DT_SCOPES_CH_GREEN]
= dt_conf_get_bool("plugins/darkroom/histogram/show_green");
- s->channels[DT_SCOPES_RGB_BLUE]
+ s->channels[DT_SCOPES_CH_BLUE]
= dt_conf_get_bool("plugins/darkroom/histogram/show_blue");
// proxy functions and data so that pixelpipe or tether can
@@ -804,14 +804,14 @@ void gui_init(dt_lib_module_t *self)
GDK_KEY_H, GDK_CONTROL_MASK | GDK_SHIFT_MASK);
// RGB channel buttons
- s->button_box_rgb = dt_gui_hbox();
- gtk_widget_set_valign(s->button_box_rgb, GTK_ALIGN_CENTER);
- gtk_widget_set_halign(s->button_box_rgb, GTK_ALIGN_CENTER);
- // red/green/blue channel on/off
- for(int i=DT_SCOPES_RGB_RED; i < DT_SCOPES_RGB_N; i++)
+ s->button_box_channels = dt_gui_hbox();
+ gtk_widget_set_valign(s->button_box_channels, GTK_ALIGN_CENTER);
+ gtk_widget_set_halign(s->button_box_channels, GTK_ALIGN_CENTER);
+ // red/green/blue on/off
+ for(int i=DT_SCOPES_CH_RED; i < DT_SCOPES_CH_N; i++)
{
- g_autofree char *name = g_strdup_printf("%s-channel-button", rgb_names[i]);
- g_autofree char *tip = g_strdup_printf(_("toggle %s channel"), _(rgb_names[i]));
+ g_autofree char *name = g_strdup_printf("%s-channel-button", channel_names[i]);
+ g_autofree char *tip = g_strdup_printf(_("toggle %s channel"), _(channel_names[i]));
GtkWidget *btn = dtgtk_togglebutton_new(dtgtk_cairo_paint_color,
CPF_NONE, NULL);
dt_gui_add_class(btn, "rgb_toggle");
@@ -819,13 +819,16 @@ void gui_init(dt_lib_module_t *self)
gtk_widget_set_tooltip_text(btn, tip);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(btn),
s->channels[i]);
- dt_action_define(dark, N_("toggle colors"), rgb_names[i], btn, &dt_action_def_toggle);
- dt_gui_box_add(s->button_box_rgb, btn);
+ dt_action_define(dark, N_("toggle colors"), channel_names[i], btn, &dt_action_def_toggle);
g_signal_connect(G_OBJECT(btn), "toggled", G_CALLBACK(_channel_toggle), s);
- s->channel_buttons[i] = btn;
+ s->channel_btns[i] = btn;
}
- // RGB channels are always rightmost
- dt_gui_box_add(s->button_box_right, s->button_box_rgb);
+ dt_gui_box_add(s->button_box_channels,
+ s->channel_btns[DT_SCOPES_CH_RED],
+ s->channel_btns[DT_SCOPES_CH_GREEN],
+ s->channel_btns[DT_SCOPES_CH_BLUE]);
+ // channels are always rightmost
+ dt_gui_box_add(s->button_box_right, s->button_box_channels);
for(dt_scopes_mode_type_t i = 0; i < DT_SCOPES_MODE_N; i++)
{
diff --git a/src/libs/scopes.h b/src/libs/scopes.h
index 3c83edd56687..ebab476c5f6f 100644
--- a/src/libs/scopes.h
+++ b/src/libs/scopes.h
@@ -43,15 +43,15 @@ typedef enum dt_scopes_highlight_t
DT_SCOPES_HIGHLIGHT_EXPOSURE
} dt_scopes_highlight_t;
-typedef enum dt_scopes_rgb_t
+typedef enum dt_scopes_channels_t
{
- DT_SCOPES_RGB_RED = 0,
- DT_SCOPES_RGB_GREEN,
- DT_SCOPES_RGB_BLUE,
- DT_SCOPES_RGB_N // needs to be the last one
-} dt_scopes_rgb_t;
+ DT_SCOPES_CH_RED = 0,
+ DT_SCOPES_CH_GREEN,
+ DT_SCOPES_CH_BLUE,
+ DT_SCOPES_CH_N // needs to be the last one
+} dt_scopes_channels_t;
-typedef gboolean scopes_channels_t[DT_SCOPES_RGB_N];
+typedef gboolean dt_scopes_channels_list_t[DT_SCOPES_CH_N];
struct dt_scopes_t;
struct dt_scopes_mode_t;
@@ -66,7 +66,7 @@ typedef struct dt_scopes_functions_t
const float *const input,
// FIXME: should ROI by dt_histogram_roi_t or another type?
dt_histogram_roi_t *const roi,
- const dt_iop_order_iccprofile_info_t *vs_prof);
+ const dt_iop_order_iccprofile_info_t *profile);
// FIXME: do want a proper clear function or just tag as not up to date?
void (*clear)(struct dt_scopes_mode_t *const self);
void (*draw_bkgd)(const struct dt_scopes_mode_t *const self,
@@ -90,7 +90,7 @@ typedef struct dt_scopes_functions_t
cairo_t *cr,
const int width,
const int height,
- const scopes_channels_t channels);
+ const dt_scopes_channels_list_t channels);
// FIXME: rename to something more sensible
dt_scopes_highlight_t (*get_highlight)(const struct dt_scopes_mode_t *const self,
const double posx,
@@ -140,7 +140,7 @@ typedef struct dt_scopes_t
dt_scopes_mode_t modes[DT_SCOPES_MODE_N]; // all available modes
int update_counter; // most recent pixelpipe vs mode data
dt_scopes_highlight_t highlight; // depends on mouse position
- scopes_channels_t channels; // display state chosen by RGB buttons
+ dt_scopes_channels_list_t channels; // RGB channels to display
gboolean dragging; // to block motion handling during drag
gdouble last_offset_x, last_offset_y; // for drag handling
// UI elements
@@ -148,8 +148,8 @@ typedef struct dt_scopes_t
GtkWidget *button_box_left; // GtkBox -- scope mode buttons
GtkWidget *button_box_split; // GtkBox -- option buttons for left scope
GtkWidget *button_box_right; // GtkBox -- option buttons for main scope
- GtkWidget *button_box_rgb; // GtkBox -- RGB channels buttons
- GtkWidget *channel_buttons[DT_SCOPES_RGB_N]; // Array of GtkToggleButton -- RGB channels
+ GtkWidget *button_box_channels; // GtkBox -- RGB channel buttons
+ GtkWidget *channel_btns[DT_SCOPES_CH_N]; // Array of GtkToggleButton -- channels
GtkWidget *scope_draw; // GtkDrawingArea -- scope & resize
// for access to data during process/draw
dt_pthread_mutex_t lock;
diff --git a/src/libs/scopes/histogram.c b/src/libs/scopes/histogram.c
index 3aa32a60a212..1f119b5dc4e8 100644
--- a/src/libs/scopes/histogram.c
+++ b/src/libs/scopes/histogram.c
@@ -38,10 +38,15 @@ static const gchar *dt_hist_scale_names[DT_HIST_SCALE_N] =
typedef struct dt_scopes_hist_t
{
+ // buffers/data
uint32_t *histogram;
uint32_t histogram_max;
- dt_hist_scale_t scale;
+ // buttons
GtkWidget *scale_button; // GtkButton -- linear or logarithmic histogram
+ GtkWidget *luma_button; // GtkButton -- luma/rgb toggle
+ // state set by buttons
+ dt_hist_scale_t scale;
+ gboolean is_rgb;
} dt_scopes_hist_t;
@@ -53,20 +58,21 @@ const char* _hist_name(const dt_scopes_mode_t *const self)
static void _hist_process(dt_scopes_mode_t *const self,
const float *const input,
dt_histogram_roi_t *const roi,
- const dt_iop_order_iccprofile_info_t *vs_prof)
+ const dt_iop_order_iccprofile_info_t *profile)
{
dt_scopes_hist_t *const d = self->data;
+ const uint32_t channels = d->is_rgb ? 4u : 1u;
dt_dev_histogram_collection_params_t histogram_params = { 0 };
const dt_iop_colorspace_type_t cst = IOP_CS_RGB;
dt_dev_histogram_stats_t histogram_stats =
{ .bins_count = HISTOGRAM_BINS,
- .ch = 4,
+ .ch = channels,
.pixels = 0,
- .buf_size = sizeof(uint32_t) * 4 * HISTOGRAM_BINS };
+ .buf_size = sizeof(uint32_t) * channels * HISTOGRAM_BINS };
uint32_t histogram_max[4] = { 0 };
d->histogram_max = 0;
- memset(d->histogram, 0, sizeof(uint32_t) * 4 * HISTOGRAM_BINS);
+ memset(d->histogram, 0, sizeof(uint32_t) * channels * HISTOGRAM_BINS);
histogram_params.roi = roi;
histogram_params.bins_count = HISTOGRAM_BINS;
@@ -77,8 +83,11 @@ static void _hist_process(dt_scopes_mode_t *const self,
// FIXME: set up "custom" histogram worker which can do colorspace
// conversion on fly -- in cases that we need to do that -- may need
// to add from colorspace to dt_dev_histogram_collection_params_t
- dt_histogram_helper(&histogram_params, &histogram_stats, cst, IOP_CS_NONE,
- input, &d->histogram, histogram_max, FALSE, NULL);
+ dt_histogram_helper(&histogram_params, &histogram_stats,
+ cst, IOP_CS_NONE,
+ input,
+ &d->histogram, histogram_max,
+ FALSE, profile);
d->histogram_max = MAX(MAX(histogram_max[0], histogram_max[1]), histogram_max[2]);
self->update_counter = self->scopes->update_counter;
}
@@ -126,7 +135,7 @@ static void _hist_draw(const dt_scopes_mode_t *const self,
cairo_t *cr,
const int width,
const int height,
- const scopes_channels_t channels)
+ const dt_scopes_channels_list_t channels)
{
const dt_scopes_hist_t *const d = self->data;
@@ -146,16 +155,22 @@ static void _hist_draw(const dt_scopes_mode_t *const self,
cairo_scale(cr, width / 255.0, -(height - 10) / hist_max);
cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
- for(int k = 0; k < DT_SCOPES_RGB_N; k++)
- if(channels[k])
- {
- // FIXME: this is the last place in dt these are used -- if can
- // eliminate, then can directly set button colors in CSS and
- // simplify things
- set_color(cr, darktable.bauhaus->graph_colors[k]);
- dt_draw_histogram_8(cr, d->histogram, 4, k,
- d->scale == DT_HIST_SCALE_LINEAR);
- }
+ if(d->is_rgb)
+ {
+ for(int k = 0; k < DT_SCOPES_CH_N; k++)
+ if(channels[k])
+ {
+ set_color(cr, darktable.bauhaus->graph_colors[k]);
+ dt_draw_histogram_8(cr, d->histogram, 4, k,
+ d->scale == DT_HIST_SCALE_LINEAR);
+ }
+ }
+ else
+ {
+ set_color(cr, darktable.bauhaus->graph_colors[3]);
+ dt_draw_histogram_8(cr, d->histogram, 1, 0,
+ d->scale == DT_HIST_SCALE_LINEAR);
+ }
cairo_pop_group_to_source(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
cairo_paint_with_alpha(cr, 0.5);
@@ -172,6 +187,7 @@ static void _hist_mode_enter(dt_scopes_mode_t *const self)
{
dt_scopes_hist_t *d = self->data;
gtk_widget_show(d->scale_button);
+ gtk_widget_show(d->luma_button);
// FIXME: can call _hist_scale_update() here instead of in gui_init_options?
}
@@ -179,6 +195,7 @@ static void _hist_mode_leave(const dt_scopes_mode_t *const self)
{
dt_scopes_hist_t *d = self->data;
gtk_widget_hide(d->scale_button);
+ gtk_widget_hide(d->luma_button);
}
static void _hist_gui_init(dt_scopes_mode_t *const self,
@@ -201,6 +218,7 @@ static void _hist_gui_init(dt_scopes_mode_t *const self,
static void _hist_update_buttons(const dt_scopes_mode_t *const self)
{
dt_scopes_hist_t *d = self->data;
+
switch(d->scale)
{
case DT_HIST_SCALE_LOGARITHMIC:
@@ -216,6 +234,10 @@ static void _hist_update_buttons(const dt_scopes_mode_t *const self)
case DT_HIST_SCALE_N:
dt_unreachable_codepath();
}
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->luma_button), !d->is_rgb);
+ gtk_widget_set_sensitive(self->scopes->button_box_channels, d->is_rgb);
+
// FIXME: this should really redraw current iop if its background is
// a histogram (check request_histogram)
darktable.lib->proxy.histogram.is_linear =
@@ -235,17 +257,39 @@ static void _hist_scale_clicked(GtkWidget *button, dt_scopes_mode_t *self)
dt_scopes_refresh(self->scopes);
}
+static void _hist_luma_toggle(GtkWidget *button, dt_scopes_mode_t *self)
+{
+ dt_scopes_hist_t *d = self->data;
+ d->is_rgb = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
+ dt_conf_set_bool("plugins/darkroom/histogram/show_luma", !d->is_rgb);
+ d->histogram_max = 0;
+ _hist_update_buttons(self);
+ dt_scopes_reprocess();
+}
+
static void _hist_add_options(dt_scopes_mode_t *const self,
dt_action_t *dark)
{
dt_scopes_hist_t *d = self->data;
+
d->scale_button = dtgtk_button_new(dtgtk_cairo_paint_empty, CPF_NONE, NULL);
gtk_widget_set_valign(d->scale_button, GTK_ALIGN_START);
dt_action_define(dark, NULL, N_("switch histogram scale"),
d->scale_button, &dt_action_def_button);
- dt_gui_box_add(self->options_box, d->scale_button);
g_signal_connect(G_OBJECT(d->scale_button), "clicked",
G_CALLBACK(_hist_scale_clicked), self);
+
+ d->luma_button = dtgtk_togglebutton_new(dtgtk_cairo_paint_color,
+ CPF_NONE, NULL);
+ gtk_widget_set_valign(d->luma_button, GTK_ALIGN_START);
+ // FIXME: s/rgb_toggle/color_toggle/?
+ dt_gui_add_class(d->luma_button, "rgb_toggle");
+ gtk_widget_set_name(d->luma_button, "luma-toggle-button");
+ gtk_widget_set_tooltip_text(d->luma_button, "toggle luma/rgb");
+ dt_action_define(dark, N_("toggle colors"), N_("histogram luma/rgb"), d->luma_button, &dt_action_def_toggle);
+ g_signal_connect(G_OBJECT(d->luma_button), "toggled", G_CALLBACK(_hist_luma_toggle), self);
+
+ dt_gui_box_add(self->options_box, d->scale_button, d->luma_button);
}
static void _hist_gui_cleanup(dt_scopes_mode_t *const self)
diff --git a/src/libs/scopes/split.c b/src/libs/scopes/split.c
index 08b86dfade27..b53f56c30b6f 100644
--- a/src/libs/scopes/split.c
+++ b/src/libs/scopes/split.c
@@ -33,11 +33,11 @@ const char* _split_name(const dt_scopes_mode_t *const self)
static void _split_process(dt_scopes_mode_t *const self,
const float *const input,
dt_histogram_roi_t *const roi,
- const dt_iop_order_iccprofile_info_t *vs_prof)
+ const dt_iop_order_iccprofile_info_t *profile)
{
dt_scopes_split_t *const d = self->data;
- dt_scopes_call(d->left, process, input, roi, vs_prof);
- dt_scopes_call(d->right, process, input, roi, vs_prof);
+ dt_scopes_call(d->left, process, input, roi, profile);
+ dt_scopes_call(d->right, process, input, roi, profile);
self->update_counter = self->scopes->update_counter;
}
@@ -90,7 +90,7 @@ static void _split_draw(const dt_scopes_mode_t *const self,
cairo_t *cr,
const int width,
const int height,
- const scopes_channels_t channels)
+ const dt_scopes_channels_list_t channels)
{
const dt_scopes_split_t *const d = self->data;
const int half_width = width / 2;
@@ -224,8 +224,8 @@ static void _responsive_buttons(dt_scopes_t *const s)
if(min_w == 0.0) return;
const int mode_btns_hori = DT_SCOPES_MODE_N;
- const int opt_btns_wave = 1; // FIXME: change this if there are more
- const int opt_btns_hori = DT_SCOPES_RGB_N + opt_btns_wave;
+ const int opt_btns_wave = 2; // FIXME: change this if there are more
+ const int opt_btns_hori = DT_SCOPES_CH_N + opt_btns_wave;
const int opt_btns_vert = 1 + opt_btns_wave;
const int estd_margin = 6; // for both boxes and buttons
const double estd_btn_width = min_w + estd_margin;
@@ -247,7 +247,7 @@ static void _responsive_buttons(dt_scopes_t *const s)
// compact layout should show all buttons without overlap on the
// smallest panel width and scope height
gtk_orientable_set_orientation(GTK_ORIENTABLE(s->button_box_left), orient_btn_left);
- gtk_orientable_set_orientation(GTK_ORIENTABLE(s->button_box_rgb), orient_btn_rgb);
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(s->button_box_channels), orient_btn_rgb);
}
static void _reparent(GtkWidget *src, GtkWidget *dest, GtkWidget *child)
@@ -279,7 +279,7 @@ static void _split_mode_enter(dt_scopes_mode_t *const self)
d->left->options_box);
if(d->left->functions->draw_scope_channels)
_reparent(self->scopes->button_box_right, self->scopes->button_box_split,
- self->scopes->button_box_rgb);
+ self->scopes->button_box_channels);
gtk_widget_show_all(self->scopes->button_box_split);
}
@@ -293,11 +293,11 @@ static void _split_mode_leave(const dt_scopes_mode_t *const self)
d->left->options_box);
if(d->left->functions->draw_scope_channels)
_reparent(self->scopes->button_box_split, self->scopes->button_box_right,
- self->scopes->button_box_rgb);
+ self->scopes->button_box_channels);
gtk_widget_hide(self->scopes->button_box_split);
gtk_orientable_set_orientation(GTK_ORIENTABLE(self->scopes->button_box_left),
GTK_ORIENTATION_HORIZONTAL);
- gtk_orientable_set_orientation(GTK_ORIENTABLE(self->scopes->button_box_rgb),
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(self->scopes->button_box_channels),
GTK_ORIENTATION_HORIZONTAL);
}
diff --git a/src/libs/scopes/waveform.c b/src/libs/scopes/waveform.c
index 7091365838c4..7f2b75fcd271 100644
--- a/src/libs/scopes/waveform.c
+++ b/src/libs/scopes/waveform.c
@@ -42,11 +42,17 @@ static const gchar *dt_wave_orient_names[DT_WAVE_ORIENT_N] =
typedef struct dt_scopes_wave_t
{
- uint8_t *waveform_img[3]; // image per RGB channel
- int waveform_bins, waveform_tones, waveform_max_bins;
- GtkWidget *orient_button; // GtkButton -- horizontal or vertical
+ // buffers/data
+ uint8_t *waveform_img[3]; // processed data per channel
+ int waveform_max_bins; // for spatial sampling of image
+ int waveform_bins; // spatial sampling of currently processed data
+ int waveform_tones; // # tones/bin
+ // buttons
+ GtkWidget *orient_button; // GtkButton -- horizontal or vertical
+ GtkWidget *luma_button; // GtkButton -- luma/rgb toggle
// state set by buttons
dt_wave_orient_t orient;
+ gboolean is_rgb; // is waveform buffer data RGB or luma
} dt_scopes_wave_t;
@@ -58,7 +64,7 @@ const char* _wave_name(const dt_scopes_mode_t *const self)
static void _wave_process(dt_scopes_mode_t *const self,
const float *const input,
dt_histogram_roi_t *const roi,
- const dt_iop_order_iccprofile_info_t *vs_prof)
+ const dt_iop_order_iccprofile_info_t *img_prof)
{
dt_scopes_wave_t *const d = self->data;
// FIXME: for point sample, calculate whole graph and the point
@@ -72,6 +78,8 @@ static void _wave_process(dt_scopes_mode_t *const self,
// orientation, bin_width will generally be 4, for "portrait" it
// will generally be 3. Note that waveform_bins varies, depending on
// preview image width and # of bins.
+ // FIXME: check these #s, waveform_max_bins, waveform_tones, and #s
+ // below now that mipf is mip3 resolution and CPUs are a bit faster
const dt_wave_orient_t orient = d->orient;
const int to_bin = orient == DT_WAVE_ORIENT_HORI ? sample_width : sample_height;
const size_t samples_per_bin = ceilf(to_bin / (float)d->waveform_max_bins);
@@ -81,11 +89,15 @@ static void _wave_process(dt_scopes_mode_t *const self,
// Note that, with current constants, the input buffer is from the
// preview pixelpipe and should be <= 1440x900x4. The output buffer
- // will be <= 360x160x3. Hence process works with a relatively small
+ // will be <= 720x160x4. Hence process works with a relatively small
// quantity of data.
size_t bin_pad;
+ const gboolean is_rgb = d->is_rgb
+ || self == &self->scopes->modes[DT_SCOPES_MODE_PARADE];
+ const size_t num_channels = (is_rgb ? 3U : 1U);
uint32_t *const restrict partial_binned =
- dt_calloc_perthread(3U * num_bins * num_tones, sizeof(uint32_t), &bin_pad);
+ dt_calloc_perthread(num_channels * num_bins * num_tones,
+ sizeof(uint32_t), &bin_pad);
DT_OMP_FOR()
for(size_t y=0; ycrop_x) + ch];
- // Using ceilf brings everything <= 0 to bottom tone,
- // everything > 1.0f/(num_tones-1) to top tone.
- tone[ch] = ceilf(CLAMPS(v, 0.0f, 1.0f) * (num_tones-1));
+ if(is_rgb)
+ { // RGB waveform
+ size_t tone[4] DT_ALIGNED_PIXEL;
+ for_each_channel(ch, aligned(px,tone:16))
+ {
+ // 1.0 is at 8/9 of the height!
+ const float v = (8.0f / 9.0f) * px[4U * (x + roi->crop_x) + ch];
+ // Using ceilf brings everything <= 0 to bottom tone,
+ // everything > 1.0f/(num_tones-1) to top tone.
+ tone[ch] = ceilf(CLAMPS(v, 0.0f, 1.0f) * (num_tones-1));
+ }
+ // must be for_three_ch as binned[3] does not exist
+ for_three_channels(ch, aligned(tone,binned:16))
+ binned[num_tones * (ch * num_bins + bin) + tone[ch]]++;
+ }
+ else
+ { // luma/luminance waveform
+ dt_aligned_pixel_t RGB;
+ for_each_channel(ch, aligned(px,RGB:16))
+ RGB[ch] = px[4U * (x + roi->crop_x) + ch];
+ // luma (Y') from linearized data by pretending this is a
+ // linear colorspace
+ const int nonlinearlut_fake = FALSE;
+ // FIXME: also have option for showing a linear waveform, then
+ // calculate relative luminance (Y) from linearized data
+ const float luma =
+ dt_ioppr_get_rgb_matrix_luminance(RGB,
+ img_prof->matrix_in,
+ img_prof->lut_in,
+ img_prof->unbounded_coeffs_in,
+ img_prof->lutsize,
+ nonlinearlut_fake);
+ const float v = (8.0f / 9.0f) * luma;
+ const size_t tone = ceilf(CLAMPS(v, 0.0f, 1.0f) * (num_tones-1));
+ binned[num_tones * bin + tone]++;
}
- for(size_t ch = 0; ch < 3; ch++)
- binned[num_tones * (ch * num_bins + bin) + tone[ch]]++;
}
}
@@ -127,7 +164,7 @@ static void _wave_process(dt_scopes_mode_t *const self,
// NOTE: if constant is decreased, will brighten output
// FIXME: instead of using an area-beased scale, figure out max bin
- // count and scale to that?
+ // count and scale to that?
const float brightness = num_tones / 40.0f;
const float scale = brightness / ((orient == DT_WAVE_ORIENT_HORI
@@ -136,13 +173,14 @@ static void _wave_process(dt_scopes_mode_t *const self,
const size_t nthreads = dt_get_num_threads();
DT_OMP_FOR(collapse(3))
- for(size_t ch = 0; ch < 3; ch++)
+ for(size_t ch = 0; ch < num_channels; ch++)
for(size_t bin = 0; bin < num_bins; bin++)
for(size_t tone = 0; tone < num_tones; tone++)
{
uint8_t *const restrict wf_img =
DT_IS_ALIGNED((uint8_t *const restrict)d->waveform_img[ch]);
uint32_t acc = 0;
+ // FIXME: can use openmp accumlate method?
for(size_t n = 0; n < nthreads; n++)
{
uint32_t *const restrict binned = dt_get_bythread(partial_binned, bin_pad, n);
@@ -158,10 +196,14 @@ static void _wave_process(dt_scopes_mode_t *const self,
dt_free_align(partial_binned);
- // waveform and rgb parade share underlying data, so updates to one update both
- self->scopes->modes[DT_SCOPES_MODE_WAVEFORM].update_counter =
- self->scopes->modes[DT_SCOPES_MODE_PARADE].update_counter =
- self->scopes->update_counter;
+ // waveform and rgb parade share underlying RGB (but not luminosity)
+ // data, so some updates will update both
+ if(is_rgb)
+ self->scopes->modes[DT_SCOPES_MODE_WAVEFORM].update_counter =
+ self->scopes->modes[DT_SCOPES_MODE_PARADE].update_counter =
+ self->scopes->update_counter;
+ else
+ self->update_counter = self->scopes->update_counter;
}
static void _wave_draw_grid(const dt_scopes_mode_t *const self,
@@ -208,7 +250,7 @@ static void _wave_draw(const dt_scopes_mode_t *const self,
cairo_t *cr,
const int width,
const int height,
- const scopes_channels_t channels)
+ const dt_scopes_channels_list_t channels)
{
const dt_scopes_wave_t *const d = self->data;
@@ -217,37 +259,58 @@ static void _wave_draw(const dt_scopes_mode_t *const self,
// composite before scaling to screen dimensions, as scaling each
// layer on draw causes a >2x slowdown
- const double alpha_chroma = 0.75, desat_over = 0.75, alpha_over = 0.35;
+ const double alpha_chroma = 0.75;
+ const double desat_over = 0.75;
+ const double alpha_over = d->is_rgb ? 0.35 : 0.65;
const int img_width = d->orient == DT_WAVE_ORIENT_HORI
- ? d->waveform_bins : d->waveform_tones;
+ ? d->waveform_bins : d->waveform_tones;
const int img_height = d->orient == DT_WAVE_ORIENT_HORI
- ? d->waveform_tones : d->waveform_bins;
+ ? d->waveform_tones : d->waveform_bins;
const size_t img_stride = cairo_format_stride_for_width(CAIRO_FORMAT_A8, img_width);
- cairo_surface_t *cs[3] = { NULL, NULL, NULL };
- cairo_surface_t *cst = cairo_image_surface_create
- (CAIRO_FORMAT_ARGB32, img_width, img_height);
+ cairo_surface_t *cst =
+ cairo_image_surface_create(CAIRO_FORMAT_ARGB32, img_width, img_height);
cairo_t *crt = cairo_create(cst);
cairo_set_operator(crt, CAIRO_OPERATOR_ADD);
- for(int ch = 0; ch < 3; ch++)
- if(channels[ch])
- {
- cs[ch] = cairo_image_surface_create_for_data(d->waveform_img[ch], CAIRO_FORMAT_A8,
- img_width, img_height, img_stride);
- cairo_set_source_rgba(crt, ch==0 ? 1.:0., ch==1 ? 1.:0., ch==2 ? 1.:0., alpha_chroma);
- cairo_mask_surface(crt, cs[ch], 0., 0.);
- }
- cairo_set_operator(crt, CAIRO_OPERATOR_HARD_LIGHT);
- for(int ch = 0; ch < 3; ch++)
- if(cs[ch])
- {
- cairo_set_source_rgba(crt,
- ch==0 ? 1.:desat_over,
- ch==1 ? 1.:desat_over,
- ch==2 ? 1.:desat_over, alpha_over);
- cairo_mask_surface(crt, cs[ch], 0., 0.);
- cairo_surface_destroy(cs[ch]);
- }
+ if(d->is_rgb)
+ {
+ cairo_surface_t *cs[3] = { NULL, NULL, NULL };
+ for(int ch = 0; ch < DT_SCOPES_CH_N; ch++)
+ if(channels[ch])
+ {
+ cs[ch] =
+ cairo_image_surface_create_for_data(d->waveform_img[ch], CAIRO_FORMAT_A8,
+ img_width, img_height, img_stride);
+ cairo_set_source_rgba(crt,
+ ch==0 ? 1.:0.,
+ ch==1 ? 1.:0.,
+ ch==2 ? 1.:0., alpha_chroma);
+ cairo_mask_surface(crt, cs[ch], 0., 0.);
+ }
+ cairo_set_operator(crt, CAIRO_OPERATOR_HARD_LIGHT);
+ for(int ch = 0; ch < DT_SCOPES_CH_N; ch++)
+ if(cs[ch])
+ {
+ cairo_set_source_rgba(crt,
+ ch==0 ? 1.:desat_over,
+ ch==1 ? 1.:desat_over,
+ ch==2 ? 1.:desat_over, alpha_over);
+ cairo_mask_surface(crt, cs[ch], 0., 0.);
+ cairo_surface_destroy(cs[ch]);
+ }
+ }
+ else
+ {
+ cairo_surface_t *cs =
+ cairo_image_surface_create_for_data(d->waveform_img[0], CAIRO_FORMAT_A8,
+ img_width, img_height, img_stride);
+ cairo_set_source_rgba(crt, 1., 1., 1., alpha_chroma);
+ cairo_mask_surface(crt, cs, 0., 0.);
+ cairo_set_operator(crt, CAIRO_OPERATOR_HARD_LIGHT);
+ cairo_set_source_rgba(crt, 1., 1., 1., alpha_over);
+ cairo_mask_surface(crt, cs, 0., 0.);
+ cairo_surface_destroy(cs);
+ }
cairo_destroy(crt);
// scale and write to output buffer
@@ -302,6 +365,7 @@ static void _wave_clear(dt_scopes_mode_t *const self)
static void _wave_update_buttons(const dt_scopes_mode_t *const self)
{
dt_scopes_wave_t *d = self->data;
+
switch(d->orient)
{
case DT_WAVE_ORIENT_HORI:
@@ -317,18 +381,32 @@ static void _wave_update_buttons(const dt_scopes_mode_t *const self)
case DT_WAVE_ORIENT_N:
dt_unreachable_codepath();
}
+
+ if(self == &self->scopes->modes[DT_SCOPES_MODE_WAVEFORM])
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->luma_button), !d->is_rgb);
+ gtk_widget_set_sensitive(self->scopes->button_box_channels, d->is_rgb);
+ }
}
static void _wave_mode_enter(dt_scopes_mode_t *const self)
{
- const dt_scopes_wave_t *const d = self->data;
+ dt_scopes_wave_t *const d = self->data;
gtk_widget_show(d->orient_button);
+ gtk_widget_show(d->luma_button);
+ // force reprocess if have valid RGB parade data but are switching
+ // to a luma waveform
+ const gboolean cur_buffer_is_rgb = d->is_rgb;
+ d->is_rgb = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->luma_button));
+ if(cur_buffer_is_rgb && !d->is_rgb)
+ self->update_counter--;
}
static void _wave_mode_leave(const dt_scopes_mode_t *const self)
{
const dt_scopes_wave_t *const d = self->data;
gtk_widget_hide(d->orient_button);
+ gtk_widget_hide(d->luma_button);
}
static void _wave_gui_init(dt_scopes_mode_t *const self,
@@ -341,13 +419,14 @@ static void _wave_gui_init(dt_scopes_mode_t *const self,
for(dt_wave_orient_t i=0; iorient = i;
+ d->is_rgb = !dt_conf_get_bool("plugins/darkroom/waveform/show_luma");
// Waveform buffer doesn't need to be coupled with the scopes
// widget size. The waveform is almost always scaled when
// drawn. Choose buffer dimensions which produces workable detail,
// don't use too much CPU/memory, and allow reasonable gradations
// of tone.
-
+ //
// Don't use absurd amounts of memory, exceed width of DT_MIPMAP_F
// (which will be darktable.mipmap_cache->max_width[DT_MIPMAP_F]*2
// for mosaiced images), nor make it too slow to calculate
@@ -356,7 +435,7 @@ static void _wave_gui_init(dt_scopes_mode_t *const self,
// width will vary with integral binning of image.
//
// FIXME: increasing waveform_max_bins increases processing speed
- // less than increasing waveform_tones -- tune these better?
+ // less than increasing waveform_tones -- tune these better?
d->waveform_max_bins = darktable.mipmap_cache->max_width[DT_MIPMAP_F]/2;
// initially no waveform to draw
d->waveform_bins = 0;
@@ -369,18 +448,15 @@ static void _wave_gui_init(dt_scopes_mode_t *const self,
// of tonal gradation. 256 would match the # of bins in a regular
// histogram.
d->waveform_tones = 160;
- // FIXME: combine waveform_8bit and vectorscope_graph, as only ever
- // use one or the other
- //
// FIXME: keep alignment instead via single alloc via
- // dt_alloc_perthread()?
+ // dt_alloc_perthread()?
const size_t bytes_hori =
d->waveform_tones
* cairo_format_stride_for_width(CAIRO_FORMAT_A8, d->waveform_max_bins);
const size_t bytes_vert =
d->waveform_max_bins
* cairo_format_stride_for_width(CAIRO_FORMAT_A8, d->waveform_tones);
- for(int ch=0; ch<3; ch++)
+ for(int ch=0; ch <= DT_SCOPES_CH_BLUE; ch++)
d->waveform_img[ch] = dt_alloc_align_uint8(MAX(bytes_hori, bytes_vert));
}
@@ -395,17 +471,38 @@ static void _wave_orient_clicked(GtkWidget *button, dt_scopes_mode_t *const self
dt_scopes_reprocess();
}
+static void _wave_luma_toggle(GtkWidget *button, dt_scopes_mode_t *self)
+{
+ dt_scopes_wave_t *d = self->data;
+ d->is_rgb = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
+ dt_conf_set_bool("plugins/darkroom/waveform/show_luma", !d->is_rgb);
+ d->waveform_bins = 0;
+ _wave_update_buttons(self);
+ dt_scopes_reprocess();
+}
+
static void _wave_add_options(dt_scopes_mode_t *const self,
dt_action_t *dark)
{
dt_scopes_wave_t *const d = self->data;
+
d->orient_button = dtgtk_button_new(dtgtk_cairo_paint_empty, CPF_NONE, NULL);
gtk_widget_set_valign(d->orient_button, GTK_ALIGN_START);
dt_action_define(dark, NULL, N_("switch scope orientation"),
d->orient_button, &dt_action_def_button);
- dt_gui_box_add(self->options_box, d->orient_button);
g_signal_connect(G_OBJECT(d->orient_button), "clicked",
G_CALLBACK(_wave_orient_clicked), self);
+
+ d->luma_button = dtgtk_togglebutton_new(dtgtk_cairo_paint_color,
+ CPF_NONE, NULL);
+ gtk_widget_set_valign(d->luma_button, GTK_ALIGN_START);
+ dt_gui_add_class(d->luma_button, "rgb_toggle");
+ gtk_widget_set_name(d->luma_button, "luma-toggle-button");
+ gtk_widget_set_tooltip_text(d->luma_button, "toggle luma/rgb");
+ dt_action_define(dark, N_("toggle colors"), N_("waveform luma/rgb"), d->luma_button, &dt_action_def_toggle);
+ g_signal_connect(G_OBJECT(d->luma_button), "toggled", G_CALLBACK(_wave_luma_toggle), self);
+
+ dt_gui_box_add(self->options_box, d->orient_button, d->luma_button);
}
static void _wave_gui_cleanup(dt_scopes_mode_t *const self)
@@ -514,6 +611,18 @@ static void _parade_draw(const dt_scopes_mode_t *const self,
cairo_restore(cr);
}
+static void _parade_mode_enter(dt_scopes_mode_t *const self)
+{
+ const dt_scopes_wave_t *const d = self->data;
+ gtk_widget_show(d->orient_button);
+}
+
+static void _parade_mode_leave(const dt_scopes_mode_t *const self)
+{
+ const dt_scopes_wave_t *const d = self->data;
+ gtk_widget_hide(d->orient_button);
+}
+
static void _parade_gui_init(dt_scopes_mode_t *const self,
dt_scopes_t *const scopes)
{
@@ -545,8 +654,8 @@ const dt_scopes_functions_t dt_scopes_functions_parade = {
.append_to_tooltip = NULL,
.eventbox_scroll = NULL,
.update_buttons = _wave_update_buttons,
- .mode_enter = _wave_mode_enter,
- .mode_leave = _wave_mode_leave,
+ .mode_enter = _parade_mode_enter,
+ .mode_leave = _parade_mode_leave,
.gui_init = _parade_gui_init,
.add_options = NULL,
.gui_cleanup = _parade_gui_cleanup