Skip to content

Commit b7ea3d8

Browse files
committed
Add exporting raster mask
1 parent ab3ca1a commit b7ea3d8

1 file changed

Lines changed: 114 additions & 2 deletions

File tree

src/develop/masks/object.c

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,106 @@ static void _update_preview(_object_data_t *d)
789789
}
790790
}
791791

792+
// save the raster mask as an RGB PNG to the raster mask root folder
793+
// (compatible with the external raster masks module)
794+
static void _save_raster_mask(const float *mask,
795+
const int w,
796+
const int h,
797+
const float threshold)
798+
{
799+
if(!mask || w <= 0 || h <= 0) return;
800+
801+
const dt_imgid_t imgid = darktable.develop->image_storage.id;
802+
if(!dt_is_valid_imgid(imgid)) return;
803+
804+
// get the raster mask root folder from preferences
805+
gchar *root = dt_conf_get_string("plugins/darkroom/segments/def_path");
806+
if(!root || !*root)
807+
{
808+
g_free(root);
809+
dt_control_log(_("set raster mask root folder in preferences"));
810+
return;
811+
}
812+
813+
// ensure the directory exists
814+
if(g_mkdir_with_parents(root, 0755) != 0)
815+
{
816+
dt_print(DT_DEBUG_AI, "[object mask] cannot create folder: %s", root);
817+
dt_control_log(_("cannot create raster mask folder"));
818+
g_free(root);
819+
return;
820+
}
821+
822+
// get image filename without directory and extension
823+
char imgpath[PATH_MAX] = { 0 };
824+
dt_image_full_path(imgid, imgpath, sizeof(imgpath), NULL);
825+
gchar *basename = g_path_get_basename(imgpath);
826+
char *dot = g_strrstr(basename, ".");
827+
if(dot) *dot = '\0';
828+
829+
// build output path, append _1, _2, ... if file already exists
830+
gchar *mask_name = g_strdup_printf("%s_mask.png", basename);
831+
gchar *outpath = g_build_filename(root, mask_name, NULL);
832+
g_free(mask_name);
833+
834+
for(int seq = 1;
835+
g_file_test(outpath, G_FILE_TEST_EXISTS) && seq < 1000;
836+
seq++)
837+
{
838+
g_free(outpath);
839+
mask_name = g_strdup_printf("%s_mask_%d.png", basename, seq);
840+
outpath = g_build_filename(root, mask_name, NULL);
841+
g_free(mask_name);
842+
}
843+
844+
g_free(basename);
845+
g_free(root);
846+
847+
// create RGB buffer (rasterfile module expects 3-channel PNG)
848+
const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
849+
uint8_t *buf = g_try_malloc0((size_t)stride * h);
850+
if(!buf)
851+
{
852+
g_free(outpath);
853+
return;
854+
}
855+
856+
for(int y = 0; y < h; y++)
857+
{
858+
uint8_t *row = buf + y * stride;
859+
for(int x = 0; x < w; x++)
860+
{
861+
const uint8_t v = (mask[y * w + x] > threshold) ? 255 : 0;
862+
// cairo RGB24 is native-endian BGRX in memory
863+
row[x * 4 + 0] = v; // B
864+
row[x * 4 + 1] = v; // G
865+
row[x * 4 + 2] = v; // R
866+
row[x * 4 + 3] = 0; // unused
867+
}
868+
}
869+
870+
cairo_surface_t *surface
871+
= cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_RGB24,
872+
w, h, stride);
873+
if(surface)
874+
{
875+
const cairo_status_t st = cairo_surface_write_to_png(surface, outpath);
876+
cairo_surface_destroy(surface);
877+
if(st == CAIRO_STATUS_SUCCESS)
878+
{
879+
dt_print(DT_DEBUG_AI, "[object mask] raster mask saved: %s", outpath);
880+
dt_control_log(_("raster mask saved"));
881+
}
882+
else
883+
{
884+
dt_print(DT_DEBUG_AI, "[object mask] failed to write: %s", outpath);
885+
dt_control_log(_("failed to save raster mask"));
886+
}
887+
}
888+
g_free(buf);
889+
g_free(outpath);
890+
}
891+
792892
// transform mask-space forms to input-normalized coords and register them,
793893
// takes ownership of `forms` and `signs` lists (forms are appended to dev->forms)
794894
static dt_masks_form_t *
@@ -1139,6 +1239,15 @@ static int _object_events_button_pressed(
11391239
if(d && g_atomic_int_get(&d->encode_state) == ENCODE_RUNNING)
11401240
return 1;
11411241

1242+
// shift+right-click: save raster mask before vectorization
1243+
if(d && d->has_selection && d->mask
1244+
&& dt_modifier_is(state, GDK_SHIFT_MASK))
1245+
{
1246+
const float thresh = CLAMP(
1247+
dt_conf_get_float(CONF_OBJECT_THRESHOLD_KEY), 0.3f, 0.9f);
1248+
_save_raster_mask(d->mask, d->mask_w, d->mask_h, thresh);
1249+
}
1250+
11421251
// right-click: finalize mask (prefer cached preview forms)
11431252
dt_masks_form_t *new_grp = NULL;
11441253
if(d && d->preview_forms)
@@ -1835,8 +1944,11 @@ static void _object_set_hint_message(
18351944
g_snprintf(msgbuf,
18361945
msgbuf_len,
18371946
_("<b>add</b>: click, <b>subtract</b>: shift+click, "
1838-
"<b>clear</b>: ctrl+shift+click, <b>apply</b>: right-click\n"
1839-
"<b>smoothing</b>: scroll (%3.2f), <b>cleanup</b>: shift+scroll (%d), "
1947+
"<b>clear</b>: ctrl+shift+click\n"
1948+
"<b>apply</b>: right-click, "
1949+
"<b>apply+save raster</b>: shift+right-click\n"
1950+
"<b>smoothing</b>: scroll (%3.2f), "
1951+
"<b>cleanup</b>: shift+scroll (%d), "
18401952
"<b>opacity</b>: ctrl+scroll (%d%%)"),
18411953
d->preview_smoothing, d->preview_cleanup, opacity);
18421954
else

0 commit comments

Comments
 (0)