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];
559 gtk_stack_set_visible_child_name(GTK_STACK(
g->mode_stack),
"automatic");
561 gtk_stack_set_visible_child_name(GTK_STACK(
g->mode_stack),
"manual");
575 g->auto_levels[0] = NAN;
576 g->auto_levels[1] = NAN;
577 g->auto_levels[2] = NAN;
581 gtk_widget_queue_draw(self->
widget);
588 module->request_histogram |= (DT_REQUEST_ON);
602 c->auto_levels[0] = NAN;
603 c->auto_levels[1] = NAN;
604 c->auto_levels[2] = NAN;
610 c->mouse_x = c->mouse_y = -1.0;
612 c->activeToggleButton = NULL;
613 c->last_picked_color = -1;
615 c->mode_stack = gtk_stack_new();
616 gtk_stack_set_homogeneous(GTK_STACK(c->mode_stack),
FALSE);
618 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
619 gtk_widget_set_hexpand(GTK_WIDGET(c->area),
TRUE);
621 gtk_box_pack_start(GTK_BOX(vbox_manual),
623 "plugins/darkroom/levels/graphheight", 280, 100),
626 gtk_widget_set_tooltip_text(GTK_WIDGET(c->area),_(
"drag handles to set black, gray, and white points. "
627 "operates on L channel."));
629 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK
630 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
641 GtkWidget *autobutton = gtk_button_new_with_label(_(
"auto"));
642 gtk_widget_set_tooltip_text(autobutton, _(
"apply auto levels"));
646 gtk_widget_set_tooltip_text(c->blackpick, _(
"pick black point from image"));
647 gtk_widget_set_name(GTK_WIDGET(c->blackpick),
"picker-black");
650 gtk_widget_set_tooltip_text(c->greypick, _(
"pick medium gray point from image"));
651 gtk_widget_set_name(GTK_WIDGET(c->greypick),
"picker-grey");
654 gtk_widget_set_tooltip_text(c->whitepick, _(
"pick white point from image"));
655 gtk_widget_set_name(GTK_WIDGET(c->whitepick),
"picker-white");
657 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(autobutton ),
TRUE,
TRUE, 0);
658 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->blackpick),
TRUE,
TRUE, 0);
659 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->greypick ),
TRUE,
TRUE, 0);
660 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->whitepick),
TRUE,
TRUE, 0);
661 gtk_box_pack_start(GTK_BOX(vbox_manual), box,
TRUE,
TRUE, 0);
663 gtk_stack_add_named(GTK_STACK(c->mode_stack), vbox_manual,
"manual");
668 gtk_widget_set_tooltip_text(c->percentile_black, _(
"black percentile"));
672 gtk_widget_set_tooltip_text(c->percentile_grey, _(
"gray percentile"));
676 gtk_widget_set_tooltip_text(c->percentile_white, _(
"white percentile"));
679 gtk_stack_add_named(GTK_STACK(c->mode_stack), vbox_automatic,
"automatic");
686 gtk_box_pack_start(GTK_BOX(self->
widget), c->mode_stack,
TRUE,
TRUE, 0);
692 g_list_free(
g->modes);
702 c->mouse_x = c->mouse_y = -1.0;
703 gtk_widget_queue_draw(widget);
714 GtkAllocation allocation;
715 gtk_widget_get_allocation(GTK_WIDGET(c->area), &allocation);
716 int width = allocation.width,
height = allocation.height;
718 cairo_t *cr = cairo_create(cst);
721 cairo_set_source_rgb(cr, .2, .2, .2);
724 cairo_translate(cr, inset, inset);
729 cairo_set_source_rgb(cr, .1, .1, .1);
733 cairo_set_source_rgb(cr, .3, .3, .3);
739 cairo_set_source_rgb(cr, .1, .1, .1);
745 for(
int k = 0;
k < 3;
k++)
747 if(
k == c->handle_move && c->mouse_x > 0)
748 cairo_set_source_rgb(cr, 1, 1, 1);
750 cairo_set_source_rgb(cr, .7, .7, .7);
753 cairo_rel_line_to(cr, 0, -
height);
760 for(
int k = 0;
k < 3;
k++)
765 cairo_set_source_rgb(cr, 0, 0, 0);
769 cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
773 cairo_set_source_rgb(cr, 1, 1, 1);
778 cairo_rel_line_to(cr, -arrw * .5f, 0);
779 cairo_rel_line_to(cr, arrw * .5f, -arrw);
780 cairo_rel_line_to(cr, arrw * .5f, arrw);
781 cairo_close_path(cr);
782 if(c->handle_move ==
k && c->mouse_x > 0)
788 cairo_translate(cr, 0,
height);
795 const gboolean is_linear =
FALSE;
801 cairo_set_source_rgba(cr, .2, .2, .2, 0.5);
809 cairo_set_source_surface(crf, cst, 0, 0);
811 cairo_surface_destroy(cst);
829 float drag_start_percentage)
835 if((handle_move < 0) || handle_move > 2)
return;
843 max_x = fminf(
levels[2] - (0.05 / drag_start_percentage), 1);
844 max_x = fminf((
levels[2] * (1 - drag_start_percentage) - 0.05) / (1 - drag_start_percentage), max_x);
853 min_x = fmaxf((0.05 / drag_start_percentage) +
levels[0], 0);
854 min_x = fmaxf((
levels[0] * (1 - drag_start_percentage) + 0.05) / (1 - drag_start_percentage), min_x);
858 levels[handle_move] = fminf(max_x, fmaxf(min_x, new_pos));
862 if(!
IS_NULL_PTR(c->activeToggleButton)) gtk_toggle_button_set_active(c->activeToggleButton,
FALSE);
863 c->last_picked_color = -1;
872 GtkAllocation allocation;
873 gtk_widget_get_allocation(widget, &allocation);
874 int height = allocation.height - 2 * inset,
width = allocation.width - 2 * inset;
877 c->mouse_x = CLAMP(event->x - inset, 0,
width);
878 c->drag_start_percentage = (
p->levels[1] -
p->levels[0]) / (
p->levels[2] -
p->levels[0]);
880 c->mouse_y = CLAMP(event->y - inset, 0,
height);
884 if(c->handle_move >= 0 && c->handle_move < 3)
886 const float mx = (CLAMP(event->x - inset, 0,
width)) / (
float)
width;
895 const float mx = CLAMP(event->x - inset, 0,
width) / (float)
width;
896 float dist = fabsf(
p->levels[0] - mx);
897 for(
int k = 1;
k < 3;
k++)
899 float d2 = fabsf(
p->levels[
k] - mx);
907 gtk_widget_queue_draw(widget);
915 if(event->button == 1)
921 if(event->type == GDK_2BUTTON_PRESS)
929 c->drag_start_percentage = 0.5;
931 gtk_widget_queue_draw(self->
widget);
945 if(event->button == 1)
972 const float interval = 0.002;
975 float new_position =
p->levels[c->handle_move] - interval * delta_y;
994 if(!
IS_NULL_PTR(c->activeToggleButton)) gtk_toggle_button_set_active(c->activeToggleButton,
FALSE);
995 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...
void void void gboolean dt_gui_widgets_suppressed(void)
#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.