int dt_load_from_string(const gchar* input, gboolean open_image_in_dr) { int id = 0; if(input == NULL || input[0] == '\0') return 0; char* filename = dt_make_path_absolute(input); if(filename == NULL) { dt_control_log(_("found strange path `%s'"), input); return 0; } if(g_file_test(filename, G_FILE_TEST_IS_DIR)) { // import a directory into a film roll unsigned int last_char = strlen(filename)-1; if(filename[last_char] == '/') filename[last_char] = '\0'; id = dt_film_import(filename); if(id) { dt_film_open(id); dt_ctl_switch_mode_to(DT_LIBRARY); } else { dt_control_log(_("error loading directory `%s'"), filename); } } else { // import a single image gchar *directory = g_path_get_dirname((const gchar *)filename); dt_film_t film; const int filmid = dt_film_new(&film, directory); id = dt_image_import(filmid, filename, TRUE); g_free (directory); if(id) { dt_film_open(filmid); // make sure buffers are loaded (load full for testing) dt_mipmap_buffer_t buf; dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, id, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING); if(!buf.buf) { id = 0; dt_control_log(_("file `%s' has unknown format!"), filename); } else { dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); if(open_image_in_dr) { dt_control_set_mouse_over_id(id); dt_ctl_switch_mode_to(DT_DEVELOP); } } } else { dt_control_log(_("error loading file `%s'"), filename); } } g_free(filename); return id; }
static void _lib_filmstrip_dnd_begin_callback(GtkWidget *widget, GdkDragContext *context, gpointer user_data) { const int ts = 64; dt_lib_module_t *self = (dt_lib_module_t *)user_data; dt_lib_filmstrip_t *strip = (dt_lib_filmstrip_t *)self->data; int imgid = strip->mouse_over_id; // imgid part of selection -> do nothing // otherwise -> select the current image strip->select = DT_LIB_FILMSTRIP_SELECT_NONE; sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select imgid from selected_images where imgid=?1", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); if(sqlite3_step(stmt) != SQLITE_ROW) { dt_selection_select_single(darktable.selection, imgid); /* redraw filmstrip */ if(darktable.view_manager->proxy.filmstrip.module) gtk_widget_queue_draw(darktable.view_manager->proxy.filmstrip.module->widget); } sqlite3_finalize(stmt); // if we are dragging a single image -> use the thumbnail of that image // otherwise use the generic d&d icon // TODO: have something pretty in the 2nd case, too. if(dt_collection_get_selected_count(NULL) == 1) { dt_mipmap_buffer_t buf; dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, ts, ts); dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, imgid, mip, DT_MIPMAP_BLOCKING); if(buf.buf) { uint8_t *scratchmem = dt_mipmap_cache_alloc_scratchmem(darktable.mipmap_cache); uint8_t *buf_decompressed = dt_mipmap_cache_decompress(&buf, scratchmem); uint8_t *rgbbuf = g_malloc((buf.width+2)*(buf.height+2)*3); memset(rgbbuf, 64, (buf.width+2)*(buf.height+2)*3); for(int i=1; i<=buf.height; i++) for(int j=1; j<=buf.width; j++) for(int k=0; k<3; k++) rgbbuf[(i*(buf.width+2)+j)*3+k] = buf_decompressed[((i-1)*buf.width+j-1)*4+2-k]; int w=ts, h=ts; if(buf.width < buf.height) w = (buf.width*ts)/buf.height; // portrait else h = (buf.height*ts)/buf.width; // landscape GdkPixbuf *source = gdk_pixbuf_new_from_data(rgbbuf, GDK_COLORSPACE_RGB, FALSE, 8, (buf.width+2), (buf.height+2), (buf.width+2)*3, NULL, NULL); GdkPixbuf *scaled = gdk_pixbuf_scale_simple(source, w, h, GDK_INTERP_HYPER); gtk_drag_set_icon_pixbuf(context, scaled, 0, 0); if(source) g_object_unref(source); if(scaled) g_object_unref(scaled); g_free(rgbbuf); } dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); } }
int32_t dt_control_merge_hdr_job_run(dt_job_t *job) { int imgid = -1; dt_control_image_enumerator_t *t1 = (dt_control_image_enumerator_t *)job->param; GList *t = t1->index; int total = g_list_length(t); char message[512]= {0}; double fraction=0; snprintf(message, 512, ngettext ("merging %d image", "merging %d images", total), total ); const guint *jid = dt_control_backgroundjobs_create(darktable.control, 1, message); float *pixels = NULL; float *weight = NULL; int wd = 0, ht = 0, first_imgid = -1; uint32_t filter = 0; float whitelevel = 0.0f; total ++; while(t) { imgid = GPOINTER_TO_INT(t->data); dt_mipmap_buffer_t buf; dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, imgid, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING); // just take a copy. also do it after blocking read, so filters and bpp will make sense. const dt_image_t *img = dt_image_cache_read_get(darktable.image_cache, imgid); dt_image_t image = *img; dt_image_cache_read_release(darktable.image_cache, img); if(image.filters == 0 || image.bpp != sizeof(uint16_t)) { dt_control_log(_("exposure bracketing only works on raw images")); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); free(pixels); free(weight); goto error; } filter = dt_image_flipped_filter(img); if(buf.size != DT_MIPMAP_FULL) { dt_control_log(_("failed to get raw buffer from image `%s'"), image.filename); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); free(pixels); free(weight); goto error; } if(!pixels) { first_imgid = imgid; pixels = (float *)malloc(sizeof(float)*image.width*image.height); weight = (float *)malloc(sizeof(float)*image.width*image.height); memset(pixels, 0x0, sizeof(float)*image.width*image.height); memset(weight, 0x0, sizeof(float)*image.width*image.height); wd = image.width; ht = image.height; } else if(image.width != wd || image.height != ht) { dt_control_log(_("images have to be of same size!")); free(pixels); free(weight); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); goto error; } // if no valid exif data can be found, assume peleng fisheye at f/16, 8mm, with half of the light lost in the system => f/22 const float eap = image.exif_aperture > 0.0f ? image.exif_aperture : 22.0f; const float efl = image.exif_focal_length > 0.0f ? image.exif_focal_length : 8.0f; const float rad = .5f * efl/eap; const float aperture = M_PI * rad * rad; const float iso = image.exif_iso > 0.0f ? image.exif_iso : 100.0f; const float exp = image.exif_exposure > 0.0f ? image.exif_exposure : 1.0f; const float cal = 100.0f/(aperture*exp*iso); // about proportional to how many photons we can expect from this shot: const float photoncnt = 100.0f*aperture*exp/iso; // stupid, but we don't know the real sensor saturation level: uint16_t saturation = 0; for(int k=0; k<wd*ht; k++) saturation = MAX(saturation, ((uint16_t *)buf.buf)[k]); // seems to be around 64500--64700 for 5dm2 // fprintf(stderr, "saturation: %u\n", saturation); whitelevel = fmaxf(whitelevel, saturation*cal); #ifdef _OPENMP #pragma omp parallel for schedule(static) default(none) shared(buf, pixels, weight, wd, ht, saturation) #endif for(int k=0; k<wd*ht; k++) { const uint16_t in = ((uint16_t *)buf.buf)[k]; // weights based on siggraph 12 poster // zijian zhu, zhengguo li, susanto rahardja, pasi fraenti // 2d denoising factor for high dynamic range imaging float w = envelope(in/(float)saturation) * photoncnt; // in case we are black and drop to zero weight, give it something // just so numerics don't collapse. blown out whites are handled below. if(w < 1e-3f && in < saturation/3) w = 1e-3f; pixels[k] += w * in * cal; weight[k] += w; } t = g_list_delete_link(t, t); /* update backgroundjob ui plate */ fraction+=1.0/total; dt_control_backgroundjobs_progress(darktable.control, jid, fraction); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); } // normalize by white level to make clipping at 1.0 work as expected (to be sure, scale down one more stop, thus the 0.5): #ifdef _OPENMP #pragma omp parallel for schedule(static) default(none) shared(pixels, wd, ht, weight, whitelevel) #endif for(int k=0; k<wd*ht; k++) { // in case w == 0, all pixels were overexposed (too dark would have been clamped to w >= eps above) if(weight[k] < 1e-3f) pixels[k] = 1.f; // mark as blown out. else // normalize: pixels[k] = fmaxf(0.0f, pixels[k]/(whitelevel*weight[k])); } // output hdr as digital negative with exif data. uint8_t exif[65535]; char pathname[DT_MAX_PATH_LEN]; gboolean from_cache = TRUE; dt_image_full_path(first_imgid, pathname, DT_MAX_PATH_LEN, &from_cache); // last param is dng mode const int exif_len = dt_exif_read_blob(exif, pathname, first_imgid, 0, wd, ht, 1); char *c = pathname + strlen(pathname); while(*c != '.' && c > pathname) c--; g_strlcpy(c, "-hdr.dng", sizeof(pathname)-(c-pathname)); dt_imageio_write_dng(pathname, pixels, wd, ht, exif, exif_len, filter, 1.0f); dt_control_backgroundjobs_progress(darktable.control, jid, 1.0f); while(*c != '/' && c > pathname) c--; dt_control_log(_("wrote merged HDR `%s'"), c+1); // import new image gchar *directory = g_path_get_dirname((const gchar *)pathname); dt_film_t film; const int filmid = dt_film_new(&film, directory); dt_image_import(filmid, pathname, TRUE); g_free (directory); free(pixels); free(weight); error: dt_control_backgroundjobs_destroy(darktable.control, jid); dt_control_queue_redraw_center(); return 0; }
static void _lib_import_single_image_callback(GtkWidget *widget,gpointer user_data) { GtkWidget *win = dt_ui_main_window(darktable.gui->ui); GtkWidget *filechooser = gtk_file_chooser_dialog_new (_("import image"), GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, (char *)NULL); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), TRUE); char *last_directory = dt_conf_get_string("ui_last/import_last_directory"); if(last_directory != NULL) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (filechooser), last_directory); char *cp, **extensions, ext[1024]; GtkFileFilter *filter; filter = GTK_FILE_FILTER(gtk_file_filter_new()); extensions = g_strsplit(dt_supported_extensions, ",", 100); for(char **i=extensions; *i!=NULL; i++) { snprintf(ext, 1024, "*.%s", *i); gtk_file_filter_add_pattern(filter, ext); gtk_file_filter_add_pattern(filter, cp=g_ascii_strup(ext, -1)); g_free(cp); } g_strfreev(extensions); gtk_file_filter_set_name(filter, _("supported images")); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter); filter = GTK_FILE_FILTER(gtk_file_filter_new()); gtk_file_filter_add_pattern(filter, "*"); gtk_file_filter_set_name(filter, _("all files")); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter); if (gtk_dialog_run (GTK_DIALOG (filechooser)) == GTK_RESPONSE_ACCEPT) { dt_conf_set_string("ui_last/import_last_directory", gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER (filechooser))); char *filename = NULL; dt_film_t film; GSList *list = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (filechooser)); GSList *it = list; int id = 0; int filmid = 0; while(it) { filename = (char *)it->data; gchar *directory = g_path_get_dirname((const gchar *)filename); filmid = dt_film_new(&film, directory); id = dt_image_import(filmid, filename, TRUE); if(!id) dt_control_log(_("error loading file `%s'"), filename); g_free (filename); g_free (directory); it = g_slist_next(it); } if(id) { dt_film_open(filmid); // make sure buffers are loaded (load full for testing) dt_mipmap_buffer_t buf; dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, id, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING); if(!buf.buf) { dt_control_log(_("file has unknown format!")); } else { dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); DT_CTL_SET_GLOBAL(lib_image_mouse_over_id, id); dt_ctl_switch_mode_to(DT_DEVELOP); } } } gtk_widget_destroy (filechooser); gtk_widget_queue_draw(dt_ui_center(darktable.gui->ui)); }
int32_t dt_control_merge_hdr_job_run(dt_job_t *job) { long int imgid = -1; dt_control_image_enumerator_t *t1 = (dt_control_image_enumerator_t *)job->param; GList *t = t1->index; int total = g_list_length(t); char message[512]= {0}; double fraction=0; snprintf(message, 512, ngettext ("merging %d image", "merging %d images", total), total ); const guint *jid = dt_control_backgroundjobs_create(darktable.control, 1, message); float *pixels = NULL; float *weight = NULL; int wd = 0, ht = 0, first_imgid = -1; uint32_t filter = 0; float whitelevel = 0.0f; total ++; while(t) { imgid = (long int)t->data; dt_mipmap_buffer_t buf; dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, imgid, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING); // just take a copy. also do it after blocking read, so filters and bpp will make sense. const dt_image_t *img = dt_image_cache_read_get(darktable.image_cache, imgid); dt_image_t image = *img; dt_image_cache_read_release(darktable.image_cache, img); if(image.filters == 0 || image.bpp != sizeof(uint16_t)) { dt_control_log(_("exposure bracketing only works on raw images")); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); free(pixels); free(weight); goto error; } filter = dt_image_flipped_filter(img); if(buf.size != DT_MIPMAP_FULL) { dt_control_log(_("failed to get raw buffer from image `%s'"), image.filename); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); free(pixels); free(weight); goto error; } if(!pixels) { first_imgid = imgid; pixels = (float *)malloc(sizeof(float)*image.width*image.height); weight = (float *)malloc(sizeof(float)*image.width*image.height); memset(pixels, 0x0, sizeof(float)*image.width*image.height); memset(weight, 0x0, sizeof(float)*image.width*image.height); wd = image.width; ht = image.height; } else if(image.width != wd || image.height != ht) { dt_control_log(_("images have to be of same size!")); free(pixels); free(weight); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); goto error; } // if no valid exif data can be found, assume peleng fisheye at f/16, 8mm, with half of the light lost in the system => f/22 const float eap = image.exif_aperture > 0.0f ? image.exif_aperture : 22.0f; const float efl = image.exif_focal_length > 0.0f ? image.exif_focal_length : 8.0f; const float rad = .5f * efl/eap; const float aperture = M_PI * rad * rad; const float iso = image.exif_iso > 0.0f ? image.exif_iso : 100.0f; const float exp = image.exif_exposure > 0.0f ? image.exif_exposure : 1.0f; const float cal = 100.0f/(aperture*exp*iso); whitelevel = fmaxf(whitelevel, cal); #ifdef _OPENMP #pragma omp parallel for schedule(static) default(none) shared(buf, pixels, weight, wd, ht) #endif for(int k=0; k<wd*ht; k++) { const uint16_t in = ((uint16_t *)buf.buf)[k]; const float w = .001f + (in >= 1000 ? (in < 65000 ? in/65000.0f : 0.0f) : exp * 0.01f); pixels[k] += w * in * cal; weight[k] += w; } t = g_list_delete_link(t, t); /* update backgroundjob ui plate */ fraction+=1.0/total; dt_control_backgroundjobs_progress(darktable.control, jid, fraction); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); } // normalize by white level to make clipping at 1.0 work as expected (to be sure, scale down one more stop, thus the 0.5): #ifdef _OPENMP #pragma omp parallel for schedule(static) default(none) shared(pixels, wd, ht, weight, whitelevel) #endif for(int k=0; k<wd*ht; k++) pixels[k] = fmaxf(0.0f, fminf(2.0f, pixels[k]/((.5f*whitelevel*65535.0f)*weight[k]))); // output hdr as digital negative with exif data. uint8_t exif[65535]; char pathname[1024]; dt_image_full_path(first_imgid, pathname, 1024); const int exif_len = dt_exif_read_blob(exif, pathname, 0, first_imgid); char *c = pathname + strlen(pathname); while(*c != '.' && c > pathname) c--; g_strlcpy(c, "-hdr.dng", sizeof(pathname)-(c-pathname)); dt_imageio_write_dng(pathname, pixels, wd, ht, exif, exif_len, filter, whitelevel); dt_control_backgroundjobs_progress(darktable.control, jid, 1.0f); while(*c != '/' && c > pathname) c--; dt_control_log(_("wrote merged hdr `%s'"), c+1); // import new image gchar *directory = g_path_get_dirname((const gchar *)pathname); dt_film_t film; const int filmid = dt_film_new(&film, directory); dt_image_import(filmid, pathname, TRUE); g_free (directory); free(pixels); free(weight); error: dt_control_backgroundjobs_destroy(darktable.control, jid); return 0; }
static void _init_f( float *out, uint32_t *width, uint32_t *height, const uint32_t imgid) { const uint32_t wd = *width, ht = *height; /* do not even try to process file if it isn't available */ char filename[2048] = {0}; gboolean from_cache = TRUE; dt_image_full_path(imgid, filename, sizeof(filename), &from_cache); if ( !*filename || !g_file_test(filename, G_FILE_TEST_EXISTS)) { *width = *height = 0; return; } dt_mipmap_buffer_t buf; dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, imgid, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING); // lock image after we have the buffer, we might need to lock the image struct for // writing during raw loading, to write to width/height. const dt_image_t *image = dt_image_cache_read_get(darktable.image_cache, imgid); dt_iop_roi_t roi_in, roi_out; roi_in.x = roi_in.y = 0; roi_in.width = image->width; roi_in.height = image->height; roi_in.scale = 1.0f; roi_out.x = roi_out.y = 0; roi_out.scale = fminf(wd/(float)image->width, ht/(float)image->height); roi_out.width = roi_out.scale * roi_in.width; roi_out.height = roi_out.scale * roi_in.height; if(!buf.buf) { dt_control_log(_("image `%s' is not available!"), image->filename); dt_image_cache_read_release(darktable.image_cache, image); *width = *height = 0; return; } assert(!buffer_is_broken(&buf)); if(image->filters) { // demosaic during downsample if(image->bpp == sizeof(float)) dt_iop_clip_and_zoom_demosaic_half_size_f( out, (const float *)buf.buf, &roi_out, &roi_in, roi_out.width, roi_in.width, dt_image_flipped_filter(image), 1.0f); else dt_iop_clip_and_zoom_demosaic_half_size( out, (const uint16_t *)buf.buf, &roi_out, &roi_in, roi_out.width, roi_in.width, dt_image_flipped_filter(image)); } else { // downsample dt_iop_clip_and_zoom(out, (const float *)buf.buf, &roi_out, &roi_in, roi_out.width, roi_in.width); } dt_image_cache_read_release(darktable.image_cache, image); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); *width = roi_out.width; *height = roi_out.height; }
// internal function: to avoid exif blob reading + 8-bit byteorder flag + high-quality override int dt_imageio_export_with_flags( const uint32_t imgid, const char *filename, dt_imageio_module_format_t *format, dt_imageio_module_data_t *format_params, const int32_t ignore_exif, const int32_t display_byteorder, const gboolean high_quality, const int32_t thumbnail_export, const char *filter) { dt_develop_t dev; dt_dev_init(&dev, 0); dt_mipmap_buffer_t buf; if(thumbnail_export && dt_conf_get_bool("plugins/lighttable/low_quality_thumbnails")) dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, imgid, DT_MIPMAP_F, DT_MIPMAP_BLOCKING); else dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, imgid, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING); dt_dev_load_image(&dev, imgid); const dt_image_t *img = &dev.image_storage; const int wd = img->width; const int ht = img->height; int res = 0; dt_times_t start; dt_get_times(&start); dt_dev_pixelpipe_t pipe; res = thumbnail_export ? dt_dev_pixelpipe_init_thumbnail(&pipe, wd, ht) : dt_dev_pixelpipe_init_export(&pipe, wd, ht, format->levels(format_params)); if(!res) { dt_control_log(_("failed to allocate memory for export, please lower the threads used for export or buy more memory.")); dt_dev_cleanup(&dev); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); return 1; } if(!buf.buf) { dt_control_log(_("image `%s' is not available!"), img->filename); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); dt_dev_cleanup(&dev); return 1; } // If a style is to be applied during export, add the iop params into the history if (!thumbnail_export && strlen(format_params->style) && strcmp(format_params->style,_("none"))) { GList *stls; GList *modules = dev.iop; dt_iop_module_t *m = NULL; if ((stls=dt_styles_get_item_list(format_params->style, TRUE)) == 0) { dt_control_log(_("cannot find the style '%s' to apply during export."), format_params->style); dt_dev_cleanup(&dev); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); return 1; } // Add each params while (stls) { dt_style_item_t *s = (dt_style_item_t *) stls->data; modules = dev.iop; while (modules) { m = (dt_iop_module_t *)modules->data; if (strcmp(m->op, s->name) == 0) { dt_dev_history_item_t *h = malloc(sizeof(dt_dev_history_item_t)); h->params = s->params; h->enabled = 1; h->module = m; h->multi_priority = 1; strcpy(h->multi_name, ""); h->blend_params = malloc(sizeof(dt_develop_blend_params_t)); memset(h->blend_params, 0, sizeof(dt_develop_blend_params_t)); dev.history_end++; dev.history = g_list_append(dev.history, h); break; } modules = g_list_next(modules); } stls = g_list_next(stls); } } dt_dev_pixelpipe_set_input(&pipe, &dev, (float *)buf.buf, buf.width, buf.height, 1.0); dt_dev_pixelpipe_create_nodes(&pipe, &dev); dt_dev_pixelpipe_synch_all(&pipe, &dev); dt_dev_pixelpipe_get_dimensions(&pipe, &dev, pipe.iwidth, pipe.iheight, &pipe.processed_width, &pipe.processed_height); if(filter) { if(!strncmp(filter, "pre:", 4)) dt_dev_pixelpipe_disable_after(&pipe, filter+4); if(!strncmp(filter, "post:", 5)) dt_dev_pixelpipe_disable_before(&pipe, filter+5); } dt_show_times(&start, "[export] creating pixelpipe", NULL); // find output color profile for this image: int sRGB = 1; gchar *overprofile = dt_conf_get_string("plugins/lighttable/export/iccprofile"); if(overprofile && !strcmp(overprofile, "sRGB")) { sRGB = 1; } else if(!overprofile || !strcmp(overprofile, "image")) { GList *modules = dev.iop; dt_iop_module_t *colorout = NULL; while (modules) { colorout = (dt_iop_module_t *)modules->data; if (strcmp(colorout->op, "colorout") == 0) { dt_iop_colorout_params_t *p = (dt_iop_colorout_params_t *)colorout->params; if(!strcmp(p->iccprofile, "sRGB")) sRGB = 1; else sRGB = 0; } modules = g_list_next(modules); } } else { sRGB = 0; } g_free(overprofile); // get only once at the beginning, in case the user changes it on the way: const gboolean high_quality_processing = ((format_params->max_width == 0 || format_params->max_width >= pipe.processed_width ) && (format_params->max_height == 0 || format_params->max_height >= pipe.processed_height)) ? FALSE : high_quality; const int width = high_quality_processing ? 0 : format_params->max_width; const int height = high_quality_processing ? 0 : format_params->max_height; const double scalex = width > 0 ? fminf(width /(double)pipe.processed_width, 1.0) : 1.0; const double scaley = height > 0 ? fminf(height/(double)pipe.processed_height, 1.0) : 1.0; const double scale = fminf(scalex, scaley); int processed_width = scale*pipe.processed_width + .5f; int processed_height = scale*pipe.processed_height + .5f; const int bpp = format->bpp(format_params); // downsampling done last, if high quality processing was requested: uint8_t *outbuf = pipe.backbuf; uint8_t *moutbuf = NULL; // keep track of alloc'ed memory if(high_quality_processing) { dt_dev_pixelpipe_process_no_gamma(&pipe, &dev, 0, 0, processed_width, processed_height, scale); const double scalex = format_params->max_width > 0 ? fminf(format_params->max_width /(double)pipe.processed_width, 1.0) : 1.0; const double scaley = format_params->max_height > 0 ? fminf(format_params->max_height/(double)pipe.processed_height, 1.0) : 1.0; const double scale = fminf(scalex, scaley); processed_width = scale*pipe.processed_width + .5f; processed_height = scale*pipe.processed_height + .5f; moutbuf = (uint8_t *)dt_alloc_align(64, sizeof(float)*processed_width*processed_height*4); outbuf = moutbuf; // now downscale into the new buffer: dt_iop_roi_t roi_in, roi_out; roi_in.x = roi_in.y = roi_out.x = roi_out.y = 0; roi_in.scale = 1.0; roi_out.scale = scale; roi_in.width = pipe.processed_width; roi_in.height = pipe.processed_height; roi_out.width = processed_width; roi_out.height = processed_height; dt_iop_clip_and_zoom((float *)outbuf, (float *)pipe.backbuf, &roi_out, &roi_in, processed_width, pipe.processed_width); } else { // do the processing (8-bit with special treatment, to make sure we can use openmp further down): if(bpp == 8) dt_dev_pixelpipe_process(&pipe, &dev, 0, 0, processed_width, processed_height, scale); else dt_dev_pixelpipe_process_no_gamma(&pipe, &dev, 0, 0, processed_width, processed_height, scale); outbuf = pipe.backbuf; } // downconversion to low-precision formats: if(bpp == 8 && !display_byteorder) { // ldr output: char if(high_quality_processing) { const float *const inbuf = (float *)outbuf; for(int k=0; k<processed_width*processed_height; k++) { // convert in place, this is unfortunately very serial.. const uint8_t r = CLAMP(inbuf[4*k+0]*0xff, 0, 0xff); const uint8_t g = CLAMP(inbuf[4*k+1]*0xff, 0, 0xff); const uint8_t b = CLAMP(inbuf[4*k+2]*0xff, 0, 0xff); outbuf[4*k+0] = r; outbuf[4*k+1] = g; outbuf[4*k+2] = b; } } else { uint8_t *const buf8 = pipe.backbuf; #ifdef _OPENMP #pragma omp parallel for default(none) shared(processed_width, processed_height) schedule(static) #endif // just flip byte order for(int k=0; k<processed_width*processed_height; k++) { uint8_t tmp = buf8[4*k+0]; buf8[4*k+0] = buf8[4*k+2]; buf8[4*k+2] = tmp; } } } else if(bpp == 16) { // uint16_t per color channel float *buff = (float *) outbuf; uint16_t *buf16 = (uint16_t *)outbuf; for(int y=0; y<processed_height; y++) for(int x=0; x<processed_width ; x++) { // convert in place const int k = x + processed_width*y; for(int i=0; i<3; i++) buf16[4*k+i] = CLAMP(buff[4*k+i]*0x10000, 0, 0xffff); } } // else output float, no further harm done to the pixels :) format_params->width = processed_width; format_params->height = processed_height; if(!ignore_exif) { int length; uint8_t exif_profile[65535]; // C++ alloc'ed buffer is uncool, so we waste some bits here. char pathname[1024]; dt_image_full_path(imgid, pathname, 1024); length = dt_exif_read_blob(exif_profile, pathname, imgid, sRGB, processed_width, processed_height); res = format->write_image (format_params, filename, outbuf, exif_profile, length, imgid); } else { res = format->write_image (format_params, filename, outbuf, NULL, 0, imgid); } dt_dev_pixelpipe_cleanup(&pipe); dt_dev_cleanup(&dev); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); free(moutbuf); return res; }
void dt_dev_process_image_job(dt_develop_t *dev) { dt_control_log_busy_enter(); // let gui know to draw preview instead of us, if it's there: dev->image_dirty = 1; dt_mipmap_buffer_t buf; dt_times_t start; dt_get_times(&start); dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, dev->image_storage.id, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING); dt_show_times(&start, "[dev]", "to load the image."); // copy over image now that width and height are sure to be correct: const dt_image_t *img = dt_image_cache_read_get(darktable.image_cache, dev->image_storage.id); dev->image_storage = *img; // but don't lock the real thing, as that would avoid any writers to change stuff. // (such as raw loading or star rating changes) dt_image_cache_read_release(darktable.image_cache, img); // failed to load raw? if(!buf.buf) return; dt_dev_pixelpipe_set_input(dev->pipe, dev, (float *)buf.buf, buf.width, buf.height, 1.0); if(dev->image_loading) { // init pixel pipeline dt_dev_pixelpipe_cleanup_nodes(dev->pipe); dt_dev_pixelpipe_create_nodes(dev->pipe, dev); if(dev->image_force_reload) dt_dev_pixelpipe_flush_caches(dev->pipe); dev->image_dirty = 1; dev->image_force_reload = 0; if(dev->gui_attached) { // during load, a mipf update could have been issued. dev->preview_input_changed = 1; dev->preview_dirty = 1; dev->gui_synch = 1; // notify gui thread we want to synch (call gui_update in the modules) dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH; } dev->pipe->changed |= DT_DEV_PIPE_SYNCH; } dt_dev_zoom_t zoom; float zoom_x, zoom_y, scale; int x, y; // adjust pipeline according to changed flag set by {add,pop}_history_item. restart: if(dev->gui_leaving) { dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); dt_control_log_busy_leave(); return; } dev->pipe->input_timestamp = dev->timestamp; // this locks dev->history_mutex. dt_dev_pixelpipe_change(dev->pipe, dev); // determine scale according to new dimensions DT_CTL_GET_GLOBAL(zoom, dev_zoom); DT_CTL_GET_GLOBAL(zoom_x, dev_zoom_x); DT_CTL_GET_GLOBAL(zoom_y, dev_zoom_y); scale = dt_dev_get_zoom_scale(dev, zoom, 1.0f, 0); dev->capwidth = MIN(MIN(dev->width, dev->pipe->processed_width *scale), darktable.thumbnail_width); dev->capheight = MIN(MIN(dev->height, dev->pipe->processed_height*scale), darktable.thumbnail_height); x = MAX(0, scale*dev->pipe->processed_width *(.5+zoom_x)-dev->capwidth/2); y = MAX(0, scale*dev->pipe->processed_height*(.5+zoom_y)-dev->capheight/2); dt_get_times(&start); if(dt_dev_pixelpipe_process(dev->pipe, dev, x, y, dev->capwidth, dev->capheight, scale)) { // interrupted because image changed? if(dev->image_force_reload) { dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); dt_control_log_busy_leave(); return; } // or because the pipeline changed? else goto restart; } dt_show_times(&start, "[dev_process_image] pixel pipeline processing", NULL); // maybe we got zoomed/panned in the meantime? if(dev->pipe->changed != DT_DEV_PIPE_UNCHANGED) goto restart; // cool, we got a new image! dev->image_dirty = 0; dev->image_loading = 0; dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); dt_control_queue_redraw_center(); dt_control_log_busy_leave(); }
void dt_dev_process_preview_job(dt_develop_t *dev) { dt_mipmap_buffer_t buf; if(dev->image_loading) { // raw is already loading, no use starting another file access, we wait. return; } dt_control_log_busy_enter(); dev->preview_pipe->input_timestamp = dev->timestamp; dev->preview_dirty = 1; // lock if there, issue a background load, if not (best-effort for mip f). dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, dev->image_storage.id, DT_MIPMAP_F, 0); if(!buf.buf) { dt_control_log_busy_leave(); return; // not loaded yet. load will issue a gtk redraw on completion, which in turn will trigger us again later. } // init pixel pipeline for preview. dt_dev_pixelpipe_set_input(dev->preview_pipe, dev, (float *)buf.buf, buf.width, buf.height, dev->image_storage.width/(float)buf.width); if(dev->preview_loading) { dt_dev_pixelpipe_cleanup_nodes(dev->preview_pipe); dt_dev_pixelpipe_create_nodes(dev->preview_pipe, dev); dt_dev_pixelpipe_flush_caches(dev->preview_pipe); dev->preview_loading = 0; } // if raw loaded, get new mipf if(dev->preview_input_changed) { dt_dev_pixelpipe_flush_caches(dev->preview_pipe); dev->preview_input_changed = 0; } // always process the whole downsampled mipf buffer, to allow for fast scrolling and mip4 write-through. restart: if(dev->gui_leaving) { dt_control_log_busy_leave(); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); return; } // adjust pipeline according to changed flag set by {add,pop}_history_item. // this locks dev->history_mutex. dt_times_t start; dt_get_times(&start); dt_dev_pixelpipe_change(dev->preview_pipe, dev); if(dt_dev_pixelpipe_process(dev->preview_pipe, dev, 0, 0, dev->preview_pipe->processed_width*dev->preview_downsampling, dev->preview_pipe->processed_height*dev->preview_downsampling, dev->preview_downsampling)) { if(dev->preview_loading || dev->preview_input_changed) { dt_control_log_busy_leave(); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); return; } else goto restart; } dt_show_times(&start, "[dev_process_preview] pixel pipeline processing", NULL); dev->preview_dirty = 0; dt_control_log_busy_leave(); dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); }
void dt_view_image_expose( dt_view_image_over_t *image_over, uint32_t imgid, cairo_t *cr, int32_t width, int32_t height, int32_t zoom, int32_t px, int32_t py) { const double start = dt_get_wtime(); // some performance tuning stuff, for your pleasure. // on my machine with 7 image per row it seems grouping has the largest // impact from around 400ms -> 55ms per redraw. #define DRAW_THUMB 1 #define DRAW_COLORLABELS 1 #define DRAW_GROUPING 1 #define DRAW_SELECTED 1 #define DRAW_HISTORY 1 #if DRAW_THUMB == 1 // this function is not thread-safe (gui-thread only), so we // can safely allocate this leaking bit of memory to decompress thumbnails: static int first_time = 1; static uint8_t *scratchmem = NULL; if(first_time) { // scratchmem might still be NULL after this, if compression is off. scratchmem = dt_mipmap_cache_alloc_scratchmem(darktable.mipmap_cache); first_time = 0; } #endif cairo_save (cr); float bgcol = 0.4, fontcol = 0.425, bordercol = 0.1, outlinecol = 0.2; int selected = 0, altered = 0, imgsel = -1, is_grouped = 0; // this is a gui thread only thing. no mutex required: imgsel = darktable.control->global_settings.lib_image_mouse_over_id; #if DRAW_SELECTED == 1 /* clear and reset statements */ DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.is_selected); DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.is_selected); /* bind imgid to prepared statments */ DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.is_selected, 1, imgid); /* lets check if imgid is selected */ if(sqlite3_step(darktable.view_manager->statements.is_selected) == SQLITE_ROW) selected = 1; #endif #if DRAW_HISTORY == 1 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.have_history); DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.have_history); DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.have_history, 1, imgid); /* lets check if imgid has history */ if(sqlite3_step(darktable.view_manager->statements.have_history) == SQLITE_ROW) altered = 1; #endif const dt_image_t *img = dt_image_cache_read_testget(darktable.image_cache, imgid); #if DRAW_GROUPING == 1 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.get_grouped); DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.get_grouped); DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_grouped, 1, imgid); DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_grouped, 2, imgid); /* lets check if imgid is in a group */ if(sqlite3_step(darktable.view_manager->statements.get_grouped) == SQLITE_ROW) is_grouped = 1; else if(img && darktable.gui->expanded_group_id == img->group_id) darktable.gui->expanded_group_id = -1; #endif if(selected == 1) { outlinecol = 0.4; bgcol = 0.6; fontcol = 0.5; } if(imgsel == imgid) { bgcol = 0.8; // mouse over fontcol = 0.7; outlinecol = 0.6; // if the user points at this image, we really want it: if(!img) img = dt_image_cache_read_get(darktable.image_cache, imgid); } float imgwd = 0.90f; if(zoom == 1) { imgwd = .97f; // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); } else { double x0 = 1, y0 = 1, rect_width = width-2, rect_height = height-2, radius = 5; double x1, y1, off, off1; x1=x0+rect_width; y1=y0+rect_height; off=radius*0.666; off1 = radius-off; cairo_move_to (cr, x0, y0 + radius); cairo_curve_to (cr, x0, y0+off1, x0+off1 , y0, x0 + radius, y0); cairo_line_to (cr, x1 - radius, y0); cairo_curve_to (cr, x1-off1, y0, x1, y0+off1, x1, y0 + radius); cairo_line_to (cr, x1 , y1 - radius); cairo_curve_to (cr, x1, y1-off1, x1-off1, y1, x1 - radius, y1); cairo_line_to (cr, x0 + radius, y1); cairo_curve_to (cr, x0+off1, y1, x0, y1-off1, x0, y1- radius); cairo_close_path (cr); cairo_set_source_rgb(cr, bgcol, bgcol, bgcol); cairo_fill_preserve(cr); cairo_set_line_width(cr, 0.005*width); cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol); cairo_stroke(cr); if(img) { const char *ext = img->filename + strlen(img->filename); while(ext > img->filename && *ext != '.') ext--; ext++; cairo_set_source_rgb(cr, fontcol, fontcol, fontcol); cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size (cr, .25*width); cairo_text_extents_t text_extends; cairo_text_extents (cr, ext, &text_extends); cairo_move_to (cr, .025*width - text_extends.x_bearing, .24*height); cairo_show_text (cr, ext); } } dt_mipmap_buffer_t buf; dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size( darktable.mipmap_cache, imgwd*width, imgwd*height); dt_mipmap_cache_read_get( darktable.mipmap_cache, &buf, imgid, mip, 0); #if DRAW_THUMB == 1 float scale = 1.0; // decompress image, if necessary. if compression is off, scratchmem will be == NULL, // so get the real pointer back: uint8_t *buf_decompressed = dt_mipmap_cache_decompress(&buf, scratchmem); cairo_surface_t *surface = NULL; if(buf.buf) { const int32_t stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, buf.width); surface = cairo_image_surface_create_for_data (buf_decompressed, CAIRO_FORMAT_RGB24, buf.width, buf.height, stride); if(zoom == 1) { scale = fminf( fminf(darktable.thumbnail_width, width) / (float)buf.width, fminf(darktable.thumbnail_height, height) / (float)buf.height ); } else scale = fminf(width*imgwd/(float)buf.width, height*imgwd/(float)buf.height); } // draw centered and fitted: cairo_save(cr); cairo_translate(cr, width/2.0, height/2.0f); cairo_scale(cr, scale, scale); if(buf.buf) { cairo_translate(cr, -.5f*buf.width, -.5f*buf.height); cairo_set_source_surface (cr, surface, 0, 0); // set filter no nearest: // in skull mode, we want to see big pixels. // in 1 iir mode for the right mip, we want to see exactly what the pipe gave us, 1:1 pixel for pixel. // in between, filtering just makes stuff go unsharp. if((buf.width <= 8 && buf.height <= 8) || fabsf(scale - 1.0f) < 0.01f) cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); cairo_rectangle(cr, 0, 0, buf.width, buf.height); cairo_fill(cr); cairo_surface_destroy (surface); cairo_rectangle(cr, 0, 0, buf.width, buf.height); } // border around image const float border = zoom == 1 ? 16/scale : 2/scale; cairo_set_source_rgb(cr, bordercol, bordercol, bordercol); if(buf.buf && selected) { cairo_set_line_width(cr, 1./scale); if(zoom == 1) { // draw shadow around border cairo_set_source_rgb(cr, 0.2, 0.2, 0.2); cairo_stroke(cr); // cairo_new_path(cr); cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); float alpha = 1.0f; for(int k=0; k<16; k++) { cairo_rectangle(cr, 0, 0, buf.width, buf.height); cairo_new_sub_path(cr); cairo_rectangle(cr, -k/scale, -k/scale, buf.width+2.*k/scale, buf.height+2.*k/scale); cairo_set_source_rgba(cr, 0, 0, 0, alpha); alpha *= 0.6f; cairo_fill(cr); } } else { cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); cairo_new_sub_path(cr); cairo_rectangle(cr, -border, -border, buf.width+2.*border, buf.height+2.*border); cairo_stroke_preserve(cr); cairo_set_source_rgb(cr, 1.0-bordercol, 1.0-bordercol, 1.0-bordercol); cairo_fill(cr); } } else if(buf.buf) { cairo_set_line_width(cr, 1); cairo_stroke(cr); } cairo_restore(cr); #endif if(buf.buf) dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf); const float fscale = fminf(width, height); if(imgsel == imgid) { // draw mouseover hover effects, set event hook for mouse button down! *image_over = DT_VIEW_DESERT; cairo_set_line_width(cr, 1.5); cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol); cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); float r1, r2; if(zoom != 1) { r1 = 0.05*width; r2 = 0.022*width; } else { r1 = 0.015*fscale; r2 = 0.007*fscale; } float x, y; if(zoom != 1) y = 0.90*height; else y = .12*fscale; gboolean image_is_rejected = (img && ((img->flags & 0x7) == 6)); if(img) for(int k=0; k<5; k++) { if(zoom != 1) x = (0.41+k*0.12)*width; else x = (.08+k*0.04)*fscale; if(!image_is_rejected) //if rejected: draw no stars { dt_view_star(cr, x, y, r1, r2); if((px - x)*(px - x) + (py - y)*(py - y) < r1*r1) { *image_over = DT_VIEW_STAR_1 + k; cairo_fill(cr); } else if((img->flags & 0x7) > k) { cairo_fill_preserve(cr); cairo_set_source_rgb(cr, 1.0-bordercol, 1.0-bordercol, 1.0-bordercol); cairo_stroke(cr); cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol); } else cairo_stroke(cr); } } //Image rejected? if(zoom !=1) x = 0.11*width; else x = .04*fscale; if (image_is_rejected) cairo_set_source_rgb(cr, 1., 0., 0.); if((px - x)*(px - x) + (py - y)*(py - y) < r1*r1) { *image_over = DT_VIEW_REJECT; //mouse sensitive cairo_new_sub_path(cr); cairo_arc(cr, x, y, (r1+r2)*.5, 0, 2.0f*M_PI); cairo_stroke(cr); } if (image_is_rejected) cairo_set_line_width(cr, 2.5); //reject cross: cairo_move_to(cr, x-r2, y-r2); cairo_line_to(cr, x+r2, y+r2); cairo_move_to(cr, x+r2, y-r2); cairo_line_to(cr, x-r2, y+r2); cairo_close_path(cr); cairo_stroke(cr); cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol); cairo_set_line_width(cr, 1.5); // image part of a group? if(is_grouped && darktable.gui && darktable.gui->grouping) { // draw grouping icon and border if the current group is expanded // align to the right, left of altered float s = (r1+r2)*.75; float _x, _y; if(zoom != 1) { _x = width*0.9 - s*2.5; _y = height*0.1 - s*.4; } else { _x = (.04+7*0.04-1.1*.04)*fscale; _y = y - (.17*.04)*fscale; } cairo_save(cr); if(img && (imgid != img->group_id)) cairo_set_source_rgb(cr, fontcol, fontcol, fontcol); dtgtk_cairo_paint_grouping(cr, _x, _y, s, s, 23); cairo_restore(cr); // mouse is over the grouping icon if(img && abs(px-_x-.5*s) <= .8*s && abs(py-_y-.5*s) <= .8*s) *image_over = DT_VIEW_GROUP; } // image altered? if(altered) { // align to right float s = (r1+r2)*.5; if(zoom != 1) { x = width*0.9; y = height*0.1; } else x = (.04+7*0.04)*fscale; dt_view_draw_altered(cr, x, y, s); //g_print("px = %d, x = %.4f, py = %d, y = %.4f\n", px, x, py, y); if(img && abs(px-x) <= 1.2*s && abs(py-y) <= 1.2*s) // mouse hovers over the altered-icon -> history tooltip! { darktable.gui->center_tooltip = 1; } } } // kill all paths, in case img was not loaded yet, or is blocked: cairo_new_path(cr); #if DRAW_COLORLABELS == 1 // TODO: make mouse sensitive, just as stars! // TODO: cache in image struct! { // color labels: const float x = zoom == 1 ? (0.07)*fscale : .21*width; const float y = zoom == 1 ? 0.17*fscale: 0.1*height; const float r = zoom == 1 ? 0.01*fscale : 0.03*width; /* clear and reset prepared statement */ DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.get_color); DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.get_color); /* setup statement and iterate rows */ DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_color, 1, imgid); while(sqlite3_step(darktable.view_manager->statements.get_color) == SQLITE_ROW) { cairo_save(cr); int col = sqlite3_column_int(darktable.view_manager->statements.get_color, 0); // see src/dtgtk/paint.c dtgtk_cairo_paint_label(cr, x+(3*r*col)-5*r, y-r, r*2, r*2, col); cairo_restore(cr); } } #endif if(img && (zoom == 1)) { // some exif data cairo_set_source_rgb(cr, .7, .7, .7); cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size (cr, .025*fscale); cairo_move_to (cr, .02*fscale, .04*fscale); // cairo_show_text(cr, img->filename); cairo_text_path(cr, img->filename); char exifline[50]; cairo_move_to (cr, .02*fscale, .08*fscale); dt_image_print_exif(img, exifline, 50); cairo_text_path(cr, exifline); cairo_fill_preserve(cr); cairo_set_line_width(cr, 1.0); cairo_set_source_rgb(cr, 0.3, 0.3, 0.3); cairo_stroke(cr); } if(img) dt_image_cache_read_release(darktable.image_cache, img); cairo_restore(cr); // if(zoom == 1) cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT); const double end = dt_get_wtime(); dt_print(DT_DEBUG_PERF, "[lighttable] image expose took %0.04f sec\n", end-start); }