void dt_selection_deselect(dt_selection_t *selection, uint32_t imgid) { gchar *query = NULL; selection->last_single_id = -1; if(imgid != -1) { const dt_image_t *image = dt_image_cache_get(darktable.image_cache, imgid, 'r'); if(image) { int img_group_id = image->group_id; dt_image_cache_read_release(darktable.image_cache, image); if(!darktable.gui || !darktable.gui->grouping || darktable.gui->expanded_group_id == img_group_id) { query = dt_util_dstrcat(query, "DELETE FROM main.selected_images WHERE imgid = %d", imgid); } else { query = dt_util_dstrcat(query, "DELETE FROM main.selected_images WHERE imgid IN " "(SELECT id FROM main.images WHERE group_id = %d)", img_group_id); } DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), query, NULL, NULL, NULL); g_free(query); } } /* update hint message */ dt_collection_hint_message(darktable.collection); }
void dt_selection_select(dt_selection_t *selection, uint32_t imgid) { gchar *query = NULL; if(imgid != -1) { const dt_image_t *image = dt_image_cache_get(darktable.image_cache, imgid, 'r'); if(image) { int img_group_id = image->group_id; dt_image_cache_read_release(darktable.image_cache, image); if(!darktable.gui || !darktable.gui->grouping || darktable.gui->expanded_group_id == img_group_id || !selection->collection) { query = dt_util_dstrcat(query, "INSERT OR IGNORE INTO main.selected_images VALUES (%d)", imgid); } else { query = dt_util_dstrcat(query, "INSERT OR IGNORE INTO main.selected_images SELECT id FROM main.images " "WHERE group_id = %d AND id IN (%s)", img_group_id, dt_collection_get_query_no_group(selection->collection)); } DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), query, NULL, NULL, NULL); g_free(query); } } /* update hint message */ dt_collection_hint_message(darktable.collection); }
/** add an image to a group */ void dt_grouping_add_to_group(int group_id, int image_id) { // remove from old group dt_grouping_remove_from_group(image_id); dt_image_t *img = dt_image_cache_get(darktable.image_cache, image_id, 'w'); img->group_id = group_id; dt_image_cache_write_release(darktable.image_cache, img, DT_IMAGE_CACHE_SAFE); }
/** remove an image from a group */ int dt_grouping_remove_from_group(int image_id) { sqlite3_stmt *stmt; int new_group_id = -1; const dt_image_t *img = dt_image_cache_get(darktable.image_cache, image_id, 'r'); int img_group_id = img->group_id; dt_image_cache_read_release(darktable.image_cache, img); if(img_group_id == image_id) { // get a new group_id for all the others in the group. also write it to the dt_image_t struct. DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select id from images where group_id = ?1 and id != ?2", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, img_group_id); DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, image_id); while(sqlite3_step(stmt) == SQLITE_ROW) { int other_id = sqlite3_column_int(stmt, 0); if(new_group_id == -1) new_group_id = other_id; dt_image_t *other_img = dt_image_cache_get(darktable.image_cache, other_id, 'w'); other_img->group_id = new_group_id; dt_image_cache_write_release(darktable.image_cache, other_img, DT_IMAGE_CACHE_SAFE); } sqlite3_finalize(stmt); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "update images set group_id = ?1 where group_id = ?2 and id != ?3", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, new_group_id); DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, img_group_id); DT_DEBUG_SQLITE3_BIND_INT(stmt, 3, image_id); sqlite3_step(stmt); sqlite3_finalize(stmt); } else { // change the group_id for this image. dt_image_t *wimg = dt_image_cache_get(darktable.image_cache, image_id, 'w'); new_group_id = wimg->group_id; wimg->group_id = image_id; dt_image_cache_write_release(darktable.image_cache, wimg, DT_IMAGE_CACHE_SAFE); } return new_group_id; }
static void remove_preset_flag(const int imgid) { dt_image_t *image = dt_image_cache_get(darktable.image_cache, imgid, 'w'); // clear flag image->flags &= ~DT_IMAGE_AUTO_PRESETS_APPLIED; // write through to sql+xmp dt_image_cache_write_release(darktable.image_cache, image, DT_IMAGE_CACHE_SAFE); }
void dt_ratings_apply_to_image(int imgid, int rating) { dt_image_t *image = dt_image_cache_get(darktable.image_cache, imgid, 'w'); // one star is a toggle, so you can easily reject images by removing the last star: if(((image->flags & 0x7) == 1) && (rating == 1)) rating = 0; image->flags = (image->flags & ~0x7) | (0x7 & rating); // synch through: dt_image_cache_write_release(darktable.image_cache, image, DT_IMAGE_CACHE_SAFE); dt_collection_hint_message(darktable.collection); }
int try_enter(dt_view_t *self) { dt_print_t *prt=(dt_print_t*)self->data; // now check that there is at least one selected image prt->image_id = -1; int selected = dt_control_get_mouse_over_id(); if(selected < 0) { // try last selected sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select * from selected_images", -1, &stmt, NULL); if(sqlite3_step(stmt) == SQLITE_ROW) selected = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); // Leave as selected only the image being edited DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "delete from selected_images", NULL, NULL, NULL); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "insert or ignore into selected_images values (?1)", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, selected); sqlite3_step(stmt); sqlite3_finalize(stmt); } if(selected < 0) { // fail :( dt_control_log(_("no image selected!")); return 1; } // this loads the image from db if needed: const dt_image_t *img = dt_image_cache_get(darktable.image_cache, selected, 'r'); // get image and check if it has been deleted from disk first! char imgfilename[PATH_MAX] = { 0 }; gboolean from_cache = TRUE; dt_image_full_path(img->id, imgfilename, sizeof(imgfilename), &from_cache); if(!g_file_test(imgfilename, G_FILE_TEST_IS_REGULAR)) { dt_control_log(_("image `%s' is currently unavailable"), img->filename); // dt_image_remove(selected); dt_image_cache_read_release(darktable.image_cache, img); return 1; } // and drop the lock again. dt_image_cache_read_release(darktable.image_cache, img); prt->image_id = selected; return 0; }
/** make an image the representative of the group it is in */ int dt_grouping_change_representative(int image_id) { sqlite3_stmt *stmt; dt_image_t *img = dt_image_cache_get(darktable.image_cache, image_id, 'w'); int group_id = img->group_id; dt_image_cache_write_release(darktable.image_cache, img, DT_IMAGE_CACHE_SAFE); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select id from images where group_id = ?1", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, group_id); while(sqlite3_step(stmt) == SQLITE_ROW) { int other_id = sqlite3_column_int(stmt, 0); dt_image_t *other_img = dt_image_cache_get(darktable.image_cache, other_id, 'w'); other_img->group_id = image_id; dt_image_cache_write_release(darktable.image_cache, other_img, DT_IMAGE_CACHE_SAFE); } sqlite3_finalize(stmt); return image_id; }
int dt_history_load_and_apply(int imgid, gchar *filename, int history_only) { int res = 0; dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'w'); if(img) { if(dt_exif_xmp_read(img, filename, history_only)) return 1; /* if current image in develop reload history */ if(dt_dev_is_current_image(darktable.develop, imgid)) dt_dev_reload_history_items(darktable.develop); dt_image_cache_write_release(darktable.image_cache, img, DT_IMAGE_CACHE_SAFE); dt_mipmap_cache_remove(darktable.mipmap_cache, imgid); } return res; }
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; }
void finalize_store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *params) { dt_imageio_email_t *d = (dt_imageio_email_t *)params; // All images are exported, generate a mailto uri and startup default mail client gchar uri[4096] = { 0 }; gchar body[4096] = { 0 }; gchar attachments[4096] = { 0 }; gchar *uriFormat = "xdg-email --subject \"%s\" --body \"%s\" %s &"; // subject, body format const gchar *subject = _("images exported from darktable"); const gchar *imageBodyFormat = " - %s (%s)\\n"; // filename, exif oneliner gchar *attachmentFormat = " --attach \"%s\""; // list of attachments format gchar *attachmentSeparator = ""; while(d->images) { gchar exif[256] = { 0 }; _email_attachment_t *attachment = (_email_attachment_t *)d->images->data; gchar *filename = g_path_get_basename(attachment->file); const dt_image_t *img = dt_image_cache_get(darktable.image_cache, attachment->imgid, 'r'); dt_image_print_exif(img, exif, sizeof(exif)); g_snprintf(body + strlen(body), sizeof(body) - strlen(body), imageBodyFormat, filename, exif); g_free(filename); if(*attachments) g_snprintf(attachments + strlen(attachments), sizeof(attachments) - strlen(attachments), "%s", attachmentSeparator); g_snprintf(attachments + strlen(attachments), sizeof(attachments) - strlen(attachments), attachmentFormat, attachment->file); // Free attachment item and remove dt_image_cache_read_release(darktable.image_cache, img); g_free(d->images->data); d->images = g_list_remove(d->images, d->images->data); } // build uri and launch before we quit... g_snprintf(uri, sizeof(uri), uriFormat, subject, body, attachments); fprintf(stderr, "[email] launching `%s'\n", uri); if(system(uri) < 0) { dt_control_log(_("could not launch email client!")); } }
int group_with(lua_State *L) { dt_lua_image_t first_image; luaA_to(L, dt_lua_image_t, &first_image, 1); if(lua_isnoneornil(L, 2)) { dt_grouping_remove_from_group(first_image); return 0; } dt_lua_image_t second_image; luaA_to(L, dt_lua_image_t, &second_image, 2); const dt_image_t *cimg = dt_image_cache_get(darktable.image_cache, second_image, 'r'); int group_id = cimg->group_id; dt_image_cache_read_release(darktable.image_cache, cimg); dt_grouping_add_to_group(group_id, first_image); return 0; }
int get_group(lua_State *L) { dt_lua_image_t first_image; luaA_to(L, dt_lua_image_t, &first_image, 1); const dt_image_t *cimg = dt_image_cache_get(darktable.image_cache, first_image, 'r'); int group_id = cimg->group_id; dt_image_cache_read_release(darktable.image_cache, cimg); sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select id from images where group_id = ?1", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, group_id); lua_newtable(L); while(sqlite3_step(stmt) == SQLITE_ROW) { int imgid = sqlite3_column_int(stmt, 0); luaA_push(L, dt_lua_image_t, &imgid); luaL_ref(L, -2); } luaA_push(L, dt_lua_image_t, &group_id); lua_setfield(L, -2, "leader"); return 1; }
static void _jump_to() { int32_t imgid = dt_control_get_mouse_over_id(); if(imgid == -1) { sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select imgid from selected_images", -1, &stmt, NULL); if(sqlite3_step(stmt) == SQLITE_ROW) imgid = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); } if(imgid != -1) { char path[512]; const dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'r'); dt_image_film_roll_directory(img, path, sizeof(path)); dt_image_cache_read_release(darktable.image_cache, img); char collect[1024]; snprintf(collect, sizeof(collect), "1:0:0:%s$", path); dt_collection_deserialize(collect); } }
static void _lib_geotagging_calculate_offset_callback(GtkWidget *widget, dt_lib_module_t *self) { dt_lib_geotagging_t *d = (dt_lib_geotagging_t *)self->data; const gchar *gps_time = gtk_entry_get_text(GTK_ENTRY(d->floating_window_entry)); if(gps_time) { gchar **tokens = g_strsplit(gps_time, ":", 0); if(tokens[0] != '\0' && tokens[1] != '\0' && tokens[2] != '\0') { if(g_ascii_isdigit(tokens[0][0]) && g_ascii_isdigit(tokens[0][1]) && tokens[0][2] == '\0' && g_ascii_isdigit(tokens[1][0]) && g_ascii_isdigit(tokens[1][1]) && tokens[1][2] == '\0' && g_ascii_isdigit(tokens[2][0]) && g_ascii_isdigit(tokens[2][1]) && tokens[2][2] == '\0') { int h, m, s; h = (tokens[0][0] - '0') * 10 + tokens[0][1] - '0'; m = (tokens[1][0] - '0') * 10 + tokens[1][1] - '0'; s = (tokens[2][0] - '0') * 10 + tokens[2][1] - '0'; if(h < 24 && m < 60 && s < 60) { // finally a valid time // get imgid int32_t imgid = -1; sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select imgid from selected_images order by imgid asc limit 1", -1, &stmt, NULL); if(sqlite3_step(stmt) == SQLITE_ROW) imgid = sqlite3_column_int(stmt, 0); else // no selection is used, use mouse over id imgid = dt_control_get_mouse_over_id(); sqlite3_finalize(stmt); if(imgid > 0) { const dt_image_t *cimg = dt_image_cache_get(darktable.image_cache, imgid, 'r'); // get the exif_datetime_taken and parse it gint year; gint month; gint day; gint hour; gint minute; gint second; if(sscanf(cimg->exif_datetime_taken, "%d:%d:%d %d:%d:%d", (int *)&year, (int *)&month, (int *)&day, (int *)&hour, (int *)&minute, (int *)&second) == 6) { // calculate the offset long int exif_seconds = hour * 60 * 60 + minute * 60 + second; long int gps_seconds = h * 60 * 60 + m * 60 + s; long int offset = gps_seconds - exif_seconds; // transform the offset back into a string gchar sign = (offset < 0) ? '-' : '+'; offset = labs(offset); gint offset_h = offset / (60 * 60); offset -= offset_h * 60 * 60; gint offset_m = offset / 60; offset -= offset_m * 60; gchar *offset_str = g_strdup_printf("%c%02d:%02d:%02ld", sign, offset_h, offset_m, offset); // write the offset into d->offset_entry gtk_entry_set_text(GTK_ENTRY(d->offset_entry), offset_str); g_free(offset_str); } dt_image_cache_read_release(darktable.image_cache, cimg); } } } } g_strfreev(tokens); } gtk_widget_destroy(d->floating_window); }
int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total, const gboolean high_quality, const gboolean upscale) { gint result = 0; dt_storage_flickr_params_t *p = (dt_storage_flickr_params_t *)sdata; flickcurl_upload_status *photo_status; gint tags = 0; const char *ext = format->extension(fdata); // Let's upload image... /* construct a temporary file name */ char fname[PATH_MAX] = { 0 }; dt_loc_get_tmp_dir(fname, sizeof(fname)); g_strlcat(fname, "/darktable.XXXXXX.", sizeof(fname)); g_strlcat(fname, ext, sizeof(fname)); char *caption = NULL; char *description = NULL; gint fd = g_mkstemp(fname); fprintf(stderr, "tempfile: %s\n", fname); if(fd == -1) { dt_control_log("failed to create temporary image for flickr export"); return 1; } close(fd); const dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'r'); // If title is not existing, then use the filename without extension. If not, then use title instead GList *title = dt_metadata_get(img->id, "Xmp.dc.title", NULL); if(title != NULL) { caption = g_strdup(title->data); g_list_free_full(title, &g_free); } else { caption = g_path_get_basename(img->filename); (g_strrstr(caption, "."))[0] = '\0'; // chop extension... } GList *desc = dt_metadata_get(img->id, "Xmp.dc.description", NULL); if(desc != NULL) { description = desc->data; } dt_image_cache_read_release(darktable.image_cache, img); if(dt_imageio_export(imgid, fname, format, fdata, high_quality, upscale, FALSE, self, sdata, num, total) != 0) { fprintf(stderr, "[imageio_storage_flickr] could not export to file: `%s'!\n", fname); dt_control_log(_("could not export to file `%s'!"), fname); result = 1; goto cleanup; } #ifdef _OPENMP #pragma omp critical #endif { // TODO: Check if this could be done in threads, so we enhance export time by using // upload time for one image to export another image to disk. // Upload image // Do we export tags? if(p->export_tags == TRUE) tags = imgid; photo_status = _flickr_api_upload_photo(p, fname, caption, description, tags); } if(!photo_status) { fprintf(stderr, "[imageio_storage_flickr] could not upload to flickr!\n"); dt_control_log(_("could not upload to flickr!")); result = 1; goto cleanup; } // int fail = 0; // A photoset is only created if we have an album title set if(p->flickr_api->current_album == NULL && p->flickr_api->new_album == TRUE) { char *photoset_id; photoset_id = _flickr_api_create_photoset(p->flickr_api, photo_status->photoid); if(photoset_id == NULL) { dt_control_log("failed to create flickr album"); // fail = 1; } else { // p->flickr_api->new_album = FALSE; p->flickr_api->current_album = flickcurl_photosets_getInfo(p->flickr_api->fc, photoset_id); } } // if(fail) return 1; // TODO: What to do if photoset creation fails? // Add to gallery, if needed if(p->flickr_api->current_album != NULL && p->flickr_api->new_album != TRUE) { flickcurl_photosets_addPhoto(p->flickr_api->fc, p->flickr_api->current_album->id, photo_status->photoid); // TODO: Check for errors adding photo to gallery } else { if(p->flickr_api->current_album != NULL && p->flickr_api->new_album == TRUE) { p->flickr_api->new_album = FALSE; } } cleanup: // And remove from filesystem.. unlink(fname); g_free(caption); if(desc) g_list_free_full(desc, &g_free); if(!result) { // this makes sense only if the export was successful dt_control_log(_("%d/%d exported to flickr webalbum"), num, total); } return result; }
static gboolean _lib_filmstrip_button_press_callback(GtkWidget *w, GdkEventButton *e, gpointer user_data) { dt_lib_module_t *self = (dt_lib_module_t *)user_data; dt_lib_filmstrip_t *strip = (dt_lib_filmstrip_t *)self->data; int32_t mouse_over_id = strip->mouse_over_id; strip->select = DT_LIB_FILMSTRIP_SELECT_NONE; if(e->button == 1) { if(e->type == GDK_BUTTON_PRESS) { /* let check if any thumb controls was clicked */ switch(strip->image_over) { case DT_VIEW_DESERT: /* is this an activation of image */ if((e->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == 0) strip->select = DT_LIB_FILMSTRIP_SELECT_SINGLE; else if((e->state & (GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) strip->select = DT_LIB_FILMSTRIP_SELECT_TOGGLE; else if((e->state & (GDK_SHIFT_MASK)) == GDK_SHIFT_MASK) strip->select = DT_LIB_FILMSTRIP_SELECT_RANGE; if(strip->select != DT_LIB_FILMSTRIP_SELECT_NONE) { strip->select_id = mouse_over_id; return TRUE; } break; case DT_VIEW_REJECT: case DT_VIEW_STAR_1: case DT_VIEW_STAR_2: case DT_VIEW_STAR_3: case DT_VIEW_STAR_4: case DT_VIEW_STAR_5: { int offset = 0; if(mouse_over_id == strip->activated_image) offset = dt_collection_image_offset(mouse_over_id); dt_image_t *image = dt_image_cache_get(darktable.image_cache, mouse_over_id, 'w'); if(strip->image_over == DT_VIEW_STAR_1 && ((image->flags & 0x7) == 1)) image->flags &= ~0x7; else if(strip->image_over == DT_VIEW_REJECT && ((image->flags & 0x7) == 6)) image->flags &= ~0x7; else { image->flags &= ~0x7; image->flags |= strip->image_over; } dt_image_cache_write_release(darktable.image_cache, image, DT_IMAGE_CACHE_SAFE); dt_collection_hint_message(darktable.collection); // More than this, we need to redraw all if(mouse_over_id == strip->activated_image) if(_lib_filmstrip_imgid_in_collection(darktable.collection, mouse_over_id) == 0) dt_view_filmstrip_scroll_relative(0, offset); gtk_widget_queue_draw(darktable.view_manager->proxy.filmstrip.module->widget); return TRUE; } default: return FALSE; } } else if(e->type == GDK_2BUTTON_PRESS) { if(mouse_over_id > 0) { strip->activated_image = mouse_over_id; dt_control_signal_raise(darktable.signals, DT_SIGNAL_VIEWMANAGER_FILMSTRIP_ACTIVATE); return TRUE; } } } return FALSE; }
/* this actually does the work */ int store(dt_imageio_module_storage_t *self, struct dt_imageio_module_data_t *sdata, const int imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total, const gboolean high_quality, const gboolean upscale, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent) { dt_storage_gphoto_gui_data_t *ui = self->gui_data; gint result = 1; dt_gphoto_context_t *ctx = (dt_gphoto_context_t *)sdata; const char *ext = format->extension(fdata); char fname[PATH_MAX] = { 0 }; dt_loc_get_tmp_dir(fname, sizeof(fname)); g_strlcat(fname, "/darktable.XXXXXX.", sizeof(fname)); g_strlcat(fname, ext, sizeof(fname)); gint fd = g_mkstemp(fname); if(fd == -1) { dt_control_log("failed to create temporary image for google photos export"); return 1; } close(fd); // get metadata const dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'r'); char *title = NULL; char *summary = NULL; GList *meta_title = NULL; title = g_path_get_basename(img->filename); (g_strrstr(title, "."))[0] = '\0'; // Chop extension... meta_title = dt_metadata_get(img->id, "Xmp.dc.title", NULL); summary = meta_title != NULL ? meta_title->data : ""; dt_image_cache_read_release(darktable.image_cache, img); if(dt_imageio_export(imgid, fname, format, fdata, high_quality, upscale, FALSE, icc_type, icc_filename, icc_intent, self, sdata, num, total) != 0) { g_printerr("[gphoto] could not export to file: `%s'!\n", fname); dt_control_log(_("could not export to file `%s'!"), fname); result = 0; goto cleanup; } if(!*(ctx->album_id)) { if(ctx->album_title == NULL) { dt_control_log(_("unable to create album, no title provided")); result = 0; goto cleanup; } const gchar *album_id = gphoto_create_album(ui, ctx, ctx->album_title); if(album_id == NULL) { dt_control_log(_("unable to create album")); result = 0; goto cleanup; } g_snprintf(ctx->album_id, sizeof(ctx->album_id), "%s", album_id); } const char *photoid = gphoto_upload_photo_to_album(ctx, ctx->album_id, fname, title, summary, imgid); if(photoid == NULL) { dt_control_log(_("unable to export to google photos album")); result = 0; goto cleanup; } cleanup: g_unlink(fname); g_free(title); g_list_free_full(meta_title, &g_free); if(result) { // this makes sense only if the export was successful dt_control_log(ngettext("%d/%d exported to google photos album", "%d/%d exported to google photos album", num), num, total); } return 0; }
/* this actually does the work */ int store(dt_imageio_module_storage_t *self, struct dt_imageio_module_data_t *sdata, const int imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total, const gboolean high_quality) { gint result = 1; dt_storage_facebook_param_t *p = (dt_storage_facebook_param_t *)sdata; const char *ext = format->extension(fdata); char fname[PATH_MAX] = { 0 }; dt_loc_get_tmp_dir(fname, sizeof(fname)); g_strlcat(fname, "/darktable.XXXXXX.", sizeof(fname)); g_strlcat(fname, ext, sizeof(fname)); gint fd = g_mkstemp(fname); if(fd == -1) { dt_control_log("failed to create temporary image for facebook export"); return 1; } close(fd); // get metadata const dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'r'); char *caption = NULL; GList *caption_list = NULL; caption_list = dt_metadata_get(img->id, "Xmp.dc.title", NULL); if(caption_list != NULL) { caption = g_strdup(caption_list->data); g_list_free_full(caption_list, &g_free); } else { caption_list = dt_metadata_get(img->id, "Xmp.dc.description", NULL); if(caption_list != NULL) { caption = g_strdup(caption_list->data); g_list_free_full(caption_list, &g_free); } } dt_image_cache_read_release(darktable.image_cache, img); // facebook doesn't allow pictures bigger than FB_IMAGE_MAX_SIZExFB_IMAGE_MAX_SIZE px if(fdata->max_height == 0 || fdata->max_height > FB_IMAGE_MAX_SIZE) fdata->max_height = FB_IMAGE_MAX_SIZE; if(fdata->max_width == 0 || fdata->max_width > FB_IMAGE_MAX_SIZE) fdata->max_width = FB_IMAGE_MAX_SIZE; if(dt_imageio_export(imgid, fname, format, fdata, high_quality, FALSE, self, sdata, num, total) != 0) { g_printerr("[facebook] could not export to file: `%s'!\n", fname); dt_control_log(_("could not export to file `%s'!"), fname); result = 0; goto cleanup; } if(p->facebook_ctx->album_id == NULL) { if(p->facebook_ctx->album_title == NULL) { dt_control_log(_("unable to create album, no title provided")); result = 0; goto cleanup; } const gchar *album_id = fb_create_album(p->facebook_ctx, p->facebook_ctx->album_title, p->facebook_ctx->album_summary, p->facebook_ctx->album_permission); if(album_id == NULL) { dt_control_log(_("unable to create album")); result = 0; goto cleanup; } p->facebook_ctx->album_id = g_strdup(album_id); } const char *photoid = fb_upload_photo_to_album(p->facebook_ctx, p->facebook_ctx->album_id, fname, caption); if(photoid == NULL) { dt_control_log(_("unable to export photo to webalbum")); result = 0; goto cleanup; } cleanup: unlink(fname); g_free(caption); if(result) { // this makes sense only if the export was successful dt_control_log(_("%d/%d exported to facebook webalbum"), num, total); } return 0; }
/* update all values to reflect mouse over image id or no data at all */ static void _metadata_view_update_values(dt_lib_module_t *self) { dt_lib_metadata_view_t *d = (dt_lib_metadata_view_t *)self->data; int32_t mouse_over_id = dt_control_get_mouse_over_id(); if(mouse_over_id == -1) { const dt_view_t *cv = dt_view_manager_get_current_view(darktable.view_manager); if(cv->view((dt_view_t *)cv) == DT_VIEW_DARKROOM) { mouse_over_id = darktable.develop->image_storage.id; } else { sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select imgid from selected_images limit 1", -1, &stmt, NULL); if(sqlite3_step(stmt) == SQLITE_ROW) mouse_over_id = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); } } if(mouse_over_id >= 0) { char value[512]; char pathname[PATH_MAX] = { 0 }; const dt_image_t *img = dt_image_cache_get(darktable.image_cache, mouse_over_id, 'r'); if(!img) goto fill_minuses; if(img->film_id == -1) { dt_image_cache_read_release(darktable.image_cache, img); goto fill_minuses; } /* update all metadata */ dt_image_film_roll(img, value, sizeof(value)); _metadata_update_value(d->metadata[md_internal_filmroll], value); const int tp = 512; char tooltip[tp]; snprintf(tooltip, tp, _("double click to jump to film roll\n%s"), value); gtk_widget_set_tooltip_text(GTK_WIDGET(d->metadata[md_internal_filmroll]), tooltip); snprintf(value, sizeof(value), "%d", img->id); _metadata_update_value(d->metadata[md_internal_imgid], value); snprintf(value, sizeof(value), "%d", img->group_id); _metadata_update_value(d->metadata[md_internal_groupid], value); _metadata_update_value(d->metadata[md_internal_filename], img->filename); snprintf(value, sizeof(value), "%d", img->version); _metadata_update_value(d->metadata[md_internal_version], value); gboolean from_cache = FALSE; dt_image_full_path(img->id, pathname, sizeof(pathname), &from_cache); _metadata_update_value(d->metadata[md_internal_fullpath], pathname); snprintf(value, sizeof(value), "%s", (img->flags & DT_IMAGE_LOCAL_COPY) ? _("yes") : _("no")); _metadata_update_value(d->metadata[md_internal_local_copy], value); // TODO: decide if this should be removed for a release. maybe #ifdef'ing to only add it to git compiles? // the bits of the flags #if SHOW_FLAGS { #define EMPTY_FIELD '.' #define FALSE_FIELD '.' #define TRUE_FIELD '!' char *tooltip = NULL; char *flag_descriptions[] = { N_("unused"), N_("unused/deprecated"), N_("ldr"), N_("raw"), N_("hdr"), N_("marked for deletion"), N_("auto-applying presets applied"), N_("legacy flag. set for all new images"), N_("local copy"), N_("has .txt"), N_("has .wav") }; char *tooltip_parts[14] = { 0 }; int next_tooltip_part = 0; memset(value, EMPTY_FIELD, sizeof(value)); int stars = img->flags & 0x7; char *star_string = NULL; if(stars == 6) { value[0] = 'x'; tooltip_parts[next_tooltip_part++] = _("image rejected"); } else { value[0] = '0' + stars; tooltip_parts[next_tooltip_part++] = star_string = g_strdup_printf(ngettext("image has %d star", "image has %d stars", stars), stars); } if(img->flags & 8) { value[1] = TRUE_FIELD; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[0]); } else value[1] = FALSE_FIELD; if(img->flags & DT_IMAGE_THUMBNAIL_DEPRECATED) { value[2] = TRUE_FIELD; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[1]); } else value[2] = FALSE_FIELD; if(img->flags & DT_IMAGE_LDR) { value[3] = 'l'; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[2]); } if(img->flags & DT_IMAGE_RAW) { value[4] = 'r'; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[3]); } if(img->flags & DT_IMAGE_HDR) { value[5] = 'h'; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[4]); } if(img->flags & DT_IMAGE_REMOVE) { value[6] = 'd'; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[5]); } if(img->flags & DT_IMAGE_AUTO_PRESETS_APPLIED) { value[7] = 'a'; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[6]); } if(img->flags & DT_IMAGE_NO_LEGACY_PRESETS) { value[8] = 'p'; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[7]); } if(img->flags & DT_IMAGE_LOCAL_COPY) { value[9] = 'c'; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[8]); } if(img->flags & DT_IMAGE_HAS_TXT) { value[10] = 't'; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[9]); } if(img->flags & DT_IMAGE_HAS_WAV) { value[11] = 'w'; tooltip_parts[next_tooltip_part++] = _(flag_descriptions[10]); } static const struct { char *tooltip; char flag; } loaders[] = { { N_("unknown"), EMPTY_FIELD}, { N_("tiff"), 't'}, { N_("png"), 'p'}, { N_("j2k"), 'J'}, { N_("jpeg"), 'j'}, { N_("exr"), 'e'}, { N_("rgbe"), 'R'}, { N_("pfm"), 'P'}, { N_("GraphicsMagick"), 'g'}, { N_("rawspeed"), 'r'} }; int loader = (unsigned int)img->loader < sizeof(loaders) / sizeof(*loaders) ? img->loader : 0; value[12] = loaders[loader].flag; char *loader_tooltip = g_strdup_printf(_("loader: %s"), _(loaders[loader].tooltip)); tooltip_parts[next_tooltip_part++] = loader_tooltip; value[13] = '\0'; tooltip = g_strjoinv("\n", tooltip_parts); g_free(loader_tooltip); _metadata_update_value(d->metadata[md_internal_flags], value); gtk_widget_set_tooltip_text(GTK_WIDGET(d->metadata[md_internal_flags]), tooltip); g_free(star_string); g_free(tooltip); #undef EMPTY_FIELD #undef FALSE_FIELD #undef TRUE_FIELD } #endif // SHOW_FLAGS /* EXIF */ _metadata_update_value_end(d->metadata[md_exif_model], img->camera_alias); _metadata_update_value_end(d->metadata[md_exif_lens], img->exif_lens); _metadata_update_value_end(d->metadata[md_exif_maker], img->camera_maker); snprintf(value, sizeof(value), "F/%.1f", img->exif_aperture); _metadata_update_value(d->metadata[md_exif_aperture], value); if(img->exif_exposure <= 0.5) snprintf(value, sizeof(value), "1/%.0f", 1.0 / img->exif_exposure); else snprintf(value, sizeof(value), "%.1f''", img->exif_exposure); _metadata_update_value(d->metadata[md_exif_exposure], value); snprintf(value, sizeof(value), "%.0f mm", img->exif_focal_length); _metadata_update_value(d->metadata[md_exif_focal_length], value); if(isnan(img->exif_focus_distance) || fpclassify(img->exif_focus_distance) == FP_ZERO) { _metadata_update_value(d->metadata[md_exif_focus_distance], NODATA_STRING); } else { snprintf(value, sizeof(value), "%.2f m", img->exif_focus_distance); _metadata_update_value(d->metadata[md_exif_focus_distance], value); } snprintf(value, sizeof(value), "%.0f", img->exif_iso); _metadata_update_value(d->metadata[md_exif_iso], value); struct tm tt_exif = { 0 }; if(sscanf(img->exif_datetime_taken, "%d:%d:%d %d:%d:%d", &tt_exif.tm_year, &tt_exif.tm_mon, &tt_exif.tm_mday, &tt_exif.tm_hour, &tt_exif.tm_min, &tt_exif.tm_sec) == 6) { char datetime[200]; tt_exif.tm_year -= 1900; tt_exif.tm_mon--; tt_exif.tm_isdst = -1; mktime(&tt_exif); // just %c is too long and includes a time zone that we don't know from exif strftime(datetime, sizeof(datetime), "%a %x %X", &tt_exif); _metadata_update_value(d->metadata[md_exif_datetime], datetime); } else _metadata_update_value(d->metadata[md_exif_datetime], img->exif_datetime_taken); snprintf(value, sizeof(value), "%d", img->height); _metadata_update_value(d->metadata[md_exif_height], value); snprintf(value, sizeof(value), "%d", img->width); _metadata_update_value(d->metadata[md_exif_width], value); /* XMP */ GList *res; if((res = dt_metadata_get(img->id, "Xmp.dc.title", NULL)) != NULL) { snprintf(value, sizeof(value), "%s", (char *)res->data); _filter_non_printable(value, sizeof(value)); g_list_free_full(res, &g_free); } else snprintf(value, sizeof(value), NODATA_STRING); _metadata_update_value(d->metadata[md_xmp_title], value); if((res = dt_metadata_get(img->id, "Xmp.dc.creator", NULL)) != NULL) { snprintf(value, sizeof(value), "%s", (char *)res->data); _filter_non_printable(value, sizeof(value)); g_list_free_full(res, &g_free); } else snprintf(value, sizeof(value), NODATA_STRING); _metadata_update_value(d->metadata[md_xmp_creator], value); if((res = dt_metadata_get(img->id, "Xmp.dc.rights", NULL)) != NULL) { snprintf(value, sizeof(value), "%s", (char *)res->data); _filter_non_printable(value, sizeof(value)); g_list_free_full(res, &g_free); } else snprintf(value, sizeof(value), NODATA_STRING); _metadata_update_value(d->metadata[md_xmp_rights], value); /* geotagging */ /* latitude */ if(isnan(img->latitude)) { _metadata_update_value(d->metadata[md_geotagging_lat], NODATA_STRING); } else { if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location")) { gchar *latitude = dt_util_latitude_str(img->latitude); _metadata_update_value(d->metadata[md_geotagging_lat], latitude); g_free(latitude); } else { gchar NS = img->latitude < 0 ? 'S' : 'N'; snprintf(value, sizeof(value), "%c %09.6f", NS, fabs(img->latitude)); _metadata_update_value(d->metadata[md_geotagging_lat], value); } } /* longitude */ if(isnan(img->longitude)) { _metadata_update_value(d->metadata[md_geotagging_lon], NODATA_STRING); } else { if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location")) { gchar *longitude = dt_util_longitude_str(img->longitude); _metadata_update_value(d->metadata[md_geotagging_lon], longitude); g_free(longitude); } else { gchar EW = img->longitude < 0 ? 'W' : 'E'; snprintf(value, sizeof(value), "%c %010.6f", EW, fabs(img->longitude)); _metadata_update_value(d->metadata[md_geotagging_lon], value); } } /* elevation */ if(isnan(img->elevation)) { _metadata_update_value(d->metadata[md_geotagging_ele], NODATA_STRING); } else { if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location")) { gchar *elevation = dt_util_elevation_str(img->elevation); _metadata_update_value(d->metadata[md_geotagging_ele], elevation); g_free(elevation); } else { snprintf(value, sizeof(value), "%.2f %s", img->elevation, _("m")); _metadata_update_value(d->metadata[md_geotagging_ele], value); } } /* release img */ dt_image_cache_read_release(darktable.image_cache, img); #ifdef USE_LUA dt_lua_async_call_alien(lua_update_metadata, 0,NULL,NULL, LUA_ASYNC_TYPENAME,"void*",self, LUA_ASYNC_TYPENAME,"int32_t",mouse_over_id,LUA_ASYNC_DONE); #endif } return; /* reset */ fill_minuses: for(int k = 0; k < md_size; k++) _metadata_update_value(d->metadata[k], NODATA_STRING); #ifdef USE_LUA dt_lua_async_call_alien(lua_update_metadata, 0,NULL,NULL, LUA_ASYNC_TYPENAME,"void*",self, LUA_ASYNC_TYPENAME,"int32_t",-1,LUA_ASYNC_DONE); #endif }
/* 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); }
gboolean _variable_get_value(dt_variables_params_t *params, gchar *variable, gchar *value, size_t value_len) { const gchar *file_ext = NULL; gboolean got_value = FALSE; struct tm tim; localtime_r(¶ms->data->time, &tim); const gchar *homedir = dt_loc_get_home_dir(NULL); gchar *pictures_folder = NULL; if(g_get_user_special_dir(G_USER_DIRECTORY_PICTURES) == NULL) pictures_folder = g_build_path(G_DIR_SEPARATOR_S, homedir, "Pictures", (char *)NULL); else pictures_folder = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_PICTURES)); if(params->filename) { file_ext = (g_strrstr(params->filename, ".") + 1); if(file_ext == (gchar *)1) file_ext = params->filename + strlen(params->filename); } /* image exif time */ gboolean have_exif_tm = FALSE; int exif_iso = 100; char camera_maker[64]; char camera_alias[64]; int version = 0; int stars = 0; struct tm exif_tm = { 0 }; if(params->imgid) { const dt_image_t *img = dt_image_cache_get(darktable.image_cache, params->imgid, 'r'); if(sscanf(img->exif_datetime_taken, "%d:%d:%d %d:%d:%d", &exif_tm.tm_year, &exif_tm.tm_mon, &exif_tm.tm_mday, &exif_tm.tm_hour, &exif_tm.tm_min, &exif_tm.tm_sec) == 6) { exif_tm.tm_year -= 1900; exif_tm.tm_mon--; have_exif_tm = TRUE; } exif_iso = img->exif_iso; g_strlcpy(camera_maker, img->camera_maker, sizeof(camera_maker)); g_strlcpy(camera_alias, img->camera_alias, sizeof(camera_alias)); version = img->version; stars = (img->flags & 0x7); if(stars == 6) stars = -1; dt_image_cache_read_release(darktable.image_cache, img); } else if (params->data->exif_time) { localtime_r(¶ms->data->exif_time, &exif_tm); have_exif_tm = TRUE; } if(g_strcmp0(variable, "$(YEAR)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.4d", tim.tm_year + 1900); else if(g_strcmp0(variable, "$(MONTH)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.2d", tim.tm_mon + 1); else if(g_strcmp0(variable, "$(DAY)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.2d", tim.tm_mday); else if(g_strcmp0(variable, "$(HOUR)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.2d", tim.tm_hour); else if(g_strcmp0(variable, "$(MINUTE)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.2d", tim.tm_min); else if(g_strcmp0(variable, "$(SECOND)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.2d", tim.tm_sec); else if(g_strcmp0(variable, "$(EXIF_YEAR)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.4d", (have_exif_tm ? exif_tm.tm_year : tim.tm_year) + 1900); else if(g_strcmp0(variable, "$(EXIF_MONTH)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.2d", (have_exif_tm ? exif_tm.tm_mon : tim.tm_mon) + 1); else if(g_strcmp0(variable, "$(EXIF_DAY)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.2d", (have_exif_tm ? exif_tm.tm_mday : tim.tm_mday)); else if(g_strcmp0(variable, "$(EXIF_HOUR)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.2d", (have_exif_tm ? exif_tm.tm_hour : tim.tm_hour)); else if(g_strcmp0(variable, "$(EXIF_MINUTE)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.2d", (have_exif_tm ? exif_tm.tm_min : tim.tm_min)); else if(g_strcmp0(variable, "$(EXIF_SECOND)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.2d", (have_exif_tm ? exif_tm.tm_sec : tim.tm_sec)); else if(g_strcmp0(variable, "$(EXIF_ISO)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%d", exif_iso); else if(g_strcmp0(variable, "$(MAKER)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%s", camera_maker); else if(g_strcmp0(variable, "$(MODEL)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%s", camera_alias); else if(g_strcmp0(variable, "$(ID)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%d", params->imgid); else if(g_strcmp0(variable, "$(VERSION)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%d", version); else if(g_strcmp0(variable, "$(JOBCODE)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%s", params->jobcode); else if(g_strcmp0(variable, "$(ROLL_NAME)") == 0 && params->filename && (got_value = TRUE)) { gchar *dirname = g_path_get_dirname(params->filename); gchar *basename = g_path_get_basename(dirname); snprintf(value, value_len, "%s", basename); g_free(basename); g_free(dirname); } else if(g_strcmp0(variable, "$(FILE_DIRECTORY)") == 0 && params->filename && (got_value = TRUE)) { gchar *dirname = g_path_get_dirname(params->filename); snprintf(value, value_len, "%s", dirname); g_free(dirname); } // undocumented : backward compatibility else if(g_strcmp0(variable, "$(FILE_FOLDER)") == 0 && params->filename && (got_value = TRUE)) { gchar *dirname = g_path_get_dirname(params->filename); snprintf(value, value_len, "%s", dirname); g_free(dirname); } else if(g_strcmp0(variable, "$(FILE_NAME)") == 0 && params->filename && (got_value = TRUE)) { gchar *basename = g_path_get_basename(params->filename); snprintf(value, value_len, "%s", basename); g_free(basename); if(g_strrstr(value, ".")) *(g_strrstr(value, ".")) = 0; } else if(g_strcmp0(variable, "$(FILE_EXTENSION)") == 0 && params->filename && (got_value = TRUE)) snprintf(value, value_len, "%s", file_ext); else if(g_strcmp0(variable, "$(SEQUENCE)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%.4d", params->sequence >= 0 ? params->sequence : params->data->sequence); else if(g_strcmp0(variable, "$(USERNAME)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%s", g_get_user_name()); else if(g_strcmp0(variable, "$(HOME_FOLDER)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%s", homedir); // undocumented : backward compatibility else if(g_strcmp0(variable, "$(HOME)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%s", homedir); else if(g_strcmp0(variable, "$(PICTURES_FOLDER)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%s", pictures_folder); else if(g_strcmp0(variable, "$(DESKTOP_FOLDER)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%s", g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP)); // undocumented : backward compatibility else if(g_strcmp0(variable, "$(DESKTOP)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%s", g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP)); else if(g_strcmp0(variable, "$(STARS)") == 0 && (got_value = TRUE)) snprintf(value, value_len, "%d", stars); else if(g_strcmp0(variable, "$(LABELS)") == 0 && (got_value = TRUE)) { // TODO: currently we concatenate all the color labels with a ',' as a separator. Maybe it's better to // only use the first/last label? GList *res = dt_metadata_get(params->imgid, "Xmp.darktable.colorlabels", NULL); res = g_list_first(res); if(res != NULL) { GList *labels = NULL; do { labels = g_list_append(labels, (char *)(_(dt_colorlabels_to_string(GPOINTER_TO_INT(res->data))))); } while((res = g_list_next(res)) != NULL); char *str = dt_util_glist_to_str(",", labels); g_list_free(labels); snprintf(value, value_len, "%s", str); g_free(str); } else { snprintf(value, value_len, "%s", _("none")); } g_list_free(res); } else if(g_strcmp0(variable, "$(TITLE)") == 0 && params->filename && (got_value = TRUE)) { GList *res = dt_metadata_get(params->imgid, "Xmp.dc.title", NULL); res = g_list_first(res); if(res != NULL) { snprintf(value, value_len, "%s", (char *)res->data); } else { snprintf(value, value_len, "%s", _("none")); } g_list_free_full(res, &g_free); } else if(g_strcmp0(variable, "$(CREATOR)") == 0 && params->filename && (got_value = TRUE)) { GList *res = dt_metadata_get(params->imgid, "Xmp.dc.creator", NULL); res = g_list_first(res); if(res != NULL) { snprintf(value, value_len, "%s", (char *)res->data); } else { snprintf(value, value_len, "%s", _("none")); } g_list_free_full(res, &g_free); } else if(g_strcmp0(variable, "$(PUBLISHER)") == 0 && params->filename && (got_value = TRUE)) { GList *res = dt_metadata_get(params->imgid, "Xmp.dc.publisher", NULL); res = g_list_first(res); if(res != NULL) { snprintf(value, value_len, "%s", (char *)res->data); } else { snprintf(value, value_len, "%s", _("none")); } g_list_free_full(res, &g_free); } else if(g_strcmp0(variable, "$(RIGHTS)") == 0 && params->filename && (got_value = TRUE)) { GList *res = dt_metadata_get(params->imgid, "Xmp.dc.rights", NULL); res = g_list_first(res); if(res != NULL) { snprintf(value, value_len, "%s", (char *)res->data); } else { snprintf(value, value_len, "%s", _("none")); } g_list_free_full(res, &g_free); } g_free(pictures_folder); g_free((gchar *)homedir); return got_value; }
int main(int argc, char *arg[]) { bindtextdomain(GETTEXT_PACKAGE, DARKTABLE_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); if(!gtk_parse_args(&argc, &arg)) exit(1); // parse command line arguments char *input_filename = NULL; char *xmp_filename = NULL; char *output_filename = NULL; int file_counter = 0; int width = 0, height = 0, bpp = 0; gboolean verbose = FALSE, high_quality = TRUE, upscale = FALSE; int k; for(k = 1; k < argc; k++) { if(arg[k][0] == '-') { if(!strcmp(arg[k], "--help")) { usage(arg[0]); exit(1); } else if(!strcmp(arg[k], "--version")) { printf("this is darktable-cli %s\ncopyright (c) 2012-2016 johannes hanika, tobias ellinghaus\n", darktable_package_version); exit(1); } else if(!strcmp(arg[k], "--width") && argc > k + 1) { k++; width = MAX(atoi(arg[k]), 0); } else if(!strcmp(arg[k], "--height") && argc > k + 1) { k++; height = MAX(atoi(arg[k]), 0); } else if(!strcmp(arg[k], "--bpp") && argc > k + 1) { k++; bpp = MAX(atoi(arg[k]), 0); fprintf(stderr, "%s %d\n", _("TODO: sorry, due to API restrictions we currently cannot set the BPP to"), bpp); } else if(!strcmp(arg[k], "--hq") && argc > k + 1) { k++; gchar *str = g_ascii_strup(arg[k], -1); if(!g_strcmp0(str, "0") || !g_strcmp0(str, "FALSE")) high_quality = FALSE; else if(!g_strcmp0(str, "1") || !g_strcmp0(str, "TRUE")) high_quality = TRUE; else { fprintf(stderr, "%s: %s\n", _("unknown option for --hq"), arg[k]); usage(arg[0]); exit(1); } g_free(str); } else if(!strcmp(arg[k], "--upscale") && argc > k + 1) { k++; gchar *str = g_ascii_strup(arg[k], -1); if(!g_strcmp0(str, "0") || !g_strcmp0(str, "FALSE")) upscale = FALSE; else if(!g_strcmp0(str, "1") || !g_strcmp0(str, "TRUE")) upscale= TRUE; else { fprintf(stderr, "%s: %s\n", _("unknown option for --upscale"), arg[k]); usage(arg[0]); exit(1); } g_free(str); } else if(!strcmp(arg[k], "-v") || !strcmp(arg[k], "--verbose")) { verbose = TRUE; } else if(!strcmp(arg[k], "--core")) { // everything from here on should be passed to the core k++; break; } } else { if(file_counter == 0) input_filename = arg[k]; else if(file_counter == 1) xmp_filename = arg[k]; else if(file_counter == 2) output_filename = arg[k]; file_counter++; } } int m_argc = 0; char **m_arg = malloc((5 + argc - k + 1) * sizeof(char *)); m_arg[m_argc++] = "darktable-cli"; m_arg[m_argc++] = "--library"; m_arg[m_argc++] = ":memory:"; m_arg[m_argc++] = "--conf"; m_arg[m_argc++] = "write_sidecar_files=FALSE"; for(; k < argc; k++) m_arg[m_argc++] = arg[k]; m_arg[m_argc] = NULL; if(file_counter < 2 || file_counter > 3) { usage(arg[0]); free(m_arg); exit(1); } else if(file_counter == 2) { // no xmp file given output_filename = xmp_filename; xmp_filename = NULL; } if(g_file_test(output_filename, G_FILE_TEST_IS_DIR)) { fprintf(stderr, _("error: output file is a directory. please specify file name")); fprintf(stderr, "\n"); free(m_arg); exit(1); } // the output file already exists, so there will be a sequence number added if(g_file_test(output_filename, G_FILE_TEST_EXISTS)) { fprintf(stderr, "%s\n", _("output file already exists, it will get renamed")); } // init dt without gui: if(dt_init(m_argc, m_arg, 0, NULL)) { free(m_arg); exit(1); } GList *id_list = NULL; if(g_file_test(input_filename, G_FILE_TEST_IS_DIR)) { int filmid = dt_film_import(input_filename); if(!filmid) { fprintf(stderr, _("error: can't open folder %s"), input_filename); fprintf(stderr, "\n"); free(m_arg); exit(1); } id_list = dt_film_get_image_ids(filmid); } else { dt_film_t film; int id = 0; int filmid = 0; gchar *directory = g_path_get_dirname(input_filename); filmid = dt_film_new(&film, directory); id = dt_image_import(filmid, input_filename, TRUE); if(!id) { fprintf(stderr, _("error: can't open file %s"), input_filename); fprintf(stderr, "\n"); free(m_arg); exit(1); } g_free(directory); id_list = g_list_append(id_list, GINT_TO_POINTER(id)); } int total = g_list_length(id_list); if(total == 0) { fprintf(stderr, _("no images to export, aborting\n")); free(m_arg); exit(1); } // attach xmp, if requested: if(xmp_filename) { for(GList *iter = id_list; iter; iter = g_list_next(iter)) { int id = GPOINTER_TO_INT(iter->data); dt_image_t *image = dt_image_cache_get(darktable.image_cache, id, 'w'); dt_exif_xmp_read(image, xmp_filename, 1); // don't write new xmp: dt_image_cache_write_release(darktable.image_cache, image, DT_IMAGE_CACHE_RELAXED); } } // print the history stack. only look at the first image and assume all got the same processing applied if(verbose) { int id = GPOINTER_TO_INT(id_list->data); gchar *history = dt_history_get_items_as_string(id); if(history) printf("%s\n", history); else printf("[%s]\n", _("empty history stack")); } // try to find out the export format from the output_filename char *ext = output_filename + strlen(output_filename); while(ext > output_filename && *ext != '.') ext--; *ext = '\0'; ext++; if(!strcmp(ext, "jpg")) ext = "jpeg"; if(!strcmp(ext, "tif")) ext = "tiff"; // init the export data structures dt_imageio_module_format_t *format; dt_imageio_module_storage_t *storage; dt_imageio_module_data_t *sdata, *fdata; storage = dt_imageio_get_storage_by_name("disk"); // only exporting to disk makes sense if(storage == NULL) { fprintf( stderr, "%s\n", _("cannot find disk storage module. please check your installation, something seems to be broken.")); free(m_arg); exit(1); } sdata = storage->get_params(storage); if(sdata == NULL) { fprintf(stderr, "%s\n", _("failed to get parameters from storage module, aborting export ...")); free(m_arg); exit(1); } // and now for the really ugly hacks. don't tell your children about this one or they won't sleep at night // any longer ... g_strlcpy((char *)sdata, output_filename, DT_MAX_PATH_FOR_PARAMS); // all is good now, the last line didn't happen. format = dt_imageio_get_format_by_name(ext); if(format == NULL) { fprintf(stderr, _("unknown extension '.%s'"), ext); fprintf(stderr, "\n"); free(m_arg); exit(1); } fdata = format->get_params(format); if(fdata == NULL) { fprintf(stderr, "%s\n", _("failed to get parameters from format module, aborting export ...")); free(m_arg); exit(1); } uint32_t w, h, fw, fh, sw, sh; fw = fh = sw = sh = 0; storage->dimension(storage, sdata, &sw, &sh); format->dimension(format, fdata, &fw, &fh); if(sw == 0 || fw == 0) w = sw > fw ? sw : fw; else w = sw < fw ? sw : fw; if(sh == 0 || fh == 0) h = sh > fh ? sh : fh; else h = sh < fh ? sh : fh; fdata->max_width = width; fdata->max_height = height; fdata->max_width = (w != 0 && fdata->max_width > w) ? w : fdata->max_width; fdata->max_height = (h != 0 && fdata->max_height > h) ? h : fdata->max_height; fdata->style[0] = '\0'; fdata->style_append = 0; if(storage->initialize_store) { storage->initialize_store(storage, sdata, &format, &fdata, &id_list, high_quality, upscale); format->set_params(format, fdata, format->params_size(format)); storage->set_params(storage, sdata, storage->params_size(storage)); } // TODO: add a callback to set the bpp without going through the config int num = 1; for(GList *iter = id_list; iter; iter = g_list_next(iter), num++) { int id = GPOINTER_TO_INT(iter->data); storage->store(storage, sdata, id, format, fdata, num, total, high_quality, upscale); } // cleanup time if(storage->finalize_store) storage->finalize_store(storage, sdata); storage->free_params(storage, sdata); format->free_params(format, fdata); g_list_free(id_list); dt_cleanup(); free(m_arg); }
static const dt_image_t *checkreadimage(lua_State *L, int index) { dt_lua_image_t imgid; luaA_to(L, dt_lua_image_t, &imgid, index); return dt_image_cache_get(darktable.image_cache, imgid, 'r'); }
void finalize_store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *params) { dt_imageio_email_t *d = (dt_imageio_email_t *)params; const gchar *imageBodyFormat = " - %s (%s)\\n"; // filename, exif oneliner const gint nb_images = g_list_length(d->images); const gint argc = 5 + (2 * nb_images); char **argv = g_malloc0(sizeof(char *) * (argc + 1)); gchar *body = NULL; argv[0] = "xdg-email"; argv[1] = "--subject"; argv[2] = _("images exported from darktable"); argv[3] = "--body"; int n = 5; for(GList *iter = d->images; iter; iter = g_list_next(iter)) { gchar exif[256] = { 0 }; _email_attachment_t *attachment = (_email_attachment_t *)iter->data; gchar *filename = g_path_get_basename(attachment->file); const dt_image_t *img = dt_image_cache_get(darktable.image_cache, attachment->imgid, 'r'); dt_image_print_exif(img, exif, sizeof(exif)); dt_image_cache_read_release(darktable.image_cache, img); gchar *imgbody = g_strdup_printf(imageBodyFormat, filename, exif); if (body != NULL) { gchar *body_bak = body; body = g_strconcat(body_bak, imgbody, NULL); g_free(body_bak); } else { body = g_strdup(imgbody); } g_free(imgbody); g_free(filename); argv[n] = g_strdup("--attach"); // use attachment->file directly as we need to freed it, and this way it will be // freed as part of the argument release after the spawn below. argv[n+1] = attachment->file; n += 2; } g_list_free_full(d->images, g_free); d->images = NULL; argv[4] = body; argv[argc] = NULL; fprintf(stderr, "[email] launching '"); for (int k=0; k<argc; k++) fprintf(stderr, " %s", argv[k]); fprintf(stderr, "'\n"); gint exit_status = 0; g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, &exit_status, NULL); for (int k=4; k<argc; k++) g_free(argv[k]); g_free(argv); if(exit_status) { dt_control_log(_("could not launch email client!")); } }
static dt_image_t *checkwriteimage(lua_State *L, int index) { dt_lua_image_t imgid; luaA_to(L, dt_lua_image_t, &imgid, index); return dt_image_cache_get(darktable.image_cache, imgid, 'w'); }
int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total, const gboolean high_quality, const gboolean upscale, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent) { dt_storage_piwigo_gui_data_t *ui = self->gui_data; gint result = 0; const char *ext = format->extension(fdata); // Let's upload image... /* construct a temporary file name */ char fname[PATH_MAX] = { 0 }; dt_loc_get_tmp_dir(fname, sizeof(fname)); g_strlcat(fname, "/darktable.XXXXXX.", sizeof(fname)); g_strlcat(fname, ext, sizeof(fname)); char *caption = NULL; char *description = NULL; char *author = NULL; gint fd = g_mkstemp(fname); if(fd == -1) { dt_control_log("failed to create temporary image for piwigo export"); fprintf(stderr, "failed to create tempfile: %s\n", fname); return 1; } close(fd); const dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'r'); // If title is not existing, then use the filename without extension. If not, then use title instead GList *title = dt_metadata_get(img->id, "Xmp.dc.title", NULL); if(title != NULL) { caption = g_strdup(title->data); g_list_free_full(title, &g_free); } else { caption = g_path_get_basename(img->filename); (g_strrstr(caption, "."))[0] = '\0'; // chop extension... } GList *desc = dt_metadata_get(img->id, "Xmp.dc.description", NULL); if(desc != NULL) { description = g_strdup(desc->data); g_list_free_full(desc, &g_free); } dt_image_cache_read_release(darktable.image_cache, img); GList *auth = dt_metadata_get(img->id, "Xmp.dc.creator", NULL); if(auth != NULL) { author = g_strdup(auth->data); g_list_free_full(auth, &g_free); } if(dt_imageio_export(imgid, fname, format, fdata, high_quality, upscale, FALSE, icc_type, icc_filename, icc_intent, self, sdata, num, total) != 0) { fprintf(stderr, "[imageio_storage_piwigo] could not export to file: `%s'!\n", fname); dt_control_log(_("could not export to file `%s'!"), fname); result = 1; goto cleanup; } dt_pthread_mutex_lock(&darktable.plugin_threadsafe); { gboolean status = TRUE; dt_storage_piwigo_params_t *p = (dt_storage_piwigo_params_t *)sdata; if(p->export_tags) { GList *tags_list = dt_tag_get_list(imgid); if(p->tags) g_free(p->tags); p->tags = dt_util_glist_to_str(",", tags_list); g_list_free_full(tags_list, g_free); } if(p->new_album) { status = _piwigo_api_create_new_album(p); if(!status) dt_control_log(_("cannot create a new piwigo album!")); } if(status) { status = _piwigo_api_upload_photo(p, fname, author, caption, description); if(!status) { fprintf(stderr, "[imageio_storage_piwigo] could not upload to piwigo!\n"); dt_control_log(_("could not upload to piwigo!")); result = 1; } else if (p->new_album) { // we do not want to create more albums when multiple upload p->new_album = FALSE; _piwigo_refresh_albums(ui, p->album); } } } dt_pthread_mutex_unlock(&darktable.plugin_threadsafe); cleanup: // And remove from filesystem.. g_unlink(fname); g_free(caption); g_free(description); g_free(author); if(!result) { // this makes sense only if the export was successful dt_control_log(ngettext("%d/%d exported to piwigo webalbum", "%d/%d exported to piwigo webalbum", num), num, total); } return result; }