|
| 1 | +/* |
| 2 | + This file is part of darktable, |
| 3 | + Copyright (C) 2025 darktable developers. |
| 4 | +
|
| 5 | + darktable is free software: you can redistribute it and/or modify |
| 6 | + it under the terms of the GNU General Public License as published by |
| 7 | + the Free Software Foundation, either version 3 of the License, or |
| 8 | + (at your option) any later version. |
| 9 | +
|
| 10 | + darktable is distributed in the hope that it will be useful, |
| 11 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + GNU General Public License for more details. |
| 14 | +
|
| 15 | + You should have received a copy of the GNU General Public License |
| 16 | + along with darktable. If not, see <http://www.gnu.org/licenses/>. |
| 17 | +*/ |
| 18 | + |
| 19 | +static void _filemap_segment(dt_segmentation_t *seg, char *filename) |
| 20 | +{ |
| 21 | + /* basically copy&paste from imageio_pfm.c |
| 22 | + inspect that code for details |
| 23 | + */ |
| 24 | + float *readbuf = NULL; |
| 25 | + if(!filename || filename[0] == 0) return; |
| 26 | + FILE *f = g_fopen(filename, "r"); |
| 27 | + if(f == NULL) goto error; |
| 28 | + |
| 29 | + seg->threshold = 32; |
| 30 | + int ret = 0; |
| 31 | + float scale_factor; |
| 32 | + char head[2] = { 'X', 'X' }; |
| 33 | + |
| 34 | + ret = fscanf(f, "%c%c\n", head, head + 1); |
| 35 | + if(ret != 2 || head[0] != 'P') goto error; |
| 36 | + |
| 37 | + if(head[1] == 'F') seg->segments = 3; |
| 38 | + else if(head[1] == 'f') seg->segments = 1; |
| 39 | + else goto error; |
| 40 | + |
| 41 | + int read_byte; |
| 42 | + gboolean made_by_photoshop = TRUE; |
| 43 | + for(;;) |
| 44 | + { |
| 45 | + read_byte = fgetc(f); |
| 46 | + if((read_byte == '\n') || (read_byte == EOF)) |
| 47 | + break; |
| 48 | + if(read_byte < '0') // easy way to match all whitespaces |
| 49 | + { |
| 50 | + made_by_photoshop = FALSE; // if present, the file is not saved by Photoshop |
| 51 | + break; |
| 52 | + } |
| 53 | + } |
| 54 | + fseek(f, 3, SEEK_SET); |
| 55 | + |
| 56 | + char width_string[10] = { 0 }; |
| 57 | + char height_string[10] = { 0 }; |
| 58 | + char scale_factor_string[64] = { 0 }; |
| 59 | + ret = fscanf(f, "%9s %9s %63s%*[^\n]", width_string, height_string, scale_factor_string); |
| 60 | + if(ret != 3) goto error; |
| 61 | + |
| 62 | + seg->width = strtol(width_string, NULL, 0); |
| 63 | + seg->height = strtol(height_string, NULL, 0); |
| 64 | + scale_factor = g_ascii_strtod(scale_factor_string, NULL); |
| 65 | + |
| 66 | + if(seg->width <= 0 || seg->height <= 0) goto error; |
| 67 | + |
| 68 | + ret = fread(&ret, sizeof(char), 1, f); |
| 69 | + if(ret != 1) goto error; |
| 70 | + |
| 71 | + int swap_byte_order = (scale_factor >= 0.0) ^ (G_BYTE_ORDER == G_BIG_ENDIAN); |
| 72 | + const size_t npixels = (size_t)seg->width * seg->height; |
| 73 | + |
| 74 | + readbuf = dt_alloc_align_float(npixels * 4); |
| 75 | + if(!readbuf) goto error; |
| 76 | + for(int i = 0; i < seg->segments; i++) |
| 77 | + seg->map[i] = dt_calloc_align_type(uint8_t, (size_t)seg->width * seg->height); |
| 78 | + |
| 79 | + union { float as_float; guint32 as_int; } value; |
| 80 | + |
| 81 | + if(seg->segments == 3) |
| 82 | + { |
| 83 | + ret = fread(readbuf, 3 * sizeof(float), npixels, f); |
| 84 | + size_t target_row = 0; |
| 85 | + |
| 86 | + DT_OMP_FOR(collapse(2)) |
| 87 | + for(size_t row = 0; row < seg->height; row++) |
| 88 | + { |
| 89 | + for(size_t column = 0; column < seg->width; column++) |
| 90 | + { |
| 91 | + if(made_by_photoshop) target_row = row; |
| 92 | + else target_row = seg->height - 1 - row; |
| 93 | + |
| 94 | + for_three_channels(c) |
| 95 | + { |
| 96 | + value.as_float = readbuf[3 * (target_row * seg->width + column) + c]; |
| 97 | + if(swap_byte_order) value.as_int = GUINT32_SWAP_LE_BE(value.as_int); |
| 98 | + |
| 99 | + if(seg->map[c]) |
| 100 | + seg->map[c][row*seg->width + column] = CLIP(value.as_float) * 255.0f; |
| 101 | + } |
| 102 | + } |
| 103 | + } |
| 104 | + } |
| 105 | + else |
| 106 | + { |
| 107 | + ret = fread(readbuf, sizeof(float), npixels, f); |
| 108 | + size_t target_row = 0; |
| 109 | + |
| 110 | + DT_OMP_FOR(collapse(2)) |
| 111 | + for(size_t row = 0; row < seg->height; row++) |
| 112 | + { |
| 113 | + for(size_t column = 0; column < seg->width; column++) |
| 114 | + { |
| 115 | + if(made_by_photoshop) target_row = row; |
| 116 | + else target_row = seg->height - 1 - row; |
| 117 | + value.as_float = readbuf[target_row * seg->width + column]; |
| 118 | + if(swap_byte_order) value.as_int = GUINT32_SWAP_LE_BE(value.as_int); |
| 119 | + |
| 120 | + if(seg->map[0]) |
| 121 | + seg->map[0][row*seg->width + column] = CLIP(value.as_float) * 255.0f; |
| 122 | + } |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + fclose(f); |
| 127 | + dt_free_align(readbuf); |
| 128 | + |
| 129 | + dt_print(DT_DEBUG_PIPE, "file='%s' %d map segments %dx%d provided hash=%"PRIx64, |
| 130 | + filename, seg->segments, seg->width, seg->height, seg->hash); |
| 131 | + dt_control_log(_("%d filemap segments %dx%d provided"), seg->segments, seg->width, seg->height); |
| 132 | + return; |
| 133 | + |
| 134 | +error: |
| 135 | + dt_print(DT_DEBUG_ALWAYS, "can't read image map file '%s'", filename ? filename : "???"); |
| 136 | + dt_control_log(_("can't read image map file '%s'"), filename ? filename : "???"); |
| 137 | + if(f) fclose(f); |
| 138 | + |
| 139 | + dt_free_align(readbuf); |
| 140 | + for(int i = 0; i < seg->segments; i++) dt_free_align(seg->map[i]); |
| 141 | + seg->width = seg->height = seg->segments = 0; |
| 142 | +} |
| 143 | + |
| 144 | +static int _check_extension(const struct dirent *namestruct) |
| 145 | +{ |
| 146 | + const char *filename = namestruct->d_name; |
| 147 | + int res = 0; |
| 148 | + if(!filename || !filename[0]) return res; |
| 149 | + char *p = g_strrstr(filename,"."); |
| 150 | + if(!p) return res; |
| 151 | + char *fext = g_ascii_strdown(g_strdup(p), -1); |
| 152 | + if(!g_strcmp0(fext, ".pfm")) res = 1; |
| 153 | + g_free(fext); |
| 154 | + return res; |
| 155 | +} |
| 156 | + |
| 157 | +static void _update_filepath(dt_iop_module_t *self) |
| 158 | +{ |
| 159 | + dt_iop_segmap_gui_data_t *g = self->gui_data; |
| 160 | + dt_iop_segmap_params_t *p = self->params; |
| 161 | + if(!p->path[0] || !p->file[0]) |
| 162 | + { |
| 163 | + dt_bauhaus_combobox_clear(g->file); |
| 164 | + return; |
| 165 | + } |
| 166 | + |
| 167 | + if(!dt_bauhaus_combobox_set_from_text(g->file, p->file)) |
| 168 | + { |
| 169 | + struct dirent **entries; |
| 170 | + const int numentries = scandir(p->path, &entries, _check_extension, alphasort); |
| 171 | + dt_bauhaus_combobox_clear(g->file); |
| 172 | + |
| 173 | + for(int i = 0; i < numentries; i++) |
| 174 | + { |
| 175 | + const char *file = entries[i]->d_name; |
| 176 | + dt_bauhaus_combobox_add_aligned(g->file, file, DT_BAUHAUS_COMBOBOX_ALIGN_LEFT); |
| 177 | + free(entries[i]); |
| 178 | + } |
| 179 | + if(numentries != -1) free(entries); |
| 180 | + |
| 181 | + if(!dt_bauhaus_combobox_set_from_text(g->file, p->file)) |
| 182 | + { // file may have disappeared - show it |
| 183 | + char *invalidfilepath = g_strconcat(" ??? ", p->file, NULL); |
| 184 | + dt_bauhaus_combobox_add_aligned(g->file, invalidfilepath, DT_BAUHAUS_COMBOBOX_ALIGN_LEFT); |
| 185 | + dt_bauhaus_combobox_set_from_text(g->file, invalidfilepath); |
| 186 | + g_free(invalidfilepath); |
| 187 | + } |
| 188 | + } |
| 189 | +} |
| 190 | + |
| 191 | +static void _fbutton_clicked(GtkWidget *widget, dt_iop_module_t *self) |
| 192 | +{ |
| 193 | + dt_iop_segmap_gui_data_t *g = self->gui_data; |
| 194 | + dt_iop_segmap_params_t *p = self->params; |
| 195 | + |
| 196 | + gchar *mfolder = dt_conf_get_string("plugins/darkroom/segments/def_path"); |
| 197 | + if(strlen(mfolder) == 0) |
| 198 | + { |
| 199 | + dt_print(DT_DEBUG_ALWAYS, "segment masks root folder not defined"); |
| 200 | + dt_control_log(_("segment masks root folder not defined")); |
| 201 | + g_free(mfolder); |
| 202 | + return; |
| 203 | + } |
| 204 | + |
| 205 | + GtkWidget *win = dt_ui_main_window(darktable.gui->ui); |
| 206 | + GtkFileChooserNative *filechooser = gtk_file_chooser_native_new( |
| 207 | + _("select segment mask file"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN, |
| 208 | + _("_select"), _("_cancel")); |
| 209 | + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), FALSE); |
| 210 | + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), mfolder); |
| 211 | + GtkFileFilter *filter = GTK_FILE_FILTER(gtk_file_filter_new()); |
| 212 | + // only pfm files yet supported |
| 213 | + gtk_file_filter_add_pattern(filter, "*.pfm"); |
| 214 | + gtk_file_filter_add_pattern(filter, "*.PFM"); |
| 215 | + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter); |
| 216 | + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(filechooser), filter); |
| 217 | + |
| 218 | + if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT) |
| 219 | + { |
| 220 | + gchar *filepath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser)); |
| 221 | + const gboolean within = (strlen(filepath) > strlen(mfolder)) |
| 222 | + && (memcmp(filepath, mfolder, strlen(mfolder)) == 0); |
| 223 | + if(within) |
| 224 | + { |
| 225 | + char *relativepath = g_path_get_dirname(filepath); |
| 226 | + const int rplen = strlen(relativepath); |
| 227 | + memcpy(p->path, relativepath, rplen); |
| 228 | + p->path[rplen] = '\0'; |
| 229 | + g_free(relativepath); |
| 230 | + |
| 231 | + const int flen = strlen(filepath) - rplen - 1; |
| 232 | + memcpy(p->file, filepath + rplen + 1, flen); |
| 233 | + p->file[flen] = '\0'; |
| 234 | + |
| 235 | + _update_filepath(self); |
| 236 | + dt_dev_add_history_item(darktable.develop, self, FALSE); |
| 237 | + } |
| 238 | + else |
| 239 | + { |
| 240 | + dt_print(DT_DEBUG_ALWAYS, "selected file not within masks root folder"); |
| 241 | + dt_control_log(_("selected file not within masks root folder")); |
| 242 | + } |
| 243 | + g_free(filepath); |
| 244 | + gtk_widget_set_sensitive(g->file, p->path[0] && p->file[0]); |
| 245 | + } |
| 246 | + g_free(mfolder); |
| 247 | + g_object_unref(filechooser); |
| 248 | +} |
| 249 | + |
| 250 | +static void _file_callback(GtkWidget *widget, dt_iop_module_t *self) |
| 251 | +{ |
| 252 | + dt_iop_segmap_params_t *p = self->params; |
| 253 | + const gchar *select = dt_bauhaus_combobox_get_text(widget); |
| 254 | + g_strlcpy(p->file, select, sizeof(p->file)); |
| 255 | + dt_dev_add_history_item(darktable.develop, self, FALSE); |
| 256 | +} |
| 257 | + |
0 commit comments