mlib-ptr.lmt /size: 15 Kb    last modification: 2024-01-16 10:22
1if not modules then modules = { } end modules ['mlib-ptr'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to mlib-ctx.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files",
8}
9
10-- This is experimental.
11
12-- too: use string.packrowsandcolumns
13
14local formatters, gsub, sub = string.formatters, string.gsub, string.sub
15local concat, setmetatableindex = table.concat, table.setmetatableindex
16local byte, gsub = string.byte, string.gsub
17
18local trace_result = false  trackers.register("potrace.results", function(v) trace_result = v end)
19
20local report = logs.reporter("metapost","potrace")
21
22local potracefromfile do -- this will move to another module
23
24    local newreader      = io.newreader
25    local openstring     = utilities.streams.openstring
26    local readstring     = utilities.streams.readstring
27    local zlibdecompress = xzip.decompress
28    local pngapplyfilter = pngdecode.applyfilter
29    local loadbinfile    = resolvers.loadbinfile
30
31    local function newcontent(filename)
32        local found, data = loadbinfile(filename)
33        return newreader(data or "", "string")
34    end
35
36    local function decodestrip(s,nx,ny,slice)
37        local input = readstring(s,ny*(nx*slice+1))
38        input = pngapplyfilter(input,nx,ny,slice)
39        return input, false
40    end
41
42    local function simplify(content,threshold)
43        if not threshold then
44            threshold = 127
45        end
46        local thresholds = setmetatableindex(function(t,k)
47            local v = byte(k) < threshold and '0' or '1'
48            t[k] = v
49            return v
50        end)
51        return (gsub(content,".",thresholds))
52    end
53
54    potracefrompng = function(filename,parameters)
55
56        if not filename then
57            return false
58        end
59
60        local threshold     = parameters.criterium
61        local specification = figures.getinfo(filename)
62
63        specification = specification and specification.status and specification.status.private
64
65        if not specification then
66            print("no specification")
67            return
68        end
69
70        local colorspace  = specification.colorspace
71        local xsize       = specification.xsize
72        local ysize       = specification.ysize
73        local colordepth  = specification.colordepth or 8
74
75        if colorspace ~= 0 or colordepth ~= 8 then
76            print("unsupported colorspace",colorspace,colordepth)
77            return
78        end
79        if specification.interlace == 1 then
80            print("unsupported interlace")
81            return
82        end
83        local tables = specification.tables
84        if not tables then
85            print("no tables")
86            return
87        end
88        local idat = tables.idat
89        if not idat then
90            print("no data")
91            return
92        end
93        local pngfile = newcontent(filename,method,true)
94        local content = idat[1]
95        if type(content) == "table" then
96            if not pngfile then
97                return
98            end
99            content = idat(pngfile,true)
100        end
101        content = zlibdecompress(content)
102        content = decodestrip(openstring(content),xsize,ysize,1)
103        content = simplify(content,threshold)
104        pngfile:close()
105        return content, xsize, ysize
106    end
107
108end
109
110local potracecontourplot, potracegetbitmap, potracesetbitmap, potraceconcat do
111
112    local luastrings = { }
113
114    potracecontourplot = function(nx,ny,f)
115        local yx = setmetatableindex("table")
116        local my = ny + 1
117        for y=1,ny do
118            local dy = yx[my-y]
119            for x=1,nx do
120                dy[x] = f(x,y)
121            end
122        end
123        return yx
124    end
125
126    potraceconcat = function(t)
127        for i=1,#t do
128            t[i] = concat(t[i])
129        end
130        return concat(t," ")
131    end
132
133    potracesetbitmap = function(name,str)
134        luastrings[name] = type(str) == "table" and potraceconcat(str) or str
135    end
136
137    potracegetbitmap = function(name)
138        return luastrings[name] or ""
139    end
140
141end
142
143local potracestripped, potracecount do
144
145    local lpegmatch    = lpeg.match
146
147    local sp = lpeg.patterns.whitespace^1
148    local n  = 0
149    local p  = (lpeg.Cmt((1-sp) * (sp + lpeg.P(-1)), function() n = n + 1 end) + lpeg.P(1))^0
150
151    potracecount = function(s)
152        n = 0
153        lpegmatch(p,s)
154        return n
155    end
156
157    potracestripped = function(s)
158        s = gsub(s,"%s","")
159        return s
160    end
161
162end
163
164local potraceflush, potracechecked, potraceconvert do
165
166    local potracenew     = potrace.new
167    local potracefree    = potrace.free
168    local potracetotable = potrace.totable
169    local potraceprocess = potrace.process
170
171    function potrace.trace(t,polygon)
172        local p = potracenew(t)
173        if p and potraceprocess(p) then
174            local r = potracetotable(p,polygon)
175            potracefree(p)
176            return r
177        end
178    end
179
180    local getparameterset = metapost.getparameterset
181    ----- getparameter    = metapost.getparameter
182    ----- setparameter    = metapost.setparameter
183    local mpprint         = mp.print
184
185    local f_moveto  = formatters["(%N,%N)"]
186    local f_lineto  = formatters["--(%N,%N)"]
187    local f_curveto = formatters["..controls(%N,%N)and(%N,%N)..(%N,%N)"]
188
189    potraceflush = function(t,alternative,polygon)
190        local result, r, add
191        if alternative == "draw" or alternative == "fill" then
192            alternative = alternative .. "("
193        else
194            alternative = false
195        end
196        if t then
197            result, r = { }, 0
198            if polygon then
199                add = function(before,ti,after)
200                    r = r + 1 ; result[r] = before
201                    r = r + 1 ; result[r] = f_moveto(ti[1],ti[2])
202                    for i=3,#ti,2 do
203                        r = r + 1 ; result[r] = f_lineto(ti[i],ti[i+1])
204                    end
205                    r = r + 1 ; result[r] = after
206                end
207            else
208                add = function(before,ti,after)
209                    r = r + 1 ; result[r] = before
210                    r = r + 1 ; result[r] = f_moveto(ti[1][1],ti[1][2])
211                    for i=2,#ti do
212                        local ti = ti[i]
213                        local ni = #ti
214                        if ni == 2 then
215                            r = r + 1 ; result[r] = f_lineto(ti[1],ti[2])
216                        elseif ni == 6 then
217                            r = r + 1 ; result[r] = f_curveto(ti[3],ti[4],ti[5],ti[6],ti[1],ti[2])
218                        end
219                    end
220                    r = r + 1 ; result[r] = after
221                end
222            end
223            if alternative then
224                r = r + 1 ; result[r] = "image(\n"
225                for i=1,#t do
226                    add(alternative,t[i],"&cycle);\n")
227                end
228                r = r + 1 ; result[r] = ")"
229            else
230                for i=1,#t do
231                    if i > 1 then
232                        r = r + 1 ; result[r] = "&&&&\n"
233                    end
234                    add("(",t[i],"&cycle)")
235                end
236                if #t > 1 then
237                    r = r + 1 ; result[r] = "\n&&&&cycle"
238                end
239            end
240            result = concat(result)
241        elseif alternative then
242            result = "nullpicture"
243        else
244            result = "origin--cycle"
245        end
246        if trace_result then
247            inspect(t)
248            inspect(result)
249        end
250        mpprint(result)
251    end
252
253    potracechecked = function(b,width,height)
254        if b and type(b) == "string" and b ~= "" then
255            local bytes = potracestripped(b)
256            if not width or width == 0 then
257                if not height or height == 0 then
258                    height = potracecount(b)
259                end
260                width = #bytes // height
261            elseif not height or height == 0 then
262                height = #bytes // width
263            end
264            return bytes, width, height
265        else
266            return false, width, height
267        end
268    end
269
270    potraceconvert = function(bytes,settings,w,h)
271        if not w or not h then
272            bytes, w, h = potracechecked(bytes)
273        end
274        local s = { bytes = bytes, width = w, height = h }
275        local p = potracenew(setmetatableindex(s,settings or { }))
276        if p then
277            potraceprocess(p, { value = "1" })
278            local t = potracetotable(p)
279            potracefree(p)
280            return t
281        end
282    end
283
284    local instance = false
285
286    local bytes    = false
287    local width    = 0
288    local height   = 0
289    local nx       = 1
290    local ny       = 1
291
292    local function potracepattern()
293        local b = { }
294        local n = 0
295        local f = 1
296        local t = width
297        for i=1,height do
298            n = n + 1 ; b[n] = sub(bytes,f,t)
299            n = n + 1 ; b[n] = "\\par"
300            f = t + 1
301            t = t + width
302        end
303        mpprint(formatters
304            ['textext.urt("\\potracebitmap{%t}")xysized(last_potraced_width,last_potraced_height)']
305            (b)
306        )
307    end
308
309    local loaddata   = io.loaddata
310    local filesuffix = file.suffix
311    local getbuffer  = buffers.getcontent
312
313    local function potracefromtxt(filename,parameters)
314        local bytes = loaddata(filename)
315        if bytes and bytes ~= 0 then
316            return potracechecked(bytes,0,0)
317        end
318    end
319
320    -- cache ?
321
322    local function potracefrompk(filename,parameters)
323        local index = parameters.index
324        if type(index) == "number" and filename and filename ~= "" then
325            filename = file.removesuffix(filename)
326            local r = math.tointeger(parameters.resolution or 7200) -- get rid of 7200.0
327            local f = fonts.handlers.tfm.readers.loadpk(resolvers.findpk(filename,r) or "")
328            if f then
329                local g = f.glyphs[index]
330                if g then
331                    local xsize = g.xsize
332                    local ysize = g.ysize
333                    parameters.xsize   = xsize
334                    parameters.ysize   = ysize
335                    parameters.xoffset = g.xoffset
336                    parameters.yoffset = g.yoffset
337--                     local b = fonts.handlers.tfm.readers.showpk(g,xsize,ysize) or ""
338--                     return potracechecked(b,0,0)
339                    return fonts.handlers.tfm.readers.showpk(g,xsize,ysize,true)
340                end
341            end
342        end
343    end
344
345    local filehandles = {
346        png = potracefrompng,
347        txt = potracefromtxt,
348        pk  = potracefrompk,
349    }
350
351    local function potracefromfile(parameters)
352        local filename = parameters.filename
353        if filename and filename ~= "" then
354            local handle = filehandles[filesuffix(filename)]
355            if handle then
356                return handle(filename,parameters)
357            end
358        end
359    end
360
361    local function potracefrombuffer(parameters,width,height)
362        local buffer = parameters.buffer
363        if buffer and buffer ~= "" then
364            local bytes = getbuffer(buffer)
365            if bytes and bytes ~= "" then
366                return potracechecked(bytes,width,height)
367            end
368        end
369    end
370
371    local function potracefromstring(parameters,width,height)
372        local name = parameters.stringname
373        if name and name ~= "" then
374            local bytes = potracegetbitmap(name)
375            if bytes and #bytes ~= "" then
376                return potracechecked(bytes,width,height)
377            end
378        end
379    end
380
381    function mp.lmt_start_potrace()
382        bytes  = false
383
384        local parameters = getparameterset("potraced")
385        width  = parameters.width or 0
386        height = parameters.height or 0
387        nx     = (parameters.nx or 1 // 1)
388        ny     = (parameters.ny or 1 // 1)
389        if parameters.explode then
390            nx = 3
391            ny = 3
392        end
393        if not bytes then
394            local b, w, h = potracefromfile(parameters)
395            if b then
396                bytes, width, height = b, w, h
397            end
398        end
399        if not bytes then
400            local b, w, h = potracefromstring(parameters,width,height)
401            if b then
402                bytes, width, height = b, w, h
403            end
404        end
405        if not bytes then
406            local b, w, h = potracefrombuffer(parameters,width,height)
407            if b then
408                bytes, width, height = b, w, h
409            end
410        end
411        if not bytes then
412            local b, w, h = potracechecked(parameters.bytes,width,height)
413            if b then
414                bytes, width, height = b, w, h
415            end
416        end
417        if not bytes then
418            bytes, width, height = potracechecked("010 111 010",width,height)
419        end
420-- report("width : %i",width)
421-- report("height: %i",height)
422-- report("bytes : %i",#bytes)
423-- report("length: %i",width*height)
424        instance = potracenew {
425            bytes     = bytes,
426            width     = width,
427            height    = height,
428            nx        = nx,
429            ny        = ny,
430            value     = parameters.value,
431            negate    = parameters.negate,
432            size      = parameters.size,      -- turdsize
433            optimize  = parameters.optimize,  -- opticurve
434            swap      = parameters.swap,
435            threshold = parameters.threshold, -- alphamax
436            policy    = parameters.policy,    -- turnpolicy
437            tolerance = parameters.tolerance, -- opttolerance
438        }
439        -- we keep bytes for tracing, maybe keep option
440        parameters.width  = width *nx
441        parameters.height = height*ny
442        parameters.count  = 0
443    end
444
445    function mp.lmt_stop_potrace()
446        if instance then
447            potracefree(instance)
448        end
449        instance = false
450        bytes    = false
451    end
452
453    function mp.lmt_step_potrace()
454        if instance then
455            local parameters  = getparameterset("potraced")
456            local result      = false
457            local alternative = parameters.alternative
458            local polygon     = parameters.polygon or false
459            local index       = parameters.index
460            local first       = parameters.first or 0
461            local last        = parameters.last  or 0
462            if index then
463                first = index
464                last = index
465            end
466            if alternative == "text" then
467                potracepattern()
468            else
469                -- we can return a count
470                local okay = potraceprocess(instance, {
471                    value     = parameters.value,
472                    negate    = parameters.negate,
473                    size      = parameters.size,
474                    optimize  = parameters.optimize,  -- opticurve
475                    swap      = parameters.swap,
476                    threshold = parameters.threshold, -- alphamax
477                    policy    = parameters.policy,    -- turnpolicy
478                    tolerance = parameters.tolerance, -- opttolerance
479                } )
480                if okay then
481                    result = potracetotable(instance,polygon,first,last)
482                    potraceflush(result,alternative,polygon)
483                    parameters.count = #result
484                else
485                    parameters.count = 0
486                end
487            end
488            parameters.first = nil
489            parameters.last  = nil
490            parameters.index = nil
491        end
492    end
493
494end
495
496potrace.stripped    = potracestripped
497potrace.count       = potracecount
498potrace.concat      = potraceconcat
499potrace.setbitmap   = potracesetbitmap
500potrace.getbitmap   = potracegetbitmap
501potrace.flush       = potraceflush
502potrace.fromfile    = potracefromfile
503potrace.contourplot = potracecontourplot
504potrace.checked     = potracechecked
505potrace.convert     = potraceconvert
506