static void _get_image_dimension (int32_t imgid, int32_t *iwidth, int32_t *iheight) { dt_develop_t dev; dt_mipmap_buffer_t buf; dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING, 'r'); dt_dev_init(&dev, 0); dt_dev_load_image(&dev, imgid); const dt_image_t *img = &dev.image_storage; dt_dev_pixelpipe_t pipe; int wd = img->width, ht = img->height; int res = dt_dev_pixelpipe_init_dummy(&pipe, wd, ht); if(res) { // set mem pointer to 0, won't be used. dt_dev_pixelpipe_set_input(&pipe, &dev, (float *)buf.buf, wd, ht, 1.0f); 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); wd = pipe.processed_width; ht = pipe.processed_height; dt_dev_pixelpipe_cleanup(&pipe); } dt_dev_cleanup(&dev); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); *iwidth = wd; *iheight = ht; }
static void _lib_filmstrip_dnd_begin_callback(GtkWidget *widget, GdkDragContext *context, gpointer user_data) { const int ts = DT_PIXEL_APPLY_DPI(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 main.selected_images WHERE imgid=?1 LIMIT 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_get(darktable.mipmap_cache, &buf, imgid, mip, DT_MIPMAP_BLOCKING, 'r'); if(buf.buf) { for(size_t i = 3; i < (size_t)4 * buf.width * buf.height; i += 4) buf.buf[i] = UINT8_MAX; 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(buf.buf, GDK_COLORSPACE_RGB, TRUE, 8, buf.width, buf.height, buf.width * 4, NULL, NULL); GdkPixbuf *scaled = gdk_pixbuf_scale_simple(source, w, h, GDK_INTERP_HYPER); gtk_drag_set_icon_pixbuf(context, scaled, 0, h); if(source) g_object_unref(source); if(scaled) g_object_unref(scaled); } dt_mipmap_cache_release(darktable.mipmap_cache, &buf); } }
void gui_post_expose(dt_lib_module_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx, int32_t pointery) { dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)self->data; if (d->imgid == 0) return; const int32_t tb = DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size")); int nw = width-2*tb; int nh = height-2*tb; dt_mipmap_buffer_t buf; dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, nw, nh); dt_mipmap_cache_get(darktable.mipmap_cache, &buf, d->imgid, mip, DT_MIPMAP_BEST_EFFORT, 'r'); int img_wd = buf.width; int img_ht = buf.height; dt_mipmap_cache_release(darktable.mipmap_cache, &buf); // and now we get the values to "fit the screen" int px,py,nimgw,nimgh; if (img_ht*nw > img_wd*nh) { nimgh = nh; nimgw = img_wd*nh/img_ht; py=0; px=(nw-nimgw)/2; } else { nimgw = nw; nimgh = img_ht*nw/img_wd; px=0; py=(nh-nimgh)/2; } // we erase everything cairo_set_source_rgb(cri, .2, .2, .2); cairo_paint(cri); //we draw the cached image dt_view_image_over_t image_over = DT_VIEW_DESERT; dt_view_image_expose(&image_over, d->imgid, cri, nw, nh, 1, px+tb, py+tb, TRUE, TRUE); //and the nice border line cairo_rectangle(cri, tb+px, tb+py, nimgw, nimgh); cairo_set_line_width(cri, 1.0); cairo_set_source_rgb(cri, .3, .3, .3); cairo_stroke(cri); }
static void _set_orientation(dt_print_t *prt) { if (prt->image_id <= 0) return; dt_mipmap_buffer_t buf; dt_mipmap_cache_get(darktable.mipmap_cache, &buf, prt->image_id, DT_MIPMAP_3, DT_MIPMAP_BEST_EFFORT, 'r'); if (buf.width > buf.height) prt->pinfo->page.landscape = TRUE; else prt->pinfo->page.landscape = FALSE; dt_mipmap_cache_release(darktable.mipmap_cache, &buf); }
static void deflicker_prepare_histogram(dt_iop_module_t *self, uint32_t **histogram, dt_dev_histogram_stats_t *histogram_stats) { dt_mipmap_buffer_t buf; dt_mipmap_cache_get(darktable.mipmap_cache, &buf, self->dev->image_storage.id, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING, 'r'); const dt_image_t *img = dt_image_cache_get(darktable.image_cache, self->dev->image_storage.id, 'r'); dt_image_t image = *img; dt_image_cache_read_release(darktable.image_cache, img); if(!buf.buf) { dt_control_log(_("failed to get raw buffer from image `%s'"), image.filename); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); return; } dt_dev_histogram_collection_params_t histogram_params = { 0 }; dt_histogram_roi_t histogram_roi = {.width = image.width, .height = image.height, // FIXME: get those from rawprepare IOP somehow !!! .crop_x = image.crop_x, .crop_y = image.crop_y, .crop_width = image.crop_width, .crop_height = image.crop_height }; histogram_params.roi = &histogram_roi; histogram_params.bins_count = 16384; dt_histogram_worker(&histogram_params, histogram_stats, buf.buf, histogram, dt_histogram_helper_cs_RAW_uint16); histogram_stats->ch = 1u; dt_mipmap_cache_release(darktable.mipmap_cache, &buf); } /* input: 0 - 16384 (valid range: from black level to white level) */ /* output: -14 ... 0 */ static float raw_to_ev(uint32_t raw, uint32_t black_level, uint32_t white_level) { uint32_t raw_max = white_level - black_level; float raw_ev = -log2f(raw_max) + log2f(CLAMP(raw, 0.0f, 16384.0f)); return raw_ev; }
static int generate_thumbnail_cache(const dt_mipmap_size_t max_mip) { fprintf(stderr, _("creating cache directories\n")); for(dt_mipmap_size_t k = DT_MIPMAP_0; k <= max_mip; k++) { char dirname[PATH_MAX] = { 0 }; snprintf(dirname, sizeof(dirname), "%s.d/%d", darktable.mipmap_cache->cachedir, k); fprintf(stderr, _("creating cache directory '%s'\n"), dirname); if(g_mkdir_with_parents(dirname, 0750)) { fprintf(stderr, _("could not create directory '%s'!\n"), dirname); return 1; } } // some progress counter sqlite3_stmt *stmt; size_t image_count = 0, counter = 0; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select count(id) from images", -1, &stmt, 0); if(sqlite3_step(stmt) == SQLITE_ROW) { image_count = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); } else { return 1; } // go through all images: DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select id from images", -1, &stmt, 0); while(sqlite3_step(stmt) == SQLITE_ROW) { const uint32_t imgid = sqlite3_column_int(stmt, 0); for(int k = max_mip; k >= DT_MIPMAP_0; k--) { char filename[PATH_MAX] = { 0 }; snprintf(filename, sizeof(filename), "%s.d/%d/%d.jpg", darktable.mipmap_cache->cachedir, k, imgid); // if the thumbnail is already on disc - do nothing if(!access(filename, R_OK)) continue; // else, generate thumbnail and store in mipmap cache. dt_mipmap_buffer_t buf; dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, k, DT_MIPMAP_BLOCKING, 'r'); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); } // and immediately write thumbs to disc and remove from mipmap cache. dt_mimap_cache_evict(darktable.mipmap_cache, imgid); counter++; fprintf(stderr, "image %zu/%zu (%.02f%%)\n", counter, image_count, 100.0 * counter / (float)image_count); } sqlite3_finalize(stmt); fprintf(stderr, "done\n"); 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, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_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); g_free(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, sizeof(ext), "*.%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); GtkWidget *preview = gtk_image_new(); gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(filechooser), preview); g_signal_connect(filechooser, "update-preview", G_CALLBACK(_lib_import_update_preview), preview); dt_lib_import_metadata_t metadata; gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(filechooser), _lib_import_get_extra_widget(&metadata, FALSE)); 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))); _lib_import_evaluate_extra_widget(&metadata, FALSE); 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; /* reset filter so that view isn't empty */ dt_view_filter_reset(darktable.view_manager, TRUE); 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_get(darktable.mipmap_cache, &buf, id, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING, 'r'); gboolean loaded = (buf.buf != NULL); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); if(!loaded) { dt_control_log(_("file has unknown format!")); } else { dt_control_set_mouse_over_id(id); dt_ctl_switch_mode_to(DT_DEVELOP); } } } gtk_widget_destroy(metadata.frame); gtk_widget_destroy(filechooser); gtk_widget_queue_draw(dt_ui_center(darktable.gui->ui)); }
// 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 gboolean upscale, const int32_t thumbnail_export, const char *filter, const gboolean copy_metadata, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent, dt_imageio_module_storage_t *storage, dt_imageio_module_data_t *storage_params, int num, int total) { dt_develop_t dev; dt_dev_init(&dev, 0); dt_dev_load_image(&dev, imgid); const int buf_is_downscaled = (thumbnail_export && dt_conf_get_bool("plugins/lighttable/low_quality_thumbnails")); dt_mipmap_buffer_t buf; if(buf_is_downscaled) dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, DT_MIPMAP_F, DT_MIPMAP_BLOCKING, 'r'); else dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING, 'r'); const dt_image_t *img = &dev.image_storage; if(!buf.buf || !buf.width || !buf.height) { fprintf(stderr, "allocation failed???\n"); dt_control_log(_("image `%s' is not available!"), img->filename); goto error_early; } const int wd = img->width; const int ht = img->height; const float max_scale = upscale ? 100.0 : 1.0; 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 %s, please lower the threads used for export or buy more memory."), thumbnail_export ? C_("noun", "thumbnail export") : C_("noun", "export")); goto error; } // If a style is to be applied during export, add the iop params into the history if(!thumbnail_export && format_params->style[0] != '\0') { GList *style_items = dt_styles_get_item_list(format_params->style, TRUE, -1); if(!style_items) { dt_control_log(_("cannot find the style '%s' to apply during export."), format_params->style); goto error; } // remove everything above history_end GList *history = g_list_nth(dev.history, dev.history_end); while(history) { GList *next = g_list_next(history); dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data); free(hist->params); free(hist->blend_params); free(history->data); dev.history = g_list_delete_link(dev.history, history); history = next; } // Add each params for(GList *iter = style_items; iter; iter = g_list_next(iter)) { dt_style_item_t *s = (dt_style_item_t *)iter->data; for(GList *module = dev.iop; module; module = g_list_next(module)) { dt_iop_module_t *m = (dt_iop_module_t *)module->data; if(!strcmp(m->op, s->operation)) { dt_dev_history_item_t *h = malloc(sizeof(dt_dev_history_item_t)); dt_iop_module_t *style_module = m; if((format_params->style_append && !(m->flags() & IOP_FLAGS_ONE_INSTANCE)) || m->multi_priority != s->multi_priority) { // dt_dev_module_duplicate() doesn't work here, it's trying too hard to be clever style_module = (dt_iop_module_t *)calloc(1, sizeof(dt_iop_module_t)); if(style_module && !dt_iop_load_module(style_module, m->so, m->dev)) { style_module->instance = m->instance; style_module->multi_priority = s->multi_priority; snprintf(style_module->multi_name, sizeof(style_module->multi_name), "%s", s->name); dev.iop = g_list_insert_sorted(dev.iop, style_module, sort_plugins); } else { free(h); goto error; } } h->params = s->params; h->blend_params = s->blendop_params; h->enabled = s->enabled; h->module = style_module; h->multi_priority = s->multi_priority; g_strlcpy(h->multi_name, s->name, sizeof(h->multi_name)); if(m->legacy_params && (s->module_version != m->version())) { void *new_params = malloc(m->params_size); m->legacy_params(m, h->params, s->module_version, new_params, labs(m->version())); free(h->params); h->params = new_params; } dev.history_end++; dev.history = g_list_append(dev.history, h); // make sure that dt_style_item_free doesn't free data we still use s->params = NULL; s->blendop_params = NULL; break; } } } g_list_free_full(style_items, dt_style_item_free); } dt_dev_pixelpipe_set_icc(&pipe, icc_type, icc_filename, icc_intent); dt_dev_pixelpipe_set_input(&pipe, &dev, (float *)buf.buf, buf.width, buf.height, buf.iscale); dt_dev_pixelpipe_create_nodes(&pipe, &dev); dt_dev_pixelpipe_synch_all(&pipe, &dev); 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_dev_pixelpipe_get_dimensions(&pipe, &dev, pipe.iwidth, pipe.iheight, &pipe.processed_width, &pipe.processed_height); dt_show_times(&start, "[export] creating pixelpipe", NULL); // find output color profile for this image: int sRGB = 1; if(icc_type == DT_COLORSPACE_SRGB) { sRGB = 1; } else if(icc_type == DT_COLORSPACE_NONE) { GList *modules = dev.iop; dt_iop_module_t *colorout = NULL; while(modules) { colorout = (dt_iop_module_t *)modules->data; if(colorout->get_p && strcmp(colorout->op, "colorout") == 0) { const dt_colorspaces_color_profile_type_t *type = colorout->get_p(colorout->params, "type"); sRGB = (!type || *type == DT_COLORSPACE_SRGB); break; // colorout can't have > 1 instance } modules = g_list_next(modules); } } else { sRGB = 0; } // 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 = format_params->max_width; const int height = format_params->max_height; const double scalex = width > 0 ? fminf(width / (double)pipe.processed_width, max_scale) : 1.0; const double scaley = height > 0 ? fminf(height / (double)pipe.processed_height, max_scale) : 1.0; const double scale = fminf(scalex, scaley); const int processed_width = scale * pipe.processed_width + .5f; const int processed_height = scale * pipe.processed_height + .5f; const int bpp = format->bpp(format_params); dt_get_times(&start); if(high_quality_processing) { /* * if high quality processing was requested, downsampling will be done * at the very end of the pipe (just before border and watermark) */ dt_dev_pixelpipe_process_no_gamma(&pipe, &dev, 0, 0, processed_width, processed_height, scale); } else { // else, downsampling will be right after demosaic // so we need to turn temporarily disable in-pipe late downsampling iop. // find the finalscale module dt_dev_pixelpipe_iop_t *finalscale = NULL; { GList *nodes = g_list_last(pipe.nodes); while(nodes) { dt_dev_pixelpipe_iop_t *node = (dt_dev_pixelpipe_iop_t *)(nodes->data); if(!strcmp(node->module->op, "finalscale")) { finalscale = node; break; } nodes = g_list_previous(nodes); } } if(finalscale) finalscale->enabled = 0; // 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); if(finalscale) finalscale->enabled = 1; } dt_show_times(&start, thumbnail_export ? "[dev_process_thumbnail] pixel pipeline processing" : "[dev_process_export] pixel pipeline processing", NULL); uint8_t *outbuf = pipe.backbuf; // downconversion to low-precision formats: if(bpp == 8) { if(display_byteorder) { if(high_quality_processing) { const float *const inbuf = (float *)outbuf; for(size_t k = 0; k < (size_t)processed_width * processed_height; k++) { // convert in place, this is unfortunately very serial.. const uint8_t r = CLAMP(inbuf[4 * k + 2] * 0xff, 0, 0xff); const uint8_t g = CLAMP(inbuf[4 * k + 1] * 0xff, 0, 0xff); const uint8_t b = CLAMP(inbuf[4 * k + 0] * 0xff, 0, 0xff); outbuf[4 * k + 0] = r; outbuf[4 * k + 1] = g; outbuf[4 * k + 2] = b; } } // else processing output was 8-bit already, and no need to swap order } else // need to flip { // ldr output: char if(high_quality_processing) { const float *const inbuf = (float *)outbuf; for(size_t k = 0; k < (size_t)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 { // !display_byteorder, need to swap: uint8_t *const buf8 = pipe.backbuf; #ifdef _OPENMP #pragma omp parallel for default(none) schedule(static) #endif // just flip byte order for(size_t k = 0; k < (size_t)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 size_t k = (size_t)processed_width * y + x; 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 = NULL; // Exif data should be 65536 bytes max, but if original size is close to that, // adding new tags could make it go over that... so let it be and see what // happens when we write the image char pathname[PATH_MAX] = { 0 }; gboolean from_cache = TRUE; dt_image_full_path(imgid, pathname, sizeof(pathname), &from_cache); // last param is dng mode, it's false here length = dt_exif_read_blob(&exif_profile, pathname, imgid, sRGB, processed_width, processed_height, 0); res = format->write_image(format_params, filename, outbuf, icc_type, icc_filename, exif_profile, length, imgid, num, total); free(exif_profile); } else { res = format->write_image(format_params, filename, outbuf, icc_type, icc_filename, NULL, 0, imgid, num, total); } dt_dev_pixelpipe_cleanup(&pipe); dt_dev_cleanup(&dev); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); /* now write xmp into that container, if possible */ if(copy_metadata && (format->flags(format_params) & FORMAT_FLAGS_SUPPORT_XMP)) { dt_exif_xmp_attach(imgid, filename); // no need to cancel the export if this fail } if(!thumbnail_export && strcmp(format->mime(format_params), "memory") && !(format->flags(format_params) & FORMAT_FLAGS_NO_TMPFILE)) { #ifdef USE_LUA //Synchronous calling of lua intermediate-export-image events dt_lua_lock(); lua_State *L = darktable.lua_state.state; luaA_push(L, dt_lua_image_t, &imgid); lua_pushstring(L, filename); luaA_push_type(L, format->parameter_lua_type, format_params); if (storage) luaA_push_type(L, storage->parameter_lua_type, storage_params); else lua_pushnil(L); dt_lua_event_trigger(L, "intermediate-export-image", 4); dt_lua_unlock(); #endif dt_control_signal_raise(darktable.signals, DT_SIGNAL_IMAGE_EXPORT_TMPFILE, imgid, filename, format, format_params, storage, storage_params); } return res; error: dt_dev_pixelpipe_cleanup(&pipe); error_early: dt_dev_cleanup(&dev); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); return 1; }
int dt_load_from_string(const gchar *input, gboolean open_image_in_dr, gboolean *single_image) { int id = 0; if(input == NULL || input[0] == '\0') return 0; char *filename = dt_util_normalize_path(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("lighttable"); } else { dt_control_log(_("error loading directory `%s'"), filename); } if(single_image) *single_image = FALSE; } 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_get(darktable.mipmap_cache, &buf, id, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING, 'r'); gboolean loaded = (buf.buf != NULL); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); if(!loaded) { id = 0; dt_control_log(_("file `%s' has unknown format!"), filename); } else { if(open_image_in_dr) { dt_control_set_mouse_over_id(id); dt_ctl_switch_mode_to("darkroom"); } } } else { dt_control_log(_("error loading file `%s'"), filename); } if(single_image) *single_image = TRUE; } g_free(filename); return id; }
/* This file is part of darktable, copyright (c) 2009--2011 johannes hanika. copyright (c) 2012 tobias ellinghaus. copyright (c) 2014 henrik andersson. darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. darktable is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with darktable. If not, see <http://www.gnu.org/licenses/>. */ #include "bauhaus/bauhaus.h" #include "common/camera_control.h" #include "common/darktable.h" #include "common/image_cache.h" #include "common/mipmap_cache.h" #include "control/conf.h" #include "control/control.h" #include "control/jobs.h" #include "dtgtk/button.h" #include "gui/accelerators.h" #include "gui/gtk.h" #include "gui/guides.h" #include "libs/lib.h" #include "libs/lib_api.h" #include <gdk/gdkkeysyms.h> typedef enum dt_lib_live_view_flip_t { FLAG_FLIP_NONE = 0, FLAG_FLIP_HORIZONTAL = 1<<0, FLAG_FLIP_VERTICAL = 1<<1, FLAG_FLIP_BOTH = FLAG_FLIP_HORIZONTAL|FLAG_FLIP_VERTICAL } dt_lib_live_view_flip_t; typedef enum dt_lib_live_view_overlay_t { OVERLAY_NONE = 0, OVERLAY_SELECTED, OVERLAY_ID } dt_lib_live_view_overlay_t; #define HANDLE_SIZE 0.02 static const cairo_operator_t _overlay_modes[] = { CAIRO_OPERATOR_OVER, CAIRO_OPERATOR_XOR, CAIRO_OPERATOR_ADD, CAIRO_OPERATOR_SATURATE #if(CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 10, 0)) , CAIRO_OPERATOR_MULTIPLY, CAIRO_OPERATOR_SCREEN, CAIRO_OPERATOR_OVERLAY, CAIRO_OPERATOR_DARKEN, CAIRO_OPERATOR_LIGHTEN, CAIRO_OPERATOR_COLOR_DODGE, CAIRO_OPERATOR_COLOR_BURN, CAIRO_OPERATOR_HARD_LIGHT, CAIRO_OPERATOR_SOFT_LIGHT, CAIRO_OPERATOR_DIFFERENCE, CAIRO_OPERATOR_EXCLUSION, CAIRO_OPERATOR_HSL_HUE, CAIRO_OPERATOR_HSL_SATURATION, CAIRO_OPERATOR_HSL_COLOR, CAIRO_OPERATOR_HSL_LUMINOSITY #endif }; DT_MODULE(1) typedef struct dt_lib_live_view_t { int imgid; int splitline_rotation; double overlay_x0, overlay_x1, overlay_y0, overlay_y1; double splitline_x, splitline_y; // 0..1 gboolean splitline_dragging; GtkWidget *live_view, *live_view_zoom, *rotate_ccw, *rotate_cw, *flip; GtkWidget *focus_out_small, *focus_out_big, *focus_in_small, *focus_in_big; GtkWidget *guide_selector, *flip_guides, *guides_widgets; GList *guides_widgets_list; GtkWidget *overlay, *overlay_id_box, *overlay_id, *overlay_mode, *overlay_splitline; } dt_lib_live_view_t; static void guides_presets_set_visibility(dt_lib_live_view_t *lib, int which) { if(which == 0) { gtk_widget_set_no_show_all(lib->guides_widgets, TRUE); gtk_widget_hide(lib->guides_widgets); gtk_widget_set_no_show_all(lib->flip_guides, TRUE); gtk_widget_hide(lib->flip_guides); } else { GtkWidget *widget = g_list_nth_data(lib->guides_widgets_list, which - 1); if(widget) { gtk_widget_set_no_show_all(lib->guides_widgets, FALSE); gtk_widget_show_all(lib->guides_widgets); gtk_stack_set_visible_child(GTK_STACK(lib->guides_widgets), widget); } else { gtk_widget_set_no_show_all(lib->guides_widgets, TRUE); gtk_widget_hide(lib->guides_widgets); } gtk_widget_set_no_show_all(lib->flip_guides, FALSE); gtk_widget_show_all(lib->flip_guides); } // TODO: add a support_flip flag to guides to hide the flip gui? } static void guides_presets_changed(GtkWidget *combo, dt_lib_live_view_t *lib) { int which = dt_bauhaus_combobox_get(combo); guides_presets_set_visibility(lib, which); } static void overlay_changed(GtkWidget *combo, dt_lib_live_view_t *lib) { int which = dt_bauhaus_combobox_get(combo); if(which == OVERLAY_NONE) { gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), FALSE); gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), FALSE); } else { gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), TRUE); gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), TRUE); } if(which == OVERLAY_ID) gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), TRUE); else gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), FALSE); } const char *name(dt_lib_module_t *self) { return _("live view"); } const char **views(dt_lib_module_t *self) { static const char *v[] = {"tethering", NULL}; return v; } uint32_t container(dt_lib_module_t *self) { return DT_UI_CONTAINER_PANEL_RIGHT_CENTER; } void gui_reset(dt_lib_module_t *self) { } int position() { return 998; } void init_key_accels(dt_lib_module_t *self) { dt_accel_register_lib(self, NC_("accel", "toggle live view"), GDK_KEY_v, 0); dt_accel_register_lib(self, NC_("accel", "zoom live view"), GDK_KEY_z, 0); dt_accel_register_lib(self, NC_("accel", "rotate 90 degrees CCW"), 0, 0); dt_accel_register_lib(self, NC_("accel", "rotate 90 degrees CW"), 0, 0); dt_accel_register_lib(self, NC_("accel", "flip horizontally"), 0, 0); dt_accel_register_lib(self, NC_("accel", "move focus point in (big steps)"), 0, 0); dt_accel_register_lib(self, NC_("accel", "move focus point in (small steps)"), 0, 0); dt_accel_register_lib(self, NC_("accel", "move focus point out (small steps)"), 0, 0); dt_accel_register_lib(self, NC_("accel", "move focus point out (big steps)"), 0, 0); } void connect_key_accels(dt_lib_module_t *self) { dt_lib_live_view_t *lib = (dt_lib_live_view_t *)self->data; dt_accel_connect_button_lib(self, "toggle live view", GTK_WIDGET(lib->live_view)); dt_accel_connect_button_lib(self, "zoom live view", GTK_WIDGET(lib->live_view_zoom)); dt_accel_connect_button_lib(self, "rotate 90 degrees CCW", GTK_WIDGET(lib->rotate_ccw)); dt_accel_connect_button_lib(self, "rotate 90 degrees CW", GTK_WIDGET(lib->rotate_cw)); dt_accel_connect_button_lib(self, "flip horizontally", GTK_WIDGET(lib->flip)); dt_accel_connect_button_lib(self, "move focus point in (big steps)", GTK_WIDGET(lib->focus_in_big)); dt_accel_connect_button_lib(self, "move focus point in (small steps)", GTK_WIDGET(lib->focus_in_small)); dt_accel_connect_button_lib(self, "move focus point out (small steps)", GTK_WIDGET(lib->focus_out_small)); dt_accel_connect_button_lib(self, "move focus point out (big steps)", GTK_WIDGET(lib->focus_out_big)); } static void _rotate_ccw(GtkWidget *widget, gpointer user_data) { dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera; cam->live_view_rotation = (cam->live_view_rotation + 1) % 4; // 0 -> 1 -> 2 -> 3 -> 0 -> ... } static void _rotate_cw(GtkWidget *widget, gpointer user_data) { dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera; cam->live_view_rotation = (cam->live_view_rotation + 3) % 4; // 0 -> 3 -> 2 -> 1 -> 0 -> ... } // Congratulations to Simon for being the first one recognizing live view in a screen shot ^^ static void _toggle_live_view_clicked(GtkWidget *widget, gpointer user_data) { if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) == TRUE) { if(dt_camctl_camera_start_live_view(darktable.camctl) == FALSE) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE); } else { dt_camctl_camera_stop_live_view(darktable.camctl); } } // TODO: using a toggle button would be better, but this setting can also be changed by right clicking on the // canvas (src/views/capture.c). // maybe using a signal would work? i have no idea. static void _zoom_live_view_clicked(GtkWidget *widget, gpointer user_data) { dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera; if(cam->is_live_viewing) { cam->live_view_zoom = !cam->live_view_zoom; if(cam->live_view_zoom == TRUE) dt_camctl_camera_set_property_string(darktable.camctl, NULL, "eoszoom", "5"); else dt_camctl_camera_set_property_string(darktable.camctl, NULL, "eoszoom", "1"); } } static void _focus_button_clicked(GtkWidget *widget, gpointer user_data) { int focus = GPOINTER_TO_INT(user_data); dt_camctl_camera_set_property_choice(darktable.camctl, NULL, "manualfocusdrive", focus); } static void _toggle_flip_clicked(GtkWidget *widget, gpointer user_data) { dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera; cam->live_view_flip = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); } static void _overlay_id_changed(GtkWidget *widget, gpointer user_data) { dt_lib_live_view_t *lib = (dt_lib_live_view_t *)user_data; lib->imgid = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget)); dt_conf_set_int("plugins/lighttable/live_view/overlay_imgid", lib->imgid); } static void _overlay_mode_changed(GtkWidget *combo, gpointer user_data) { dt_conf_set_int("plugins/lighttable/live_view/overlay_mode", dt_bauhaus_combobox_get(combo)); } static void _overlay_splitline_changed(GtkWidget *combo, gpointer user_data) { dt_conf_set_int("plugins/lighttable/live_view/splitline", dt_bauhaus_combobox_get(combo)); } void gui_init(dt_lib_module_t *self) { self->data = calloc(1, sizeof(dt_lib_live_view_t)); // Setup lib data dt_lib_live_view_t *lib = self->data; lib->splitline_x = lib->splitline_y = 0.5; // Setup gui self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); dt_gui_add_help_link(self->widget, "live_view.html#live_view"); GtkWidget *box; box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0); lib->live_view = dtgtk_togglebutton_new(dtgtk_cairo_paint_eye, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL); lib->live_view_zoom = dtgtk_button_new( dtgtk_cairo_paint_zoom, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL); // TODO: see _zoom_live_view_clicked lib->rotate_ccw = dtgtk_button_new(dtgtk_cairo_paint_refresh, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL); lib->rotate_cw = dtgtk_button_new(dtgtk_cairo_paint_refresh, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | CPF_DIRECTION_UP, NULL); lib->flip = dtgtk_togglebutton_new(dtgtk_cairo_paint_flip, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | CPF_DIRECTION_UP, NULL); gtk_box_pack_start(GTK_BOX(box), lib->live_view, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(box), lib->live_view_zoom, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(box), lib->rotate_ccw, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(box), lib->rotate_cw, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(box), lib->flip, TRUE, TRUE, 0); gtk_widget_set_tooltip_text(lib->live_view, _("toggle live view")); gtk_widget_set_tooltip_text(lib->live_view_zoom, _("zoom live view")); gtk_widget_set_tooltip_text(lib->rotate_ccw, _("rotate 90 degrees ccw")); gtk_widget_set_tooltip_text(lib->rotate_cw, _("rotate 90 degrees cw")); gtk_widget_set_tooltip_text(lib->flip, _("flip live view horizontally")); g_signal_connect(G_OBJECT(lib->live_view), "clicked", G_CALLBACK(_toggle_live_view_clicked), lib); g_signal_connect(G_OBJECT(lib->live_view_zoom), "clicked", G_CALLBACK(_zoom_live_view_clicked), lib); g_signal_connect(G_OBJECT(lib->rotate_ccw), "clicked", G_CALLBACK(_rotate_ccw), lib); g_signal_connect(G_OBJECT(lib->rotate_cw), "clicked", G_CALLBACK(_rotate_cw), lib); g_signal_connect(G_OBJECT(lib->flip), "clicked", G_CALLBACK(_toggle_flip_clicked), lib); // focus buttons box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0); lib->focus_in_big = dtgtk_button_new(dtgtk_cairo_paint_solid_triangle, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | CPF_DIRECTION_LEFT, NULL); lib->focus_in_small = dtgtk_button_new(dtgtk_cairo_paint_arrow, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | CPF_DIRECTION_LEFT, NULL); // TODO icon not centered lib->focus_out_small = dtgtk_button_new(dtgtk_cairo_paint_arrow, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | CPF_DIRECTION_RIGHT, NULL); // TODO same here lib->focus_out_big = dtgtk_button_new(dtgtk_cairo_paint_solid_triangle, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | CPF_DIRECTION_RIGHT, NULL); gtk_box_pack_start(GTK_BOX(box), lib->focus_in_big, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(box), lib->focus_in_small, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(box), lib->focus_out_small, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(box), lib->focus_out_big, TRUE, TRUE, 0); gtk_widget_set_tooltip_text(lib->focus_in_big, _("move focus point in (big steps)")); gtk_widget_set_tooltip_text(lib->focus_in_small, _("move focus point in (small steps)")); gtk_widget_set_tooltip_text(lib->focus_out_small, _("move focus point out (small steps)")); gtk_widget_set_tooltip_text(lib->focus_out_big, _("move focus point out (big steps)")); // Near 3 g_signal_connect(G_OBJECT(lib->focus_in_big), "clicked", G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(2)); // Near 1 g_signal_connect(G_OBJECT(lib->focus_in_small), "clicked", G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(0)); // Far 1 g_signal_connect(G_OBJECT(lib->focus_out_small), "clicked", G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(4)); // Far 3 g_signal_connect(G_OBJECT(lib->focus_out_big), "clicked", G_CALLBACK(_focus_button_clicked), GINT_TO_POINTER(6)); // Guides lib->guide_selector = dt_bauhaus_combobox_new(NULL); dt_bauhaus_widget_set_label(lib->guide_selector, NULL, _("guides")); gtk_box_pack_start(GTK_BOX(self->widget), lib->guide_selector, TRUE, TRUE, 0); lib->guides_widgets = gtk_stack_new(); gtk_stack_set_homogeneous(GTK_STACK(lib->guides_widgets), FALSE); gtk_box_pack_start(GTK_BOX(self->widget), lib->guides_widgets, TRUE, TRUE, 0); dt_bauhaus_combobox_add(lib->guide_selector, _("none")); int i = 0; for(GList *iter = darktable.guides; iter; iter = g_list_next(iter), i++) { GtkWidget *widget = NULL; dt_guides_t *guide = (dt_guides_t *)iter->data; dt_bauhaus_combobox_add(lib->guide_selector, _(guide->name)); if(guide->widget) { // generate some unique name so that we can have the same name several times char name[5]; snprintf(name, sizeof(name), "%d", i); widget = guide->widget(NULL, guide->user_data); gtk_widget_show_all(widget); gtk_stack_add_named(GTK_STACK(lib->guides_widgets), widget, name); } lib->guides_widgets_list = g_list_append(lib->guides_widgets_list, widget); } gtk_widget_set_no_show_all(lib->guides_widgets, TRUE); gtk_widget_set_tooltip_text(lib->guide_selector, _("display guide lines to help compose your photograph")); g_signal_connect(G_OBJECT(lib->guide_selector), "value-changed", G_CALLBACK(guides_presets_changed), lib); lib->flip_guides = dt_bauhaus_combobox_new(NULL); dt_bauhaus_widget_set_label(lib->flip_guides, NULL, _("flip")); dt_bauhaus_combobox_add(lib->flip_guides, _("none")); dt_bauhaus_combobox_add(lib->flip_guides, _("horizontally")); dt_bauhaus_combobox_add(lib->flip_guides, _("vertically")); dt_bauhaus_combobox_add(lib->flip_guides, _("both")); gtk_widget_set_tooltip_text(lib->flip_guides, _("flip guides")); gtk_box_pack_start(GTK_BOX(self->widget), lib->flip_guides, TRUE, TRUE, 0); lib->overlay = dt_bauhaus_combobox_new(NULL); dt_bauhaus_widget_set_label(lib->overlay, NULL, _("overlay")); dt_bauhaus_combobox_add(lib->overlay, _("none")); dt_bauhaus_combobox_add(lib->overlay, _("selected image")); dt_bauhaus_combobox_add(lib->overlay, _("id")); gtk_widget_set_tooltip_text(lib->overlay, _("overlay another image over the live view")); g_signal_connect(G_OBJECT(lib->overlay), "value-changed", G_CALLBACK(overlay_changed), lib); gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay, TRUE, TRUE, 0); lib->overlay_id_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); GtkWidget *label = gtk_label_new(_("image id")); gtk_widget_set_halign(label, GTK_ALIGN_START); lib->overlay_id = gtk_spin_button_new_with_range(0, 1000000000, 1); gtk_spin_button_set_digits(GTK_SPIN_BUTTON(lib->overlay_id), 0); gtk_widget_set_tooltip_text(lib->overlay_id, _("enter image id of the overlay manually")); g_signal_connect(G_OBJECT(lib->overlay_id), "value-changed", G_CALLBACK(_overlay_id_changed), lib); gtk_spin_button_set_value(GTK_SPIN_BUTTON(lib->overlay_id), dt_conf_get_int("plugins/lighttable/live_view/overlay_imgid")); gtk_box_pack_start(GTK_BOX(lib->overlay_id_box), label, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(lib->overlay_id_box), lib->overlay_id, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_id_box, TRUE, TRUE, 0); gtk_widget_show(lib->overlay_id); gtk_widget_show(label); lib->overlay_mode = dt_bauhaus_combobox_new(NULL); dt_bauhaus_widget_set_label(lib->overlay_mode, NULL, _("overlay mode")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "normal")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "xor")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "add")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "saturate")); #if(CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 10, 0)) dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "multiply")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "screen")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "overlay")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "darken")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "lighten")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "color dodge")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "color burn")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "hard light")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "soft light")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "difference")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "exclusion")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL hue")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL saturation")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL color")); dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL luminosity")); #endif gtk_widget_set_tooltip_text(lib->overlay_mode, _("mode of the overlay")); dt_bauhaus_combobox_set(lib->overlay_mode, dt_conf_get_int("plugins/lighttable/live_view/overlay_mode")); g_signal_connect(G_OBJECT(lib->overlay_mode), "value-changed", G_CALLBACK(_overlay_mode_changed), lib); gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_mode, TRUE, TRUE, 0); lib->overlay_splitline = dt_bauhaus_combobox_new(NULL); dt_bauhaus_widget_set_label(lib->overlay_splitline, NULL, _("split line")); dt_bauhaus_combobox_add(lib->overlay_splitline, _("off")); dt_bauhaus_combobox_add(lib->overlay_splitline, _("on")); gtk_widget_set_tooltip_text(lib->overlay_splitline, _("only draw part of the overlay")); dt_bauhaus_combobox_set(lib->overlay_splitline, dt_conf_get_int("plugins/lighttable/live_view/splitline")); g_signal_connect(G_OBJECT(lib->overlay_splitline), "value-changed", G_CALLBACK(_overlay_splitline_changed), lib); gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_splitline, TRUE, TRUE, 0); gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), FALSE); gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), FALSE); gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), FALSE); gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_mode), TRUE); gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_id_box), TRUE); gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_splitline), TRUE); guides_presets_set_visibility(lib, 0); } void gui_cleanup(dt_lib_module_t *self) { // dt_lib_live_view_t *lib = self->data; // g_list_free(lib->guides_widgets_list); // INTENTIONAL. it's supposed to be leaky until lua is fixed. free(self->data); self->data = NULL; } void view_enter(struct dt_lib_module_t *self,struct dt_view_t *old_view,struct dt_view_t *new_view) { // disable buttons that won't work with this camera // TODO: initialize tethering mode outside of libs/camera.s so we can use darktable.camctl->active_camera // here dt_lib_live_view_t *lib = self->data; const dt_camera_t *cam = darktable.camctl->active_camera; if(cam == NULL) cam = darktable.camctl->wanted_camera; gboolean sensitive = cam && cam->can_live_view_advanced; gtk_widget_set_sensitive(lib->live_view_zoom, sensitive); gtk_widget_set_sensitive(lib->focus_in_big, sensitive); gtk_widget_set_sensitive(lib->focus_in_small, sensitive); gtk_widget_set_sensitive(lib->focus_out_big, sensitive); gtk_widget_set_sensitive(lib->focus_out_small, sensitive); } // TODO: find out where the zoom window is and draw overlay + grid accordingly #define MARGIN 20 #define BAR_HEIGHT 18 /* see libs/camera.c */ void gui_post_expose(dt_lib_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery) { dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera; dt_lib_live_view_t *lib = self->data; if(cam->is_live_viewing == FALSE || cam->live_view_zoom == TRUE) return; dt_pthread_mutex_lock(&cam->live_view_pixbuf_mutex); if(GDK_IS_PIXBUF(cam->live_view_pixbuf) == FALSE) { dt_pthread_mutex_unlock(&cam->live_view_pixbuf_mutex); return; } double w = width - (MARGIN * 2.0f); double h = height - (MARGIN * 2.0f) - BAR_HEIGHT; gint pw = gdk_pixbuf_get_width(cam->live_view_pixbuf); gint ph = gdk_pixbuf_get_height(cam->live_view_pixbuf); lib->overlay_x0 = lib->overlay_x1 = lib->overlay_y0 = lib->overlay_y1 = 0.0; gboolean use_splitline = (dt_bauhaus_combobox_get(lib->overlay_splitline) == 1); // OVERLAY int imgid = 0; switch(dt_bauhaus_combobox_get(lib->overlay)) { case OVERLAY_SELECTED: imgid = dt_view_tethering_get_selected_imgid(darktable.view_manager); break; case OVERLAY_ID: imgid = lib->imgid; break; } if(imgid > 0) { cairo_save(cr); const dt_image_t *img = dt_image_cache_testget(darktable.image_cache, imgid, 'r'); // if the user points at this image, we really want it: if(!img) img = dt_image_cache_get(darktable.image_cache, imgid, 'r'); const float imgwd = 0.97f; dt_mipmap_buffer_t buf; dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, imgwd * w, imgwd * h); dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, mip, 0, 'r'); float scale = 1.0; 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.buf, CAIRO_FORMAT_RGB24, buf.width, buf.height, stride); scale = fminf(fminf(w, pw) / (float)buf.width, fminf(h, ph) / (float)buf.height); } // draw centered and fitted: cairo_translate(cr, width / 2.0, (height + BAR_HEIGHT) / 2.0f); cairo_scale(cr, scale, scale); if(buf.buf) { cairo_translate(cr, -.5f * buf.width, -.5f * buf.height); if(use_splitline) { double x0, y0, x1, y1; switch(lib->splitline_rotation) { case 0: x0 = 0.0; y0 = 0.0; x1 = buf.width * lib->splitline_x; y1 = buf.height; break; case 1: x0 = 0.0; y0 = 0.0; x1 = buf.width; y1 = buf.height * lib->splitline_y; break; case 2: x0 = buf.width * lib->splitline_x; y0 = 0.0; x1 = buf.width; y1 = buf.height; break; case 3: x0 = 0.0; y0 = buf.height * lib->splitline_y; x1 = buf.width; y1 = buf.height; break; default: fprintf(stderr, "OMFG, the world will collapse, this shouldn't be reachable!\n"); dt_pthread_mutex_unlock(&cam->live_view_pixbuf_mutex); return; } cairo_rectangle(cr, x0, y0, x1, y1); cairo_clip(cr); } 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); int overlay_modes_index = dt_bauhaus_combobox_get(lib->overlay_mode); if(overlay_modes_index >= 0) { cairo_operator_t mode = _overlay_modes[overlay_modes_index]; cairo_set_operator(cr, mode); } cairo_fill(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_surface_destroy(surface); } cairo_restore(cr); if(buf.buf) dt_mipmap_cache_release(darktable.mipmap_cache, &buf); if(img) dt_image_cache_read_release(darktable.image_cache, img); // ON CANVAS CONTROLS if(use_splitline) { scale = fminf(1.0, fminf(w / pw, h / ph)); // image coordinates lib->overlay_x0 = 0.5 * (width - pw * scale); lib->overlay_y0 = 0.5 * (height - ph * scale + BAR_HEIGHT); lib->overlay_x1 = lib->overlay_x0 + pw * scale; lib->overlay_y1 = lib->overlay_y0 + ph * scale; // splitline position to absolute coords: double sl_x = lib->overlay_x0 + lib->splitline_x * pw * scale; double sl_y = lib->overlay_y0 + lib->splitline_y * ph * scale; int x0 = sl_x, y0 = 0.0, x1 = x0, y1 = height; if(lib->splitline_rotation % 2 != 0) { x0 = 0.0; y0 = sl_y; x1 = width; y1 = y0; } gboolean mouse_over_control = (lib->splitline_rotation % 2 == 0) ? (fabs(sl_x - pointerx) < 5) : (fabs(sl_y - pointery) < 5); cairo_save(cr); cairo_set_source_rgb(cr, .7, .7, .7); cairo_set_line_width(cr, (mouse_over_control ? 2.0 : 0.5)); cairo_move_to(cr, x0, y0); cairo_line_to(cr, x1, y1); cairo_stroke(cr); /* if mouse over control lets draw center rotate control, hide if split is dragged */ if(!lib->splitline_dragging && mouse_over_control) { cairo_set_line_width(cr, 0.5); double s = width * HANDLE_SIZE; dtgtk_cairo_paint_refresh(cr, sl_x - (s * 0.5), sl_y - (s * 0.5), s, s, 1, NULL); } cairo_restore(cr); } } // GUIDES if(cam->live_view_rotation % 2 == 1) { gint tmp = pw; pw = ph; ph = tmp; } float scale = 1.0; // if(cam->live_view_zoom == FALSE) // { if(pw > w) scale = w / pw; if(ph > h) scale = fminf(scale, h / ph); // } double sw = scale * pw; double sh = scale * ph; // draw guides int guide_flip = dt_bauhaus_combobox_get(lib->flip_guides); double left = (width - sw) * 0.5; double top = (height + BAR_HEIGHT - sh) * 0.5; double dashes = 5.0; cairo_save(cr); cairo_rectangle(cr, left, top, sw, sh); cairo_clip(cr); cairo_set_dash(cr, &dashes, 1, 0); // Move coordinates to local center selection. cairo_translate(cr, (sw / 2 + left), (sh / 2 + top)); // Flip horizontal. if(guide_flip & FLAG_FLIP_HORIZONTAL) cairo_scale(cr, -1, 1); // Flip vertical. if(guide_flip & FLAG_FLIP_VERTICAL) cairo_scale(cr, 1, -1); int which = dt_bauhaus_combobox_get(lib->guide_selector); dt_guides_t *guide = (dt_guides_t *)g_list_nth_data(darktable.guides, which - 1); if(guide) { guide->draw(cr, -sw / 2, -sh / 2, sw, sh, 1.0, guide->user_data); cairo_stroke_preserve(cr); cairo_set_dash(cr, &dashes, 0, 0); cairo_set_source_rgba(cr, .3, .3, .3, .8); cairo_stroke(cr); } cairo_restore(cr); dt_pthread_mutex_unlock(&cam->live_view_pixbuf_mutex); }
// 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 gboolean upscale, const int32_t thumbnail_export, const char *filter, const gboolean copy_metadata, dt_imageio_module_storage_t *storage, dt_imageio_module_data_t *storage_params, int num, int total) { 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_get(darktable.mipmap_cache, &buf, imgid, DT_MIPMAP_F, DT_MIPMAP_BLOCKING, 'r'); else dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, DT_MIPMAP_FULL, DT_MIPMAP_BLOCKING, 'r'); dt_dev_load_image(&dev, imgid); const dt_image_t *img = &dev.image_storage; const int wd = img->width; const int ht = img->height; const float max_scale = upscale ? 100.0 : 1.0; 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 %s, please lower the threads used for export or buy more memory."), thumbnail_export ? C_("noun", "thumbnail export") : C_("noun", "export")); dt_dev_cleanup(&dev); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); return 1; } if(!buf.buf) { fprintf(stderr, "allocation failed???\n"); dt_control_log(_("image `%s' is not available!"), img->filename); dt_mipmap_cache_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 && format_params->style[0] != '\0') { GList *stls; GList *modules = dev.iop; dt_iop_module_t *m = NULL; if((stls = dt_styles_get_item_list(format_params->style, TRUE, -1)) == 0) { dt_control_log(_("cannot find the style '%s' to apply during export."), format_params->style); dt_dev_cleanup(&dev); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); return 1; } // Add each params while(stls) { dt_style_item_t *s = (dt_style_item_t *)stls->data; gboolean module_found = FALSE; modules = dev.iop; while(modules) { m = (dt_iop_module_t *)modules->data; // since the name in the style is returned with a possible multi-name, just check the start of the // name if(strncmp(m->op, s->name, strlen(m->op)) == 0) { dt_dev_history_item_t *h = malloc(sizeof(dt_dev_history_item_t)); dt_iop_module_t *sty_module = m; if(format_params->style_append && !(m->flags() & IOP_FLAGS_ONE_INSTANCE)) { sty_module = dt_dev_module_duplicate(m->dev, m, 0); if(!sty_module) { free(h); return 1; } } h->params = s->params; h->blend_params = s->blendop_params; h->enabled = s->enabled; h->module = sty_module; h->multi_priority = 1; g_strlcpy(h->multi_name, "<style>", sizeof(h->multi_name)); if(m->legacy_params && (s->module_version != m->version())) { void *new_params = malloc(m->params_size); m->legacy_params(m, h->params, s->module_version, new_params, labs(m->version())); free(h->params); h->params = new_params; } dev.history_end++; dev.history = g_list_append(dev.history, h); module_found = TRUE; g_free(s->name); break; } modules = g_list_next(modules); } if(!module_found) dt_style_item_free(s); stls = g_list_next(stls); } g_list_free(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(colorout->get_p && strcmp(colorout->op, "colorout") == 0) { const char *iccprofile = colorout->get_p(colorout->params, "iccprofile"); if(!strcmp(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, max_scale) : 1.0; const double scaley = height > 0 ? fminf(height / (double)pipe.processed_height, max_scale) : 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); dt_get_times(&start); if(high_quality_processing) { /* * if high quality processing was requested, downsampling will be done * at the very end of the pipe (just before border and watermark) */ const double scalex = format_params->max_width > 0 ? fminf(format_params->max_width / (double)pipe.processed_width, max_scale) : 1.0; const double scaley = format_params->max_height > 0 ? fminf(format_params->max_height / (double)pipe.processed_height, max_scale) : 1.0; const double scale = fminf(scalex, scaley); processed_width = scale * pipe.processed_width + .5f; processed_height = scale * pipe.processed_height + .5f; dt_dev_pixelpipe_process_no_gamma(&pipe, &dev, 0, 0, processed_width, processed_height, scale); } else { // else, downsampling will be right after demosaic // so we need to turn temporarily disable in-pipe late downsampling iop. GList *finalscalep = g_list_last(pipe.nodes); dt_dev_pixelpipe_iop_t *finalscale = (dt_dev_pixelpipe_iop_t *)finalscalep->data; while(strcmp(finalscale->module->op, "finalscale")) { finalscale = NULL; finalscalep = g_list_previous(finalscalep); if(!finalscalep) break; finalscale = (dt_dev_pixelpipe_iop_t *)finalscalep->data; } if(finalscale) finalscale->enabled = 0; // 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); if(finalscale) finalscale->enabled = 1; } dt_show_times(&start, thumbnail_export ? "[dev_process_thumbnail] pixel pipeline processing" : "[dev_process_export] pixel pipeline processing", NULL); uint8_t *outbuf = pipe.backbuf; // downconversion to low-precision formats: if(bpp == 8) { if(display_byteorder) { if(high_quality_processing) { const float *const inbuf = (float *)outbuf; for(size_t k = 0; k < (size_t)processed_width * processed_height; k++) { // convert in place, this is unfortunately very serial.. const uint8_t r = CLAMP(inbuf[4 * k + 2] * 0xff, 0, 0xff); const uint8_t g = CLAMP(inbuf[4 * k + 1] * 0xff, 0, 0xff); const uint8_t b = CLAMP(inbuf[4 * k + 0] * 0xff, 0, 0xff); outbuf[4 * k + 0] = r; outbuf[4 * k + 1] = g; outbuf[4 * k + 2] = b; } } // else processing output was 8-bit already, and no need to swap order } else // need to flip { // ldr output: char if(high_quality_processing) { const float *const inbuf = (float *)outbuf; for(size_t k = 0; k < (size_t)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 { // !display_byteorder, need to swap: 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(size_t k = 0; k < (size_t)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 size_t k = (size_t)processed_width * y + x; 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[PATH_MAX] = { 0 }; gboolean from_cache = TRUE; dt_image_full_path(imgid, pathname, sizeof(pathname), &from_cache); // last param is dng mode, it's false here length = dt_exif_read_blob(exif_profile, pathname, imgid, sRGB, processed_width, processed_height, 0); res = format->write_image(format_params, filename, outbuf, exif_profile, length, imgid, num, total); } else { res = format->write_image(format_params, filename, outbuf, NULL, 0, imgid, num, total); } dt_dev_pixelpipe_cleanup(&pipe); dt_dev_cleanup(&dev); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); /* now write xmp into that container, if possible */ if(copy_metadata && (format->flags(format_params) & FORMAT_FLAGS_SUPPORT_XMP)) { dt_exif_xmp_attach(imgid, filename); // no need to cancel the export if this fail } if(!thumbnail_export && strcmp(format->mime(format_params), "memory")) { dt_control_signal_raise(darktable.signals, DT_SIGNAL_IMAGE_EXPORT_TMPFILE, imgid, filename, format, format_params, storage, storage_params); } return res; }
static int generate_thumbnail_cache(const dt_mipmap_size_t min_mip, const dt_mipmap_size_t max_mip, const int32_t min_imgid, const int32_t max_imgid) { fprintf(stderr, _("creating cache directories\n")); for(dt_mipmap_size_t k = min_mip; k <= max_mip; k++) { char dirname[PATH_MAX] = { 0 }; snprintf(dirname, sizeof(dirname), "%s.d/%d", darktable.mipmap_cache->cachedir, k); fprintf(stderr, _("creating cache directory '%s'\n"), dirname); if(g_mkdir_with_parents(dirname, 0750)) { fprintf(stderr, _("could not create directory '%s'!\n"), dirname); return 1; } } // some progress counter sqlite3_stmt *stmt; size_t image_count = 0, counter = 0; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT COUNT(*) FROM main.images WHERE id >= ?1 AND id <= ?2", -1, &stmt, 0); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, min_imgid); DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, max_imgid); if(sqlite3_step(stmt) == SQLITE_ROW) { image_count = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); } else { return 1; } if(!image_count) { fprintf(stderr, _("warning: no images are matching the requested image id range\n")); if(min_imgid > max_imgid) { fprintf(stderr, _("warning: did you want to swap these boundaries?\n")); } } // go through all images: DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT id FROM main.images WHERE id >= ?1 AND id <= ?2", -1, &stmt, 0); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, min_imgid); DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, max_imgid); while(sqlite3_step(stmt) == SQLITE_ROW) { const int32_t imgid = sqlite3_column_int(stmt, 0); for(int k = max_mip; k >= min_mip && k >= 0; k--) { char filename[PATH_MAX] = { 0 }; snprintf(filename, sizeof(filename), "%s.d/%d/%d.jpg", darktable.mipmap_cache->cachedir, k, imgid); // if the thumbnail is already on disc - do nothing if(!access(filename, R_OK)) continue; // else, generate thumbnail and store in mipmap cache. dt_mipmap_buffer_t buf; dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, k, DT_MIPMAP_BLOCKING, 'r'); dt_mipmap_cache_release(darktable.mipmap_cache, &buf); } // and immediately write thumbs to disc and remove from mipmap cache. dt_mimap_cache_evict(darktable.mipmap_cache, imgid); counter++; fprintf(stderr, "image %zu/%zu (%.02f%%)\n", counter, image_count, 100.0 * counter / (float)image_count); } sqlite3_finalize(stmt); fprintf(stderr, "done\n"); return 0; }
static void generate_thumbnail_cache() { const int max_mip = DT_MIPMAP_2; fprintf(stderr, _("creating cache directories\n")); char filename[PATH_MAX] = {0}; for(int k=DT_MIPMAP_0;k<=max_mip;k++) { snprintf(filename, sizeof(filename), "%s.d/%d", darktable.mipmap_cache->cachedir, k); fprintf(stderr, _("creating cache directory '%s'\n"), filename); int mkd = g_mkdir_with_parents(filename, 0750); if(mkd) { fprintf(stderr, _("could not create directory '%s'!\n"), filename); return; } } // some progress counter sqlite3_stmt *stmt; uint64_t image_count = 0, counter = 0; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select count(id) from images", -1, &stmt, 0); if(sqlite3_step(stmt) == SQLITE_ROW) image_count = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); // go through all images: DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select id from images", -1, &stmt, 0); // could only alloc max_mip-1, but would need to detect the special case that max==0. const size_t bufsize = (size_t)4 * darktable.mipmap_cache->max_width[max_mip] * darktable.mipmap_cache->max_height[max_mip]; uint8_t *tmp = (uint8_t *)dt_alloc_align(16, bufsize); if(!tmp) { fprintf(stderr, "couldn't allocate temporary memory!\n"); sqlite3_finalize(stmt); return; } const int cache_quality = MIN(100, MAX(10, dt_conf_get_int("database_cache_quality"))); while(sqlite3_step(stmt) == SQLITE_ROW) { const int32_t imgid = sqlite3_column_int(stmt, 0); // check whether all of these files are already there int all_exist = 1; for(int k=max_mip;k>=DT_MIPMAP_0;k--) { snprintf(filename, sizeof(filename), "%s.d/%d/%d.jpg", darktable.mipmap_cache->cachedir, k, imgid); all_exist &= !access(filename, R_OK); } if(all_exist) goto next; dt_mipmap_buffer_t buf; // get largest thumbnail for this image // this one will take care of itself, we'll just write out the lower thumbs manually: dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, max_mip, DT_MIPMAP_BLOCKING, 'r'); if(buf.width > 8 && buf.height > 8) // don't create for skulls for(int k=max_mip-1;k>=DT_MIPMAP_0;k--) { uint32_t width, height; const int wd = darktable.mipmap_cache->max_width[k]; const int ht = darktable.mipmap_cache->max_height[k]; // use exactly the same mechanism as the cache internally to rescale the thumbnail: dt_iop_flip_and_zoom_8(buf.buf, buf.width, buf.height, tmp, wd, ht, 0, &width, &height); snprintf(filename, sizeof(filename), "%s.d/%d/%d.jpg", darktable.mipmap_cache->cachedir, k, imgid); FILE *f = fopen(filename, "wb"); if(f) { // allocate temp memory: uint8_t *blob = (uint8_t *)malloc(bufsize); if(!blob) goto write_error; const int32_t length = dt_imageio_jpeg_compress(tmp, blob, width, height, cache_quality); assert(length <= bufsize); int written = fwrite(blob, sizeof(uint8_t), length, f); if(written != length) { write_error: unlink(filename); } free(blob); fclose(f); } } dt_mipmap_cache_release(darktable.mipmap_cache, &buf); next: counter ++; fprintf(stderr, "\rimage %lu/%lu (%.02f%%) ", counter, image_count, 100.0*counter/(float)image_count); } dt_free_align(tmp); sqlite3_finalize(stmt); fprintf(stderr, "done \n"); }