lmtpotrace.c /size: 19 Kb    last modification: 2025-02-21 11:03
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_getnewfields(lua_State *L)
190{
191    lua_createtable(L, 0, 8);
192    lua_set_string_by_key(L, "bytes",  "string");
193    lua_set_string_by_key(L, "width",  "integer");
194    lua_set_string_by_key(L, "height", "integer");
195    lua_set_string_by_key(L, "nx",     "integer");
196    lua_set_string_by_key(L, "ny",     "integer");
197    lua_set_string_by_key(L, "swap",   "boolean");
198    lua_set_string_by_key(L, "value",  "character");
199    lua_set_string_by_key(L, "negate", "boolean");
200    return 1;
201}
202
203static int potracelib_getprocessfields(lua_State *L)
204{
205    lua_createtable(L, 0, 8);
206    lua_set_string_by_key(L, "size",      "integer");
207    lua_set_string_by_key(L, "threshold", "number");
208    lua_set_string_by_key(L, "tolerance", "number");
209    lua_set_string_by_key(L, "optimize",  "boolean");
210    lua_set_string_by_key(L, "value",     "characetr");
211    lua_set_string_by_key(L, "negate",    "boolean");
212    lua_set_string_by_key(L, "policy",    "string");
213    return 1;
214}
215
216static int potracelib_getpolicyvalues(lua_State *L)
217{
218    lua_createtable(L, 7, 0);
219    lua_set_string_by_index(L, 1, "black");
220    lua_set_string_by_index(L, 2, "white");
221    lua_set_string_by_index(L, 3, "left");
222    lua_set_string_by_index(L, 4, "right"); 
223    lua_set_string_by_index(L, 5, "minority");
224    lua_set_string_by_index(L, 6, "majority"); 
225    lua_set_string_by_index(L, 7, "random");
226    return 1;
227}
228
229static int potracelib_new(lua_State *L) 
230{
231    if (lua_type(L, 1) == LUA_TTABLE) {
232
233        potracer p = { 
234            .state      = NULL,
235            .parameters = NULL,
236            .bitmap     = NULL,
237            .bytes      = NULL,
238            .height     = 0,
239            .width      = 0,
240            .swap       = 0, 
241            .nx         = 1,
242            .ny         = 1,
243            .value      = '1',
244            .match      = 1,
245        };
246
247        size_t length = 0;
248
249        if (lua_getfield(L, 1, "bytes")   == LUA_TSTRING)  { p.bytes   =   lua_tolstring(L, -1, &length); } lua_pop(L, 1);
250        if (lua_getfield(L, 1, "width")   == LUA_TNUMBER)  { p.width   =   lmt_tointeger(L, -1);          } lua_pop(L, 1);
251        if (lua_getfield(L, 1, "height")  == LUA_TNUMBER)  { p.height  =   lmt_tointeger(L, -1);          } lua_pop(L, 1);
252        if (lua_getfield(L, 1, "nx")      == LUA_TNUMBER)  { p.nx      =   lmt_tointeger(L, -1);          } lua_pop(L, 1);
253        if (lua_getfield(L, 1, "ny")      == LUA_TNUMBER)  { p.ny      =   lmt_tointeger(L, -1);          } lua_pop(L, 1);
254        if (lua_getfield(L, 1, "swap")    == LUA_TBOOLEAN) { p.swap    =   lua_toboolean(L, -1);          } lua_pop(L, 1);
255        if (lua_getfield(L, 1, "value")   == LUA_TSTRING)  { p.value   =   lmt_tochar   (L, -1);          } lua_pop(L, 1);
256        if (lua_getfield(L, 1, "negate")  == LUA_TBOOLEAN) { p.match   = ! lua_toboolean(L, -1);          } lua_pop(L, 1);
257                                          
258        if (! p.bytes) {
259            return 0;
260        } 
261
262        if ((size_t) (p.width * p.height) > length) {
263            return 0;
264        }
265
266        p.nx = p.nx < 1 ? 1 : (p.nx > max_explode ? max_explode : p.nx); 
267        p.ny = p.ny < 1 ? 1 : (p.ny > max_explode ? max_explode : p.ny); 
268
269        p.width *= p.nx;
270        p.height *= p.ny;
271
272        if (p.swap) { 
273            int tmp = p.width; 
274            p.width = p.height;
275            p.height = tmp;
276            tmp = p.nx; 
277            p.nx = p.ny;
278            p.ny = tmp;
279        }
280
281        p.parameters = potrace_param_default();
282        if (! p.parameters) {
283            free_bitmap(p.bitmap);
284            return 0;
285        }
286
287        potracelib_aux_get_parameters(L, 1, &p); 
288
289        lua_pop(L, 1);
290
291        {
292            potracer *pp = (potracer *) lua_newuserdatauv(L, sizeof(potracer), 0);
293            if (pp) { 
294                *pp = p; 
295                luaL_getmetatable(L, POTRACE_METATABLE);
296                lua_setmetatable(L, -2);
297                return 1;
298            }
299        }
300    }
301    return 0;
302}
303
304static void potracelib_aux_free(potracer *p)
305{
306    if (p) { 
307        if (p->state) { 
308            potrace_state_free(p->state);
309            p->state = NULL;
310        }
311        if (p->parameters) { 
312            potrace_param_free(p->parameters);
313            p->parameters = NULL;
314        }
315        if (p->bitmap) { 
316            free_bitmap(p->bitmap);
317            p->bitmap = NULL;
318        } 
319    }
320}
321
322static int potracelib_free(lua_State *L) 
323{
324    potracelib_aux_free(potracelib_aux_maybe_ispotracer(L));
325    return 0;
326}
327
328static int aux_potracelib_entries(potracer *p)
329{
330    potrace_path_t *entry = p->state->plist;
331    int entries = 0;
332    while (entry) {
333        entries++;
334        entry = entry->next;
335    }
336    return entries; 
337}
338
339/* maybe also - last*/
340
341static potrace_path_t *aux_potrace_goto_first(potracer *p, int nofentries, int *first, int *last, int *used) {
342    potrace_path_t *entry = p->state->plist;
343    if (*first && ! *last) {
344        *last = nofentries; 
345    }
346    if (*last < 0) { 
347        *last = nofentries - *last;
348        if (*last < 0) { 
349            *last = 1; 
350        }
351    }
352    if (*first <= 0) { 
353        *first = 1; 
354    }
355    if ((! *last) || (*last > nofentries)) { 
356        *last = nofentries; 
357    }
358    if (*first > *last) {
359        *first = *last; 
360    }
361    for (int i = 1; i < *first; i++) {
362        entry = entry->next;
363    }
364    *used = *last - *first + 1; 
365    return entry; 
366}
367
368static int potracelib_totable_normal(lua_State *L, potracer *p, int first, int last)
369{
370    int entries = 0;
371    int nofentries = aux_potracelib_entries(p);
372    int used = nofentries;
373    potrace_path_t *entry = aux_potrace_goto_first(p, nofentries, &first, &last, &used);
374    lua_createtable(L, used, 0);
375    while (entry) {
376        int segments = 0;
377        int n = entry->curve.n;
378        int m = n + 1;
379        int *tag = entry->curve.tag;
380//        int sign = (entry->next == NULL || entry->next->sign == '+') ? 1 : 0;
381        int sign = (entry->sign == '+') ? 1 : 0;
382        potrace_dpoint_t (*c)[3] =entry->curve.c;
383        lua_createtable(L, m, sign ? 2 : 1);
384        if (sign) {
385            lua_push_boolean_at_key(L, sign, 1);
386        }
387        lua_push_integer_at_key(L, index, first + entries); /* for tracing when we select */
388        lua_createtable(L, 2, 0);
389        lua_push_number_at_index(L, 1, c[n-1][2].x);
390        lua_push_number_at_index(L, 2, c[n-1][2].y);
391        lua_rawseti(L, -2, ++segments);
392        for (int i = 0; i < n; i++) {
393            switch (tag[i]) {
394                case POTRACE_CORNER:
395                    lua_createtable(L, 2, 0);
396                    lua_push_number_at_index(L, 1, c[i][1].x);
397                    lua_push_number_at_index(L, 2, c[i][1].y);
398                    lua_rawseti(L, -2, ++segments);
399                    lua_createtable(L, 2, 0);
400                    lua_push_number_at_index(L, 1, c[i][2].x);
401                    lua_push_number_at_index(L, 2, c[i][2].y);
402                    lua_rawseti(L, -2, ++segments);
403                	break;
404                case POTRACE_CURVETO:
405                    lua_createtable(L, 6, 0);
406                    lua_push_number_at_index(L, 1, c[i][2].x);
407                    lua_push_number_at_index(L, 2, c[i][2].y);
408                    lua_push_number_at_index(L, 3, c[i][0].x);
409                    lua_push_number_at_index(L, 4, c[i][0].y);
410                    lua_push_number_at_index(L, 5, c[i][1].x);
411                    lua_push_number_at_index(L, 6, c[i][1].y);
412                    lua_rawseti(L, -2, ++segments);
413                	break;
414            }
415        }
416        lua_rawseti(L, -2, ++entries);
417        if (first + entries > last) { 
418            break; 
419        } else {
420            entry = entry->next;
421        }
422    }
423    return 1;
424}
425
426/*tex 
427    These intermediate state tables are based on the debugger in |backend_eps.c|. There is no need 
428    to speed them up. 
429*/
430
431static int potracelib_totable_debug(lua_State *L, potracer *p, int first, int last)
432{
433    int entries = 0;
434    int nofentries = aux_potracelib_entries(p);
435    int used = nofentries;
436    potrace_path_t *entry = aux_potrace_goto_first(p, nofentries, &first, &last, &used);
437    lua_createtable(L, used, 0);
438    while (entry) { 
439        point_t *pt = entry->priv->pt;
440        int segments = 0;
441//        int sign = (entry->next == NULL || entry->next->sign == '+') ? 1 : 0;
442        int sign = (entry->sign == '+') ? 1 : 0;
443        lua_newtable(L);
444        if (sign) {
445            lua_push_boolean_at_key(L, sign, 1);
446        }
447        lua_push_integer_at_key(L, index, first + entries); /* for tracing when we select */
448        /*tex 
449            We can get a redundant point 0 when we go left and come back right on the same line, 
450            but we can simplify that at the receiving end. 
451        */
452        if (sign)  { 
453            point_t cur = pt[entry->priv->len - 1];
454            point_t prev = cur; 
455            lua_push_integer_at_index(L, ++segments, cur.x);
456            lua_push_integer_at_index(L, ++segments, cur.y);
457            for (int i = 0; i < entry->priv->len; i++) {
458                if (pt[i].x != cur.x && pt[i].y != cur.y) {
459                    cur = prev;
460                    lua_push_integer_at_index(L, ++segments, cur.x);
461                    lua_push_integer_at_index(L, ++segments, cur.y);
462                }
463                prev = pt[i];
464            }
465            lua_push_integer_at_index(L, ++segments, pt[entry->priv->len-1].x);
466            lua_push_integer_at_index(L, ++segments, pt[entry->priv->len-1].y);
467        } else { 
468            point_t cur = pt[0];
469            point_t prev = cur; 
470            lua_push_integer_at_index(L, ++segments, cur.x);
471            lua_push_integer_at_index(L, ++segments, cur.y);
472            for (int i = entry->priv->len - 1; i >= 0; i--) {
473                if (pt[i].x != cur.x && pt[i].y != cur.y) {
474                    cur = prev;
475                    lua_push_integer_at_index(L, ++segments, cur.x);
476                    lua_push_integer_at_index(L, ++segments, cur.y);
477                }
478                prev = pt[i];
479            }
480            lua_push_integer_at_index(L, ++segments, pt[0].x);
481            lua_push_integer_at_index(L, ++segments, pt[0].y);
482        }
483        lua_rawseti(L, -2, ++entries);
484        if (first + entries > last) { 
485            break; 
486        } else {
487            entry = entry->next;
488        }
489    }
490    return 1;
491}
492
493static int potracelib_totable(lua_State *L)
494{
495    int debug = lua_toboolean(L, 2);
496    int first = lmt_optinteger(L, 3, 0);
497    int last = lmt_optinteger(L, 4, first);
498    lua_settop(L, 1);
499    {
500        potracer *p = potracelib_aux_maybe_ispotracer(L);
501        if (p) { 
502            return debug ? potracelib_totable_debug(L, p, first, last) : potracelib_totable_normal(L, p, first, last);
503        } else {
504            return 0;
505        }
506    }
507}
508
509static int potracelib_process(lua_State *L)
510{
511    potracer *p = potracelib_aux_maybe_ispotracer(L);
512    if (p) { 
513        potracelib_aux_get_parameters(L, 2, p); 
514        if (p->bitmap) {
515            free_bitmap(p->bitmap);
516        }
517        p->bitmap = new_bitmap(p->width, p->height);
518        if (p->bitmap) {
519            potracelib_get_bitmap(p, p->match);
520            p->state = potrace_trace(p->parameters, p->bitmap);
521            if (p->state && p->state->status == POTRACE_STATUS_OK) {
522                lua_pushboolean(L, 1);
523                return 1;
524            }
525        }
526    }
527    lua_pushboolean(L, 0);
528    return 1;
529}
530
531static int potracelib_tostring(lua_State * L)
532 {
533    potracer *p = potracelib_aux_maybe_ispotracer(L);
534    if (p) {
535        (void) lua_pushfstring(L, "<potracer %p>", p);
536    } else {
537        lua_pushnil(L);
538    }
539    return 1;
540}
541
542/*tex 
543    We keep the interface simple because we glue via \LUA\ and have to connect to \METAPOST\ too. 
544*/
545
546static const struct luaL_Reg potracelib_instance_metatable[] = {
547    { "__tostring", potracelib_tostring },
548    { "__gc",       potracelib_free     },
549    { NULL,         NULL                },
550};
551
552static const luaL_Reg potracelib_function_list[] = {
553    { "new",              potracelib_new              },
554    { "free",             potracelib_free             },
555    { "process",          potracelib_process          },
556    { "totable",          potracelib_totable          },
557    { "getnewfields",     potracelib_getnewfields     },
558    { "getprocessfields", potracelib_getprocessfields },
559    { "getpolicyvalues",  potracelib_getpolicyvalues  },
560    { NULL,               NULL                        },
561};
562
563int luaopen_potrace(lua_State *L)
564{
565    luaL_newmetatable(L, POTRACE_METATABLE);
566    luaL_setfuncs(L, potracelib_instance_metatable, 0);
567    lua_newtable(L);
568    luaL_setfuncs(L, potracelib_function_list, 0);
569    return 1;
570}