79#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(5)
80#define DT_GUI_CURVE_INFL .3f
139 return _(
"levels (legacy)");
169 _(
"linear or non-linear, Lab, display-referred"),
170 _(
"non-linear, Lab"),
171 _(
"non-linear, Lab, display-referred"));
175 void *new_params,
const int new_version)
177 if(old_version == 1 && new_version == 2)
179 typedef struct dt_iop_levels_params_v1_t
183 } dt_iop_levels_params_v1_t;
185 dt_iop_levels_params_v1_t *o = (dt_iop_levels_params_v1_t *)old_params;
191 n->levels[0] = o->levels[0];
192 n->levels[1] = o->levels[1];
193 n->levels[2] = o->levels[2];
204 for(
int k = 0;
k <= 4 * 255;
k += 4)
208 levels[0] = ((float)(
k) / (4 * 256));
213 for(
int k = 4 * 255;
k >= 0;
k -= 4)
217 levels[2] = ((float)(
k) / (4 * 256));
230 uint32_t *histogram = NULL;
236 const size_t histogram_size = 4 * histogram_stats.
bins_count *
sizeof(uint32_t);
242 uint32_t total = histogram_stats.
pixels;
245 for(
int k = 0;
k < 3;
k++)
247 thr[
k] = (float)total *
d->percentiles[
k] / 100.0f;
257 n += histogram[4 *
i];
259 for(
int k = 0;
k < 3;
k++)
261 if(isnan(
d->levels[
k]) && (
n >= thr[
k]))
263 d->levels[
k] = (float)
i / (
float)(histogram_stats.
bins_count - 1);
272 if(isnan(
d->levels[2]))
d->levels[2] = 1.0f;
275 float center =
d->percentiles[1] / 100.0f;
276 if(!isnan(
d->levels[0]) && !isnan(
d->levels[2]))
277 d->levels[1] = (1.0f - center) *
d->levels[0] + center *
d->levels[2];
286 float delta = (
d->levels[2] -
d->levels[0]) / 2.0f;
287 float mid =
d->levels[0] +
delta;
288 float tmp = (
d->levels[1] - mid) /
delta;
289 d->in_inv_gamma = pow(10, tmp);
291 for(
unsigned int i = 0;
i < 0x10000;
i++)
293 float percentage = (float)
i / (
float)0x10000ul;
294 d->lut[
i] = 100.0f * powf(percentage,
d->in_inv_gamma);
311 if(mean_picked_color != c->last_picked_color)
314 previous_color[0] =
p->levels[0];
315 previous_color[1] =
p->levels[1];
316 previous_color[2] =
p->levels[2];
318 c->last_picked_color = mean_picked_color;
320 if(picker == c->blackpick)
322 if(mean_picked_color >
p->levels[1])
324 p->levels[0] =
p->levels[1] - FLT_EPSILON;
328 p->levels[0] = mean_picked_color;
331 else if(picker == c->greypick)
333 if(mean_picked_color < p->
levels[0] || mean_picked_color >
p->levels[2])
335 p->levels[1] =
p->levels[1];
339 p->levels[1] = mean_picked_color;
342 else if(picker == c->whitepick)
344 if(mean_picked_color < p->
levels[1])
346 p->levels[2] =
p->levels[1] + FLT_EPSILON;
350 p->levels[2] = mean_picked_color;
354 if(previous_color[0] !=
p->levels[0]
355 || previous_color[1] !=
p->levels[1]
356 || previous_color[2] !=
p->levels[2])
389 d->levels[0] =
g->auto_levels[0];
390 d->levels[1] =
g->auto_levels[1];
391 d->levels[2] =
g->auto_levels[2];
398 || isnan(
d->levels[0]) || isnan(
d->levels[1])
399 || isnan(
d->levels[2]))
410 g->auto_levels[0] =
d->levels[0];
411 g->auto_levels[1] =
d->levels[1];
412 g->auto_levels[2] =
d->levels[2];
421 const void *
const ivoid,
void *
const ovoid)
430 commit_params_late(self, pipe, piece);
433 const float *
const restrict in = (
float*)ivoid;
434 float *
const restrict
out = (
float*)
ovoid;
435 const size_t npixels = (size_t)roi_out->
width * roi_out->
height;
437 for(
int i = 0;
i <
ch * npixels;
i +=
ch)
439 const float L_in = in[
i] / 100.0f;
448 const float percentage = (L_in -
d->levels[0]) / (
d->levels[2] -
d->levels[0]);
450 L_out = percentage < 1.0f ?
d->lut[(int)(percentage * 0x10000ul)] : 100.0f * powf(percentage,
d->in_inv_gamma);
454 const float denom = (in[
i] > 0.01f) ? in[
i] : 0.01f;
456 out[
i+1] = in[
i+1] * L_out / denom;
457 out[
i+2] = in[
i+2] * L_out / denom;
510 d->percentiles[0] =
p->black;
511 d->percentiles[1] =
p->gray;
512 d->percentiles[2] =
p->white;
526 d->levels[0] =
p->levels[0];
527 d->levels[1] =
p->levels[1];
528 d->levels[2] =
p->levels[2];
554 gtk_stack_set_visible_child_name(GTK_STACK(
g->mode_stack),
"automatic");
556 gtk_stack_set_visible_child_name(GTK_STACK(
g->mode_stack),
"manual");
570 g->auto_levels[0] = NAN;
571 g->auto_levels[1] = NAN;
572 g->auto_levels[2] = NAN;
576 gtk_widget_queue_draw(self->
widget);
583 module->request_histogram |= (DT_REQUEST_ON);
597 c->auto_levels[0] = NAN;
598 c->auto_levels[1] = NAN;
599 c->auto_levels[2] = NAN;
605 c->mouse_x = c->mouse_y = -1.0;
607 c->activeToggleButton = NULL;
608 c->last_picked_color = -1;
610 c->mode_stack = gtk_stack_new();
611 gtk_stack_set_homogeneous(GTK_STACK(c->mode_stack),
FALSE);
613 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
614 gtk_widget_set_hexpand(GTK_WIDGET(c->area),
TRUE);
616 gtk_box_pack_start(GTK_BOX(vbox_manual),
618 "plugins/darkroom/levels/graphheight", 280, 100),
621 gtk_widget_set_tooltip_text(GTK_WIDGET(c->area),_(
"drag handles to set black, gray, and white points. "
622 "operates on L channel."));
624 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK
625 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
636 GtkWidget *autobutton = gtk_button_new_with_label(_(
"auto"));
637 gtk_widget_set_tooltip_text(autobutton, _(
"apply auto levels"));
641 gtk_widget_set_tooltip_text(c->blackpick, _(
"pick black point from image"));
642 gtk_widget_set_name(GTK_WIDGET(c->blackpick),
"picker-black");
645 gtk_widget_set_tooltip_text(c->greypick, _(
"pick medium gray point from image"));
646 gtk_widget_set_name(GTK_WIDGET(c->greypick),
"picker-grey");
649 gtk_widget_set_tooltip_text(c->whitepick, _(
"pick white point from image"));
650 gtk_widget_set_name(GTK_WIDGET(c->whitepick),
"picker-white");
652 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(autobutton ),
TRUE,
TRUE, 0);
653 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->blackpick),
TRUE,
TRUE, 0);
654 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->greypick ),
TRUE,
TRUE, 0);
655 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->whitepick),
TRUE,
TRUE, 0);
656 gtk_box_pack_start(GTK_BOX(vbox_manual), box,
TRUE,
TRUE, 0);
658 gtk_stack_add_named(GTK_STACK(c->mode_stack), vbox_manual,
"manual");
663 gtk_widget_set_tooltip_text(c->percentile_black, _(
"black percentile"));
667 gtk_widget_set_tooltip_text(c->percentile_grey, _(
"gray percentile"));
671 gtk_widget_set_tooltip_text(c->percentile_white, _(
"white percentile"));
674 gtk_stack_add_named(GTK_STACK(c->mode_stack), vbox_automatic,
"automatic");
681 gtk_box_pack_start(GTK_BOX(self->
widget), c->mode_stack,
TRUE,
TRUE, 0);
687 g_list_free(
g->modes);
697 c->mouse_x = c->mouse_y = -1.0;
698 gtk_widget_queue_draw(widget);
709 GtkAllocation allocation;
710 gtk_widget_get_allocation(GTK_WIDGET(c->area), &allocation);
711 int width = allocation.width,
height = allocation.height;
713 cairo_t *cr = cairo_create(cst);
716 cairo_set_source_rgb(cr, .2, .2, .2);
719 cairo_translate(cr, inset, inset);
724 cairo_set_source_rgb(cr, .1, .1, .1);
728 cairo_set_source_rgb(cr, .3, .3, .3);
734 cairo_set_source_rgb(cr, .1, .1, .1);
740 for(
int k = 0;
k < 3;
k++)
742 if(
k == c->handle_move && c->mouse_x > 0)
743 cairo_set_source_rgb(cr, 1, 1, 1);
745 cairo_set_source_rgb(cr, .7, .7, .7);
748 cairo_rel_line_to(cr, 0, -
height);
755 for(
int k = 0;
k < 3;
k++)
760 cairo_set_source_rgb(cr, 0, 0, 0);
764 cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
768 cairo_set_source_rgb(cr, 1, 1, 1);
773 cairo_rel_line_to(cr, -arrw * .5f, 0);
774 cairo_rel_line_to(cr, arrw * .5f, -arrw);
775 cairo_rel_line_to(cr, arrw * .5f, arrw);
776 cairo_close_path(cr);
777 if(c->handle_move ==
k && c->mouse_x > 0)
783 cairo_translate(cr, 0,
height);
790 const gboolean is_linear =
FALSE;
796 cairo_set_source_rgba(cr, .2, .2, .2, 0.5);
804 cairo_set_source_surface(crf, cst, 0, 0);
806 cairo_surface_destroy(cst);
824 float drag_start_percentage)
830 if((handle_move < 0) || handle_move > 2)
return;
838 max_x = fminf(
levels[2] - (0.05 / drag_start_percentage), 1);
839 max_x = fminf((
levels[2] * (1 - drag_start_percentage) - 0.05) / (1 - drag_start_percentage), max_x);
848 min_x = fmaxf((0.05 / drag_start_percentage) +
levels[0], 0);
849 min_x = fmaxf((
levels[0] * (1 - drag_start_percentage) + 0.05) / (1 - drag_start_percentage), min_x);
853 levels[handle_move] = fminf(max_x, fmaxf(min_x, new_pos));
857 if(!
IS_NULL_PTR(c->activeToggleButton)) gtk_toggle_button_set_active(c->activeToggleButton,
FALSE);
858 c->last_picked_color = -1;
867 GtkAllocation allocation;
868 gtk_widget_get_allocation(widget, &allocation);
869 int height = allocation.height - 2 * inset,
width = allocation.width - 2 * inset;
872 c->mouse_x = CLAMP(event->x - inset, 0,
width);
873 c->drag_start_percentage = (
p->levels[1] -
p->levels[0]) / (
p->levels[2] -
p->levels[0]);
875 c->mouse_y = CLAMP(event->y - inset, 0,
height);
879 if(c->handle_move >= 0 && c->handle_move < 3)
881 const float mx = (CLAMP(event->x - inset, 0,
width)) / (
float)
width;
890 const float mx = CLAMP(event->x - inset, 0,
width) / (float)
width;
891 float dist = fabsf(
p->levels[0] - mx);
892 for(
int k = 1;
k < 3;
k++)
894 float d2 = fabsf(
p->levels[
k] - mx);
902 gtk_widget_queue_draw(widget);
910 if(event->button == 1)
916 if(event->type == GDK_2BUTTON_PRESS)
924 c->drag_start_percentage = 0.5;
926 gtk_widget_queue_draw(self->
widget);
940 if(event->button == 1)
967 const float interval = 0.002;
970 float new_position =
p->levels[c->handle_move] - interval * delta_y;
989 if(!
IS_NULL_PTR(c->activeToggleButton)) gtk_toggle_button_set_active(c->activeToggleButton,
FALSE);
990 c->last_picked_color = -1;
static double dist(double x1, double y1, double x2, double y2)
int levels(struct dt_imageio_module_data_t *data)
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
const dt_colormatrix_t dt_aligned_pixel_t out
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_control_log(const char *msg,...)
void * dt_alloc_align(size_t size)
#define dt_free_align(ptr)
static void * dt_calloc_align(size_t size)
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
#define __DT_CLONE_TARGETS__
#define __OMP_PARALLEL_FOR__(...)
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
#define dt_dev_add_history_item(dev, module, enable, redraw)
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
static void dt_draw_vertical_lines(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
static void dt_draw_histogram_8(cairo_t *cr, const uint32_t *hist, int32_t channels, int32_t channel, const gboolean linear)
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_iop_default_init(dt_iop_module_t *module)
void dt_iop_request_focus(dt_iop_module_t *module)
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
@ IOP_FLAGS_SUPPORTS_BLENDING
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
#define IOP_GUI_ALLOC(module)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void init(dt_iop_module_t *module)
const char ** description(struct dt_iop_module_t *self)
static gboolean dt_iop_levels_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
static __DT_CLONE_TARGETS__ void dt_iop_levels_compute_levels_automatic(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece)
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
__DT_CLONE_TARGETS__ int process(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
#define DT_GUI_CURVE_EDITOR_INSET
void gui_init(dt_iop_module_t *self)
static void dt_iop_levels_autoadjust_callback(GtkRange *range, dt_iop_module_t *self)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
void gui_cleanup(dt_iop_module_t *self)
static void dt_iop_levels_compute_levels_manual(const uint32_t *histogram, float *levels)
void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
void input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
static gboolean dt_iop_levels_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
static gboolean dt_iop_levels_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
static gboolean dt_iop_levels_area_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
static gboolean dt_iop_levels_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static void dt_iop_levels_move_handle(dt_iop_module_t *self, int handle_move, float new_pos, float *levels, float drag_start_percentage)
static gboolean dt_iop_levels_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
static __DT_CLONE_TARGETS__ void compute_lut(const dt_dev_pixelpipe_iop_t *piece)
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
float dt_aligned_pixel_t[4]
struct _GtkWidget GtkWidget
unsigned __int64 uint64_t
struct dt_gui_gtk_t * gui
struct dt_develop_t * develop
dt_dev_request_flags_t request_histogram
struct dt_iop_module_t *void * data
dt_dev_histogram_collection_params_t histogram_params
struct dt_iop_module_t * gui_module
dt_iop_buffer_type_t datatype
dt_iop_levels_mode_t mode
GtkWidget * percentile_black
GtkToggleButton * activeToggleButton
GtkWidget * percentile_white
float drag_start_percentage
GtkWidget * percentile_grey
dt_iop_levels_mode_t mode
dt_iop_params_t * default_params
struct dt_develop_t * dev
dt_iop_gui_data_t * gui_data
dt_dev_request_flags_t request_histogram
dt_dev_histogram_stats_t histogram_stats
uint32_t histogram_max[4]
dt_aligned_pixel_t picked_color
Region of interest passed through the pixelpipe.