/** process, all real work is done here. */ void process (struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, void *i, void *o, const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out) { // this is called for preview and full pipe separately, each with its own pixelpipe piece. assert(dt_iop_module_colorspace(self) == iop_cs_Lab); // get our data struct: dt_iop_colorcontrast_params_t *d = (dt_iop_colorcontrast_params_t *)piece->data; // how many colors in our buffer? const int ch = piece->colors; // iterate over all output pixels (same coordinates as input) #ifdef _OPENMP // optional: parallelize it! #pragma omp parallel for default(none) schedule(static) shared(i,o,roi_in,roi_out,d) #endif for(int j=0; j<roi_out->height; j++) { float *in = ((float *)i) + ch*roi_in->width *j; float *out = ((float *)o) + ch*roi_out->width*j; const __m128 scale = _mm_set_ps(0.0f,d->b_steepness,d->a_steepness,1.0f); const __m128 offset = _mm_set_ps(0.0f,d->b_offset,d->a_offset,0.0f); const __m128 min = _mm_set_ps(0.0f,-128.0f,-128.0f, -INFINITY); const __m128 max = _mm_set_ps(0.0f, 128.0f, 128.0f, INFINITY); for(int i=0; i<roi_out->width; i++) { _mm_stream_ps(out,_mm_min_ps(max,_mm_max_ps(min,_mm_add_ps(offset,_mm_mul_ps(scale,_mm_load_ps(in)))))); in+=ch; out+=ch; } } _mm_sfence(); }
void dt_iop_gui_init_blending(GtkWidget *iopw, dt_iop_module_t *module) { /* create and add blend mode if module supports it */ if (module->flags()&IOP_FLAGS_SUPPORTS_BLENDING) { module->blend_data = g_malloc(sizeof(dt_iop_gui_blend_data_t)); memset(module->blend_data, 0, sizeof(dt_iop_gui_blend_data_t)); dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t*)module->blend_data; dt_iop_gui_blendop_modes_t modes[23]; /* number must fit exactly!!! */ modes[0].mode = DEVELOP_BLEND_DISABLED; modes[0].name = _("off"); modes[1].mode = DEVELOP_BLEND_NORMAL; modes[1].name = _("normal"); modes[2].mode = DEVELOP_BLEND_INVERSE; modes[2].name = _("inverse"); modes[3].mode = DEVELOP_BLEND_LIGHTEN; modes[3].name = _("lighten"); modes[4].mode = DEVELOP_BLEND_DARKEN; modes[4].name = _("darken"); modes[5].mode = DEVELOP_BLEND_MULTIPLY; modes[5].name = _("multiply"); modes[6].mode = DEVELOP_BLEND_AVERAGE; modes[6].name = _("average"); modes[7].mode = DEVELOP_BLEND_ADD; modes[7].name = _("addition"); modes[8].mode = DEVELOP_BLEND_SUBSTRACT; modes[8].name = _("subtract"); modes[9].mode = DEVELOP_BLEND_DIFFERENCE; modes[9].name = _("difference"); modes[10].mode = DEVELOP_BLEND_SCREEN; modes[10].name = _("screen"); modes[11].mode = DEVELOP_BLEND_OVERLAY; modes[11].name = _("overlay"); modes[12].mode = DEVELOP_BLEND_SOFTLIGHT; modes[12].name = _("softlight"); modes[13].mode = DEVELOP_BLEND_HARDLIGHT; modes[13].name = _("hardlight"); modes[14].mode = DEVELOP_BLEND_VIVIDLIGHT; modes[14].name = _("vividlight"); modes[15].mode = DEVELOP_BLEND_LINEARLIGHT; modes[15].name = _("linearlight"); modes[16].mode = DEVELOP_BLEND_PINLIGHT; modes[16].name = _("pinlight"); modes[17].mode = DEVELOP_BLEND_LIGHTNESS; modes[17].name = _("lightness"); modes[18].mode = DEVELOP_BLEND_CHROMA; modes[18].name = _("chroma"); modes[19].mode = DEVELOP_BLEND_HUE; modes[19].name = _("hue"); modes[20].mode = DEVELOP_BLEND_COLOR; modes[20].name = _("color"); modes[21].mode = DEVELOP_BLEND_COLORADJUST; modes[21].name = _("coloradjustment"); modes[22].mode = DEVELOP_BLEND_UNBOUNDED; modes[22].name = _("unbounded"); bd->number_modes = sizeof(modes) / sizeof(dt_iop_gui_blendop_modes_t); memcpy(bd->modes, modes, bd->number_modes * sizeof(dt_iop_gui_blendop_modes_t)); bd->iopw = iopw; bd->module = module; bd->csp = dt_iop_module_colorspace(module); bd->blendif_support = (bd->csp == iop_cs_Lab || bd->csp == iop_cs_rgb); bd->blendif_box = NULL; bd->blend_modes_combo = dt_bauhaus_combobox_new(module); dt_bauhaus_widget_set_label(bd->blend_modes_combo, _("blend mode")); bd->opacity_slider = dt_bauhaus_slider_new_with_range(module, 0.0, 100.0, 1, 100.0, 0); dt_bauhaus_widget_set_label(bd->opacity_slider, _("opacity")); dt_bauhaus_slider_set_format(bd->opacity_slider, "%.0f%%"); module->fusion_slider = bd->opacity_slider; for(int k = 0; k < bd->number_modes; k++) dt_bauhaus_combobox_add(bd->blend_modes_combo, bd->modes[k].name); dt_bauhaus_combobox_set(bd->blend_modes_combo, 0); gtk_object_set(GTK_OBJECT(bd->opacity_slider), "tooltip-text", _("set the opacity of the blending"), (char *)NULL); gtk_object_set(GTK_OBJECT(bd->blend_modes_combo), "tooltip-text", _("choose blending mode"), (char *)NULL); g_signal_connect (G_OBJECT (bd->opacity_slider), "value-changed", G_CALLBACK (_blendop_opacity_callback), bd); g_signal_connect (G_OBJECT (bd->blend_modes_combo), "value-changed", G_CALLBACK (_blendop_mode_callback), bd); gtk_box_pack_start(GTK_BOX(iopw), bd->blend_modes_combo, TRUE, TRUE,0); gtk_box_pack_start(GTK_BOX(iopw), bd->opacity_slider, TRUE, TRUE,0); if(bd->blendif_support) { dt_iop_gui_init_blendif(GTK_VBOX(iopw), module); } bd->blend_inited = 1; gtk_widget_queue_draw(GTK_WIDGET(iopw)); dt_iop_gui_update_blending(module); } }
// the basis of how the following algorithm works comes from rawtherapee (http://rawtherapee.com/) // defringe -- thanks to Emil Martinec <*****@*****.**> for that // quite some modifications were done though: // 1. use a fibonacci lattice instead of full window, to speed things up // 2. option for local averaging or static (RT used the global/region one) // 3. additional condition to reduce sharp edged artifacts, by blurring pixels near pixels over threshold, // this really helps improving the filter with thick fringes // ----------------------------------------------------------------------------------------- // in the following you will also see some more "magic numbers", // most are chosen arbitrarily and/or by experiment/trial+error ... I am sorry ;-) // and having everything user-defineable would be just too much // ----------------------------------------------------------------------------------------- void process(struct dt_iop_module_t *module, dt_dev_pixelpipe_iop_t *piece, void *i, void *o, const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out) { dt_iop_defringe_data_t *d = (dt_iop_defringe_data_t *)piece->data; assert(dt_iop_module_colorspace(module) == iop_cs_Lab); const int order = 1; // 0,1,2 const float sigma = fmax(0.1f, fabs(d->radius)) * roi_in->scale / piece->iscale; const float Labmax[] = { 100.0f, 128.0f, 128.0f, 1.0f }; const float Labmin[] = { 0.0f, -128.0f, -128.0f, 0.0f }; const int ch = piece->colors; const int radius = ceil(2.0 * ceilf(sigma)); // save the fibonacci lattices in them later int *xy_avg = NULL; int *xy_artifact = NULL; int *xy_small = NULL; if(roi_out->width < 2 * radius + 1 || roi_out->height < 2 * radius + 1) goto ERROR_EXIT; float avg_edge_chroma = 0.0; float *const in = (float *const)i; float *const out = (float *const)o; int width = roi_in->width; int height = roi_in->height; dt_gaussian_t *gauss = NULL; gauss = dt_gaussian_init(width, height, ch, Labmax, Labmin, sigma, order); if(!gauss) { fprintf(stderr, "Error allocating memory for gaussian blur in: defringe module\n"); goto ERROR_EXIT; } dt_gaussian_blur(gauss, in, out); dt_gaussian_free(gauss); // Pre-Compute Fibonacci Lattices int *tmp; int samples_wish = radius * radius; int sampleidx_avg; // select samples by fibonacci number if(samples_wish > 89) { sampleidx_avg = 12; // 144 samples } else if(samples_wish > 55) { sampleidx_avg = 11; // 89 samples } else if(samples_wish > 34) { sampleidx_avg = 10; // ..you get the idea } else if(samples_wish > 21) { sampleidx_avg = 9; } else if(samples_wish > 13) { sampleidx_avg = 8; } else { // don't use less than 13 samples sampleidx_avg = 7; } const int sampleidx_small = sampleidx_avg - 1; const int small_radius = MAX(radius, 3); const int avg_radius = 24 + radius * 4; const int samples_small = fib[sampleidx_small]; const int samples_avg = fib[sampleidx_avg]; // precompute all required fibonacci lattices: if((xy_avg = malloc((size_t)2 * sizeof(int) * samples_avg))) { tmp = xy_avg; for(int u = 0; u < samples_avg; u++) { int dx, dy; fib_latt(&dx, &dy, avg_radius, u, sampleidx_avg); *tmp++ = dx; *tmp++ = dy; } } else { fprintf(stderr, "Error allocating memory for fibonacci lattice in: defringe module\n"); goto ERROR_EXIT; } if((xy_small = malloc((size_t)2 * sizeof(int) * samples_small))) { tmp = xy_small; for(int u = 0; u < samples_small; u++) { int dx, dy; fib_latt(&dx, &dy, small_radius, u, sampleidx_small); *tmp++ = dx; *tmp++ = dy; } } else { fprintf(stderr, "Error allocating memory for fibonacci lattice in: defringe module\n"); goto ERROR_EXIT; } #ifdef _OPENMP #pragma omp parallel for default(none) shared(width, height, \ d) reduction(+ : avg_edge_chroma) schedule(static) #endif for(int v = 0; v < height; v++) { for(int t = 0; t < width; t++) { // edge-detect on color channels // method: difference of original to gaussian blurred image: float a = in[(size_t)v * width * ch + t * ch + 1] - out[(size_t)v * width * ch + t * ch + 1]; float b = in[(size_t)v * width * ch + t * ch + 2] - out[(size_t)v * width * ch + t * ch + 2]; float edge = (a * a + b * b); // range up to 2*(256)^2 -> approx. 0 to 131072 // save local edge chroma in out[.. +3] , this is later compared with threshold out[(size_t)v * width * ch + t * ch + 3] = edge; // the average chroma of the edge-layer in the roi if(MODE_GLOBAL_AVERAGE == d->op_mode) avg_edge_chroma += edge; } } float thresh; if(MODE_GLOBAL_AVERAGE == d->op_mode) { avg_edge_chroma = avg_edge_chroma / (width * height) + 10.0 * FLT_EPSILON; thresh = fmax(0.1f, 4.0 * d->thresh * avg_edge_chroma / MAGIC_THRESHOLD_COEFF); } else { // this fixed value will later be changed when doing local averaging, or kept as-is in "static" mode avg_edge_chroma = MAGIC_THRESHOLD_COEFF; thresh = fmax(0.1f, d->thresh); } #ifdef _OPENMP // dynamically/guided scheduled due to possible uneven edge-chroma distribution (thanks to rawtherapee code // for this hint!) #pragma omp parallel for default(none) shared(width, height, d, xy_small, xy_avg, xy_artifact) \ firstprivate(thresh, avg_edge_chroma) schedule(guided, 32) #endif for(int v = 0; v < height; v++) { for(int t = 0; t < width; t++) { float local_thresh = thresh; // think of compiler setting "-funswitch-loops" to maybe improve these things: if(MODE_LOCAL_AVERAGE == d->op_mode && out[(size_t)v * width * ch + t * ch + 3] > thresh) { float local_avg = 0.0; // use some and not all values from the neigbourhood to speed things up: const int *tmp = xy_avg; for(int u = 0; u < samples_avg; u++) { int dx = *tmp++; int dy = *tmp++; int x = MAX(0, MIN(width - 1, t + dx)); int y = MAX(0, MIN(height - 1, v + dy)); local_avg += out[(size_t)y * width * ch + x * ch + 3]; } avg_edge_chroma = fmax(0.01f, (float)local_avg / samples_avg); local_thresh = fmax(0.1f, 4.0 * d->thresh * avg_edge_chroma / MAGIC_THRESHOLD_COEFF); } if(out[(size_t)v * width * ch + t * ch + 3] > local_thresh // reduces artifacts ("region growing by 1 pixel"): || out[(size_t)MAX(0, (v - 1)) * width * ch + MAX(0, (t - 1)) * ch + 3] > local_thresh || out[(size_t)MAX(0, (v - 1)) * width * ch + t * ch + 3] > local_thresh || out[(size_t)MAX(0, (v - 1)) * width * ch + MIN(width - 1, (t + 1)) * ch + 3] > local_thresh || out[(size_t)v * width * ch + MAX(0, (t - 1)) * ch + 3] > local_thresh || out[(size_t)v * width * ch + MIN(width - 1, (t + 1)) * ch + 3] > local_thresh || out[(size_t)MIN(height - 1, (v + 1)) * width * ch + MAX(0, (t - 1)) * ch + 3] > local_thresh || out[(size_t)MIN(height - 1, (v + 1)) * width * ch + t * ch + 3] > local_thresh || out[(size_t)MIN(height - 1, (v + 1)) * width * ch + MIN(width - 1, (t + 1)) * ch + 3] > local_thresh) { float atot = 0, btot = 0; float norm = 0; float weight; // it seems better to use only some pixels from a larger window instead of all pixels from a smaller // window // we use a fibonacci lattice for that, samples amount need to be a fibonacci number, this can then be // scaled to // a certain radius // use some neighbourhood pixels for lowest chroma average const int *tmp = xy_small; for(int u = 0; u < samples_small; u++) { int dx = *tmp++; int dy = *tmp++; int x = MAX(0, MIN(width - 1, t + dx)); int y = MAX(0, MIN(height - 1, v + dy)); // inverse chroma weighted average of neigbouring pixels inside window // also taking average edge chromaticity into account (either global or local average) weight = 1.0 / (out[(size_t)y * width * ch + x * ch + 3] + avg_edge_chroma); atot += weight * in[(size_t)y * width * ch + x * ch + 1]; btot += weight * in[(size_t)y * width * ch + x * ch + 2]; norm += weight; } // here we could try using a "balance" between original and changed value, this could be used to // reduce artifcats // but on first tries, results weren't very convincing, and there are blend settings available anyway // in dt // float balance = (out[v*width*ch +t*ch +3]-thresh)/out[v*width*ch +t*ch +3]; double a = (atot / norm); // *balance + in[v*width*ch + t*ch +1]*(1.0-balance); double b = (btot / norm); // *balance + in[v*width*ch + t*ch +2]*(1.0-balance); // if (a < -128.0 || a > 127.0) CLIP(a,-128.0,127.0); // if (b < -128.0 || b > 127.0) CLIP(b,-128.0,127.0); out[(size_t)v * width * ch + t * ch + 1] = a; out[(size_t)v * width * ch + t * ch + 2] = b; } else { out[(size_t)v * width * ch + t * ch + 1] = in[(size_t)v * width * ch + t * ch + 1]; out[(size_t)v * width * ch + t * ch + 2] = in[(size_t)v * width * ch + t * ch + 2]; } out[(size_t)v * width * ch + t * ch] = in[(size_t)v * width * ch + t * ch]; } } if(piece->pipe->mask_display) dt_iop_alpha_copy(i, o, roi_out->width, roi_out->height); goto FINISH_PROCESS; ERROR_EXIT: memcpy(o, i, (size_t)sizeof(float) * ch * roi_out->width * roi_out->height); FINISH_PROCESS: free(xy_artifact); free(xy_small); free(xy_avg); }