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
11
12
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
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
182
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
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)
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
338
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
421
422
423
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,
433 optimize = parameters.optimize,
434 swap = parameters.swap,
435 threshold = parameters.threshold,
436 policy = parameters.policy,
437 tolerance = parameters.tolerance,
438 }
439
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
470 local okay = potraceprocess(instance, {
471 value = parameters.value,
472 negate = parameters.negate,
473 size = parameters.size,
474 optimize = parameters.optimize,
475 swap = parameters.swap,
476 threshold = parameters.threshold,
477 policy = parameters.policy,
478 tolerance = parameters.tolerance,
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 |