lmtpotrace.c /size: 17 Kb    last modification: 2024-01-16 10:22
1/*
2    See license.txt in the root of this project.
3*/
4
5/*tex 
6
7   We use the library part of potrace:
8   
9       Copyright (C) 2001-2019 Peter Selinger. This file is part of Potrace. It is free software 
10       and it is covered by the GNU General Public License. See the file COPYING for details.
11
12   For the following code we used some of: 
13   
14       A simple and self-contained demo of the potracelib API.
15       
16   that comes with the file set. We can use a minimal set of files because potrace has been very 
17   table for a while! 
18
19   In case one wonders why we need it: one can think of vectorizing logos, old fonts, shapes of any 
20   kind that can be used anywhere in \CONTEXT\ and \METAFUN. Just stay tuned. 
21
22*/
23
24# include <luametatex.h>
25# include <potracelib.h>
26# include <curve.h> 
27
28# define POTRACE_METATABLE "potracer"
29 
30#define BM_WORDSIZE          ((int)sizeof(potrace_word))
31#define BM_WORDBITS          (8*BM_WORDSIZE)
32#define BM_HIBIT             (((potrace_word)1)<<(BM_WORDBITS-1))
33#define bm_scanline(bm, y)   ((bm)->map + (y)*(bm)->dy)
34#define bm_index(bm, x, y)   (&bm_scanline(bm, y)[(x)/BM_WORDBITS])
35#define bm_mask(x)           (BM_HIBIT >> ((x) & (BM_WORDBITS-1)))
36#define bm_range(x, a)       ((int)(x) >= 0 && (int)(x) < (a))
37#define bm_safe(bm, x, y)    (bm_range(x, (bm)->w) && bm_range(y, (bm)->h))
38#define BM_USET(bm, x, y)    (*bm_index(bm, x, y) |= bm_mask(x))
39#define BM_UCLR(bm, x, y)    (*bm_index(bm, x, y) &= ~bm_mask(x))
40#define BM_UPUT(bm, x, y, b) ((b) ? BM_USET(bm, x, y) : BM_UCLR(bm, x, y))
41#define BM_PUT(bm, x, y, b)  (bm_safe(bm, x, y) ? BM_UPUT(bm, x, y, b) : 0)
42
43/* also internal frees */
44
45static potrace_bitmap_t *new_bitmap(int w, int h) 
46{
47    int dy = (w + BM_WORDBITS - 1) / BM_WORDBITS;
48    potrace_bitmap_t *bitmap = (potrace_bitmap_t *) lmt_memory_malloc(sizeof(potrace_bitmap_t));
49    if (! bitmap) {
50        return NULL;
51    }
52    bitmap->w = w;
53    bitmap->h = h;
54    bitmap->dy = dy;
55    bitmap->map = (potrace_word *) lmt_memory_calloc(h, dy * BM_WORDSIZE);
56    if (! bitmap->map) {
57        lmt_memory_free(bitmap);
58        return NULL;
59    } else {
60        return bitmap;
61    }
62}
63
64static void free_bitmap(potrace_bitmap_t *bitmap) 
65{ 
66    if (bitmap) {
67        lmt_memory_free(bitmap->map);
68    }
69    lmt_memory_free(bitmap);
70}
71
72static const char* const policies[] = { "black", "white", "left", "right", "minority", "majority", "random", NULL }; 
73
74typedef struct potracer { 
75    potrace_state_t  *state;
76    potrace_param_t  *parameters;
77    potrace_bitmap_t *bitmap;
78    const char       *bytes;
79    int               width;
80    int               height;
81    int               swap;
82    int               nx;
83    int               ny;
84    unsigned char     value;
85    unsigned char     match;
86    /* 7 bytes padding */
87} potracer;
88
89static potracer *potracelib_aux_maybe_ispotracer(lua_State *L)
90{
91    return (potracer *) luaL_checkudata(L, 1, POTRACE_METATABLE);
92}
93
94static unsigned char lmt_tochar(lua_State *L, int index)
95{
96    const char *s = lua_tostring(L, index);
97    return s ? (unsigned char) s[0] : '0';
98}
99
100static void potracelib_aux_get_parameters(lua_State *L, int index, potracer *p) 
101{
102    if (lua_type(L, index) == LUA_TTABLE) {
103        if (lua_getfield(L, index, "size")      == LUA_TNUMBER ) { p->parameters->turdsize     =   lmt_tointeger(L, -1); } lua_pop(L, 1);
104        if (lua_getfield(L, index, "threshold") == LUA_TNUMBER ) { p->parameters->alphamax     =   lua_tonumber (L, -1); } lua_pop(L, 1);
105        if (lua_getfield(L, index, "tolerance") == LUA_TNUMBER ) { p->parameters->opttolerance =   lua_tonumber (L, -1); } lua_pop(L, 1);
106        if (lua_getfield(L, index, "optimize")  == LUA_TBOOLEAN) { p->parameters->opticurve    =   lua_toboolean(L, -1); } lua_pop(L, 1);
107        if (lua_getfield(L, index, "value")     == LUA_TSTRING ) { p->value                    =   lmt_tochar   (L, -1); } lua_pop(L, 1);
108        if (lua_getfield(L, index, "negate")    == LUA_TBOOLEAN) { p->match                    = ! lua_toboolean(L, -1); } lua_pop(L, 1);
109
110        if (lua_getfield(L, index, "policy") == LUA_TSTRING ) { 
111            p->parameters->turnpolicy = luaL_checkoption(L, -1, "minority", policies); 
112        } 
113        lua_pop(L, 1);
114    }
115}
116
117static void potracelib_get_bitmap(potracer *p, unsigned char match) 
118{
119    /* Kind of suboptimal but it might change anyway so let the compiler worry about it. */
120
121    const char *bytes = p->bytes;
122
123    if (bytes) { 
124        unsigned char c = p->value;
125        if (p->swap) { 
126            if (p->nx != 1 || p->ny != 1) { 
127                /* maybe a 3x3 fast one */
128                for (int x = 0; x < p->width; x += p->nx) {
129                    int bp = (p->height/p->ny) * (x/p->nx); /* yet unchecked */
130                    for (int y = 0; y < p->height; y += p->ny) {
131                        unsigned char b = (unsigned char) bytes[bp++] == c ? 1 : 0;
132                        if (b) { 
133                            int dy = p->height - y - 1;
134                            for (int xn = 0; xn < p->nx; xn++) {
135                                for (int yn = 0; yn < p->ny; yn++) {
136                                    BM_PUT(p->bitmap, x + xn, dy - yn, b);
137                                }
138                            }
139                        }
140                    }
141                }
142            } else { /* fast one */
143                for (int x = 0; x < p->width; x++) {
144                    int bp = p->height * x;
145                    int dy = p->height - 1;
146                    for (int y = 0; y < p->height; y++) {
147                        unsigned char b = (unsigned char) bytes[bp++] == c ? 1 : 0;
148                        if (b) { 
149                            BM_PUT(p->bitmap, x, dy - y, b);
150                        }
151                    }
152                }
153            }
154        } else {
155            if (p->nx != 1 || p->ny != 1) { 
156                /* maybe a 3x3 fast one and also when one of them is 1 */
157                for (int y = 0; y < p->height; y += p->ny) {
158                    int bp = (p->width/p->nx) * (y/p->ny);
159                    for (int x = 0; x < p->width; x += p->nx) {
160                        unsigned char b = (unsigned char) bytes[bp++] == c ? 1 : 0;
161                        if (b) { 
162                            int dy = p->height - y - 1;
163                            for (int xn = 0; xn < p->nx; xn++) {
164                                for (int yn = 0; yn < p->ny; yn++) {
165                                    BM_PUT(p->bitmap, x + xn, dy - yn, b);
166                                }
167                            }
168                        }
169                    }
170                }
171            } else { /* fast one */
172                for (int y = 0; y < p->height; y++) {
173                    int bp = p->width * y;
174                    int dy = p->height - y - 1; 
175                    for (int x = 0; x < p->width; x++) {
176                        unsigned char b = ((unsigned char) bytes[bp++] == c ? 1 : 0) == match;
177                        if (b) { 
178                            BM_PUT(p->bitmap, x, dy, b);
179                        }
180                    }
181                }
182            }
183        }
184    }
185}
186
187# define max_explode 4
188
189static int potracelib_new(lua_State *L) 
190{
191    if (lua_type(L, 1) == LUA_TTABLE) {
192
193        potracer p = { 
194            .state      = NULL,
195            .parameters = NULL,
196            .bitmap     = NULL,
197            .bytes      = NULL,
198            .height     = 0,
199            .width      = 0,
200            .swap       = 0, 
201            .nx         = 1,
202            .ny         = 1,
203            .value      = '1',
204            .match      = 1,
205        };
206
207        size_t length = 0;
208
209        if (lua_getfield(L, 1, "bytes")   == LUA_TSTRING)  { p.bytes   =   lua_tolstring(L, -1, &length); } lua_pop(L, 1);
210        if (lua_getfield(L, 1, "width")   == LUA_TNUMBER)  { p.width   =   lmt_tointeger(L, -1);          } lua_pop(L, 1);
211        if (lua_getfield(L, 1, "height")  == LUA_TNUMBER)  { p.height  =   lmt_tointeger(L, -1);          } lua_pop(L, 1);
212        if (lua_getfield(L, 1, "nx")      == LUA_TNUMBER)  { p.nx      =   lmt_tointeger(L, -1);          } lua_pop(L, 1);
213        if (lua_getfield(L, 1, "ny")      == LUA_TNUMBER)  { p.ny      =   lmt_tointeger(L, -1);          } lua_pop(L, 1);
214        if (lua_getfield(L, 1, "swap")    == LUA_TBOOLEAN) { p.swap    =   lua_toboolean(L, -1);          } lua_pop(L, 1);
215        if (lua_getfield(L, 1, "value")   == LUA_TSTRING)  { p.value   =   lmt_tochar   (L, -1);          } lua_pop(L, 1);
216        if (lua_getfield(L, 1, "negate")  == LUA_TBOOLEAN) { p.match   = ! lua_toboolean(L, -1);          } lua_pop(L, 1);
217                                          
218        if (! p.bytes) {
219            return 0;
220        } 
221
222        if ((size_t) (p.width * p.height) > length) {
223            return 0;
224        }
225
226        p.nx = p.nx < 1 ? 1 : (p.nx > max_explode ? max_explode : p.nx); 
227        p.ny = p.ny < 1 ? 1 : (p.ny > max_explode ? max_explode : p.ny); 
228
229        p.width *= p.nx;
230        p.height *= p.ny;
231
232        if (p.swap) { 
233            int tmp = p.width; 
234            p.width = p.height;
235            p.height = tmp;
236            tmp = p.nx; 
237            p.nx = p.ny;
238            p.ny = tmp;
239        }
240
241        p.parameters = potrace_param_default();
242        if (! p.parameters) {
243            free_bitmap(p.bitmap);
244            return 0;
245        }
246
247        potracelib_aux_get_parameters(L, 1, &p); 
248
249        lua_pop(L, 1);
250
251        {
252            potracer *pp = (potracer *) lua_newuserdatauv(L, sizeof(potracer), 0);
253            if (pp) { 
254                *pp = p; 
255                luaL_getmetatable(L, POTRACE_METATABLE);
256                lua_setmetatable(L, -2);
257                return 1;
258            }
259        }
260    }
261    return 0;
262}
263
264static void potracelib_aux_free(potracer *p)
265{
266    if (p) { 
267        if (p->state) { 
268            potrace_state_free(p->state);
269            p->state = NULL;
270        }
271        if (p->parameters) { 
272            potrace_param_free(p->parameters);
273            p->parameters = NULL;
274        }
275        if (p->bitmap) { 
276            free_bitmap(p->bitmap);
277            p->bitmap = NULL;
278        } 
279    }
280}
281
282static int potracelib_free(lua_State *L) 
283{
284    potracelib_aux_free(potracelib_aux_maybe_ispotracer(L));
285    return 0;
286}
287
288static int aux_potracelib_entries(potracer *p)
289{
290    potrace_path_t *entry = p->state->plist;
291    int entries = 0;
292    while (entry) {
293        entries++;
294        entry = entry->next;
295    }
296    return entries; 
297}
298
299/* maybe also - last*/
300
301static potrace_path_t *aux_potrace_goto_first(potracer *p, int nofentries, int *first, int *last, int *used) {
302    potrace_path_t *entry = p->state->plist;
303    if (*first && ! *last) {
304        *last = nofentries; 
305    }
306    if (*last < 0) { 
307        *last = nofentries - *last;
308        if (*last < 0) { 
309            *last = 1; 
310        }
311    }
312    if (*first <= 0) { 
313        *first = 1; 
314    }
315    if ((! *last) || (*last > nofentries)) { 
316        *last = nofentries; 
317    }
318    if (*first > *last) {
319        *first = *last; 
320    }
321    for (int i = 1; i < *first; i++) {
322        entry = entry->next;
323    }
324    *used = *last - *first + 1; 
325    return entry; 
326}
327
328static int potracelib_totable_normal(lua_State *L, potracer *p, int first, int last)
329{
330    int entries = 0;
331    int nofentries = aux_potracelib_entries(p);
332    int used = nofentries;
333    potrace_path_t *entry = aux_potrace_goto_first(p, nofentries, &first, &last, &used);
334    lua_createtable(L, used, 0);
335    while (entry) {
336        int segments = 0;
337        int n = entry->curve.n;
338        int m = n + 1;
339        int *tag = entry->curve.tag;
340//        int sign = (entry->next == NULL || entry->next->sign == '+') ? 1 : 0;
341        int sign = (entry->sign == '+') ? 1 : 0;
342        potrace_dpoint_t (*c)[3] =entry->curve.c;
343        lua_createtable(L, m, sign ? 2 : 1);
344        if (sign) {
345            lua_push_boolean_at_key(L, sign, 1);
346        }
347        lua_push_integer_at_key(L, index, first + entries); /* for tracing when we select */
348        lua_createtable(L, 2, 0);
349        lua_push_number_at_index(L, 1, c[n-1][2].x);
350        lua_push_number_at_index(L, 2, c[n-1][2].y);
351        lua_rawseti(L, -2, ++segments);
352        for (int i = 0; i < n; i++) {
353            switch (tag[i]) {
354                case POTRACE_CORNER:
355                    lua_createtable(L, 2, 0);
356                    lua_push_number_at_index(L, 1, c[i][1].x);
357                    lua_push_number_at_index(L, 2, c[i][1].y);
358                    lua_rawseti(L, -2, ++segments);
359                    lua_createtable(L, 2, 0);
360                    lua_push_number_at_index(L, 1, c[i][2].x);
361                    lua_push_number_at_index(L, 2, c[i][2].y);
362                    lua_rawseti(L, -2, ++segments);
363                	break;
364                case POTRACE_CURVETO:
365                    lua_createtable(L, 6, 0);
366                    lua_push_number_at_index(L, 1, c[i][2].x);
367                    lua_push_number_at_index(L, 2, c[i][2].y);
368                    lua_push_number_at_index(L, 3, c[i][0].x);
369                    lua_push_number_at_index(L, 4, c[i][0].y);
370                    lua_push_number_at_index(L, 5, c[i][1].x);
371                    lua_push_number_at_index(L, 6, c[i][1].y);
372                    lua_rawseti(L, -2, ++segments);
373                	break;
374            }
375        }
376        lua_rawseti(L, -2, ++entries);
377        if (first + entries > last) { 
378            break; 
379        } else {
380            entry = entry->next;
381        }
382    }
383    return 1;
384}
385
386/*tex 
387    These intermediate state tables are based on the debugger in |backend_eps.c|. There is no need 
388    to speed them up. 
389*/
390
391static int potracelib_totable_debug(lua_State *L, potracer *p, int first, int last)
392{
393    int entries = 0;
394    int nofentries = aux_potracelib_entries(p);
395    int used = nofentries;
396    potrace_path_t *entry = aux_potrace_goto_first(p, nofentries, &first, &last, &used);
397    lua_createtable(L, used, 0);
398    while (entry) { 
399        point_t *pt = entry->priv->pt;
400        int segments = 0;
401//        int sign = (entry->next == NULL || entry->next->sign == '+') ? 1 : 0;
402        int sign = (entry->sign == '+') ? 1 : 0;
403        lua_newtable(L);
404        if (sign) {
405            lua_push_boolean_at_key(L, sign, 1);
406        }
407        lua_push_integer_at_key(L, index, first + entries); /* for tracing when we select */
408        /*tex 
409            We can get a redundant point 0 when we go left and come back right on the same line, 
410            but we can simplify that at the receiving end. 
411        */
412        if (sign)  { 
413            point_t cur = pt[entry->priv->len - 1];
414            point_t prev = cur; 
415            lua_push_integer_at_index(L, ++segments, cur.x);
416            lua_push_integer_at_index(L, ++segments, cur.y);
417            for (int i = 0; i < entry->priv->len; i++) {
418                if (pt[i].x != cur.x && pt[i].y != cur.y) {
419                    cur = prev;
420                    lua_push_integer_at_index(L, ++segments, cur.x);
421                    lua_push_integer_at_index(L, ++segments, cur.y);
422                }
423                prev = pt[i];
424            }
425            lua_push_integer_at_index(L, ++segments, pt[entry->priv->len-1].x);
426            lua_push_integer_at_index(L, ++segments, pt[entry->priv->len-1].y);
427        } else { 
428            point_t cur = pt[0];
429            point_t prev = cur; 
430            lua_push_integer_at_index(L, ++segments, cur.x);
431            lua_push_integer_at_index(L, ++segments, cur.y);
432            for (int i = entry->priv->len - 1; i >= 0; i--) {
433                if (pt[i].x != cur.x && pt[i].y != cur.y) {
434                    cur = prev;
435                    lua_push_integer_at_index(L, ++segments, cur.x);
436                    lua_push_integer_at_index(L, ++segments, cur.y);
437                }
438                prev = pt[i];
439            }
440            lua_push_integer_at_index(L, ++segments, pt[0].x);
441            lua_push_integer_at_index(L, ++segments, pt[0].y);
442        }
443        lua_rawseti(L, -2, ++entries);
444        if (first + entries > last) { 
445            break; 
446        } else {
447            entry = entry->next;
448        }
449    }
450    return 1;
451}
452
453static int potracelib_totable(lua_State *L)
454{
455    int debug = lua_toboolean(L, 2);
456    int first = lmt_optinteger(L, 3, 0);
457    int last = lmt_optinteger(L, 4, first);
458    lua_settop(L, 1);
459    {
460        potracer *p = potracelib_aux_maybe_ispotracer(L);
461        if (p) { 
462            return debug ? potracelib_totable_debug(L, p, first, last) : potracelib_totable_normal(L, p, first, last);
463        } else {
464            return 0;
465        }
466    }
467}
468
469static int potracelib_process(lua_State *L)
470{
471    potracer *p = potracelib_aux_maybe_ispotracer(L);
472    if (p) { 
473        potracelib_aux_get_parameters(L, 2, p); 
474        if (p->bitmap) {
475            free_bitmap(p->bitmap);
476        }
477        p->bitmap = new_bitmap(p->width, p->height);
478        if (p->bitmap) {
479            potracelib_get_bitmap(p, p->match);
480            p->state = potrace_trace(p->parameters, p->bitmap);
481            if (p->state && p->state->status == POTRACE_STATUS_OK) {
482                lua_pushboolean(L, 1);
483                return 1;
484            }
485        }
486    }
487    lua_pushboolean(L, 0);
488    return 1;
489}
490
491static int potracelib_tostring(lua_State * L)
492 {
493    potracer *p = potracelib_aux_maybe_ispotracer(L);
494    if (p) {
495        (void) lua_pushfstring(L, "<potracer %p>", p);
496    } else {
497        lua_pushnil(L);
498    }
499    return 1;
500}
501
502/*tex 
503    We keep the interface simple because we glue via \LUA\ and have to connect to \METAPOST\ too. 
504*/
505
506static const struct luaL_Reg potracelib_instance_metatable[] = {
507    { "__tostring", potracelib_tostring },
508    { "__gc",       potracelib_free     },
509    { NULL,         NULL                },
510};
511
512static const luaL_Reg potracelib_function_list[] =
513{
514    { "new",        potracelib_new     },
515    { "free",       potracelib_free    },
516    { "process",    potracelib_process },
517    { "totable",    potracelib_totable },
518    /* */
519    { NULL,    NULL             },
520};
521
522int luaopen_potrace(lua_State *L)
523{
524    luaL_newmetatable(L, POTRACE_METATABLE);
525    luaL_setfuncs(L, potracelib_instance_metatable, 0);
526    lua_newtable(L);
527    luaL_setfuncs(L, potracelib_function_list, 0);
528    return 1;
529}