|
| 1 | +/* |
| 2 | + This file is part of darktable, |
| 3 | + Copyright (C) 2026 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 | +#include <stddef.h> |
| 20 | +#include <potracelib.h> |
| 21 | + |
| 22 | +#include "develop/masks.h" |
| 23 | + |
| 24 | +#ifdef _OPENMP |
| 25 | +#include <omp.h> |
| 26 | +#endif |
| 27 | + |
| 28 | +/* Macros as in Inkscape */ |
| 29 | +#define BM_WORDBITS (8 * (int)sizeof(potrace_word)) |
| 30 | +#define BM_HIBIT ((potrace_word)1 << (BM_WORDBITS-1)) |
| 31 | + |
| 32 | +#define bm_scanline(bm, y) ((bm)->map + (ptrdiff_t)(y) * (ptrdiff_t)(bm)->dy) |
| 33 | +#define bm_index(bm,x,y) (&bm_scanline(bm,y)[ (x) / BM_WORDBITS ]) |
| 34 | +#define bm_mask(x) (BM_HIBIT >> ((x) & (BM_WORDBITS-1))) |
| 35 | + |
| 36 | +#define BM_USET(bm,x,y) (*bm_index(bm,x,y) |= bm_mask(x)) |
| 37 | +#define BM_UCLR(bm,x,y) (*bm_index(bm,x,y) &= ~bm_mask(x)) |
| 38 | + |
| 39 | +static potrace_bitmap_t *_bm_new(const int w, |
| 40 | + const int h) |
| 41 | +{ |
| 42 | + potrace_bitmap_t *bm = calloc(1, sizeof(*bm)); |
| 43 | + if(!bm) return NULL; |
| 44 | + |
| 45 | + bm->w = w; |
| 46 | + bm->h = h; |
| 47 | + bm->dy = (w + BM_WORDBITS - 1) / BM_WORDBITS; /* words per scanline */ |
| 48 | + const size_t total_words = (size_t)bm->dy * h; |
| 49 | + bm->map = calloc(total_words, sizeof(potrace_word)); |
| 50 | + |
| 51 | + if(!bm->map) |
| 52 | + { |
| 53 | + free(bm); |
| 54 | + return NULL; |
| 55 | + } |
| 56 | + |
| 57 | + return bm; |
| 58 | +} |
| 59 | + |
| 60 | +static void _bm_free(potrace_bitmap_t *bm) |
| 61 | +{ |
| 62 | + if(bm) |
| 63 | + { |
| 64 | + free(bm->map); |
| 65 | + free(bm); |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +#define SET_THRESHOLD 0.6f |
| 70 | + |
| 71 | +static inline void _scale_point(float p[2], |
| 72 | + const float xscale, |
| 73 | + const float yscale, |
| 74 | + const float cx, |
| 75 | + const float cy, |
| 76 | + const float iwidth, |
| 77 | + const float iheight) |
| 78 | +{ |
| 79 | + p[0] = ((p[0] * xscale) + cx) / iwidth; |
| 80 | + p[1] = ((p[1] * yscale) + cy) / iheight; |
| 81 | +} |
| 82 | + |
| 83 | +static void _add_point(dt_masks_form_t *form, |
| 84 | + const dt_image_t *const image, |
| 85 | + const float width, |
| 86 | + const float height, |
| 87 | + const float x, |
| 88 | + const float y, |
| 89 | + const float ctl1_x, |
| 90 | + const float ctl1_y, |
| 91 | + const float ctl2_x, |
| 92 | + const float ctl2_y) |
| 93 | +{ |
| 94 | + dt_masks_point_path_t *bzpt = calloc(1, sizeof(dt_masks_point_path_t)); |
| 95 | + |
| 96 | + bzpt->corner[0] = x; |
| 97 | + bzpt->corner[1] = y; |
| 98 | + |
| 99 | + // set the control points if defined |
| 100 | + if(ctl1_x > 0) |
| 101 | + { |
| 102 | + bzpt->ctrl1[0] = ctl1_x; |
| 103 | + bzpt->ctrl1[1] = ctl1_y; |
| 104 | + bzpt->ctrl2[0] = ctl2_x; |
| 105 | + bzpt->ctrl2[1] = ctl2_y; |
| 106 | + } |
| 107 | + else |
| 108 | + { |
| 109 | + bzpt->ctrl1[0] = x; |
| 110 | + bzpt->ctrl1[1] = y; |
| 111 | + bzpt->ctrl2[0] = x; |
| 112 | + bzpt->ctrl2[1] = y; |
| 113 | + } |
| 114 | + |
| 115 | + if(image) |
| 116 | + { |
| 117 | + const float iwidth = image->width; |
| 118 | + const float iheight = image->height; |
| 119 | + const float pwidth = image->p_width; |
| 120 | + const float pheight = image->p_height; |
| 121 | + const float cx = image->crop_x; |
| 122 | + const float cy = image->crop_y; |
| 123 | + |
| 124 | + const float xscale = pwidth / (float)width; |
| 125 | + const float yscale = pheight / (float)height; |
| 126 | + |
| 127 | + _scale_point(bzpt->corner, xscale, yscale, cx, cy, iwidth, iheight); |
| 128 | + _scale_point(bzpt->ctrl1, xscale, yscale, cx, cy, iwidth, iheight); |
| 129 | + _scale_point(bzpt->ctrl2, xscale, yscale, cx, cy, iwidth, iheight); |
| 130 | + } |
| 131 | + |
| 132 | + bzpt->state = DT_MASKS_POINT_STATE_USER; |
| 133 | + bzpt->border[0] = bzpt->border[1] = 0.f; |
| 134 | + |
| 135 | + form->points = g_list_append(form->points, bzpt); |
| 136 | +} |
| 137 | + |
| 138 | +static uint32_t formnb = 0; |
| 139 | + |
| 140 | +GList *ras2forms(const float *mask, |
| 141 | + const int width, |
| 142 | + const int height, |
| 143 | + const dt_image_t *const image) |
| 144 | +{ |
| 145 | + GList *forms = NULL; |
| 146 | + |
| 147 | + // create bitmap mask for potrace |
| 148 | + |
| 149 | + potrace_bitmap_t *bm = _bm_new(width, height); |
| 150 | + |
| 151 | + DT_OMP_FOR() |
| 152 | + for(int y=0; y < height; y++) |
| 153 | + { |
| 154 | + for(int x=0; x < width; x++) |
| 155 | + { |
| 156 | + const int index = x + y * width; |
| 157 | + if(mask[index] < SET_THRESHOLD) |
| 158 | + { |
| 159 | + // black enough to be a point of the form |
| 160 | + BM_USET(bm, x, y); |
| 161 | + } |
| 162 | + else |
| 163 | + { |
| 164 | + BM_UCLR(bm, x, y); |
| 165 | + } |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + potrace_param_t *param = potrace_param_default(); |
| 170 | + // finer path possible |
| 171 | + param->alphamax = 0.0f; |
| 172 | + |
| 173 | + potrace_state_t *st = potrace_trace(param, bm); |
| 174 | + |
| 175 | + // get all paths, create corresponding path form |
| 176 | + |
| 177 | + for(const potrace_path_t *p = st->plist; |
| 178 | + p; |
| 179 | + p = p->next) |
| 180 | + { |
| 181 | + const potrace_curve_t *cv = &p->curve; |
| 182 | + const int n = cv->n; |
| 183 | + |
| 184 | + // Start = end of last segment |
| 185 | + const potrace_dpoint_t start = cv->c[n-1][2]; |
| 186 | + |
| 187 | + dt_masks_form_t *form = dt_masks_create(DT_MASKS_PATH); |
| 188 | + snprintf(form->name, sizeof(form->name), "path raster %d", ++formnb); |
| 189 | + |
| 190 | + _add_point(form, image, width, height, start.x, start.y, -1, -1, -1, -1); |
| 191 | + |
| 192 | + for(int i = 0; i < n; i++) |
| 193 | + { |
| 194 | + if(cv->tag[i] == POTRACE_CURVETO) |
| 195 | + { |
| 196 | + const potrace_dpoint_t c0 = cv->c[i][0]; |
| 197 | + const potrace_dpoint_t c1 = cv->c[i][1]; |
| 198 | + const potrace_dpoint_t e = cv->c[i][2]; |
| 199 | + |
| 200 | + _add_point(form, image, width, height, e.x, e.y, c0.x, c0.y, c1.x, c1.y); |
| 201 | + } |
| 202 | + else // POTRACE_CORNER |
| 203 | + { |
| 204 | + const potrace_dpoint_t v = cv->c[i][1]; |
| 205 | + const potrace_dpoint_t e = cv->c[i][2]; |
| 206 | + |
| 207 | + _add_point(form, image, width, height, v.x, v.y, -1, -1, -1, -1); |
| 208 | + _add_point(form, image, width, height, e.x, e.y, -1, -1, -1, -1); |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + forms = g_list_prepend(forms, form); |
| 213 | + } |
| 214 | + |
| 215 | + potrace_state_free(st); |
| 216 | + potrace_param_free(param); |
| 217 | + _bm_free(bm); |
| 218 | + |
| 219 | + return forms; |
| 220 | +} |
| 221 | + |
| 222 | +// clang-format off |
| 223 | +// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py |
| 224 | +// vim: shiftwidth=2 expandtab tabstop=2 cindent |
| 225 | +// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; |
| 226 | +// clang-format on |
0 commit comments