lpdf-img.lmt /size: 54 Kb    last modification: 2024-01-16 09:02
1if not modules then modules = { } end modules ['lpdf-img'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to lpdf-ini.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 started as an experiment but has potential for some (cached) optimizations.
11-- At some point we can also use it for fonts. For small images performance is ok
12-- with pure lua but for bigger images we can use some helpers. Normally in a
13-- typesetting workflow non-interlaced images are used. One should convert
14-- interlaced images to more efficient non-interlaced ones (ok, we can cache them if
15-- needed).
16--
17-- The \LUA\ code is slightly optimized so we could have done with less lines if we
18-- wanted but best gain a little. The idea is that we collect striped (in stages) so
19-- that we can play with substitutions. We keep this variant commented but not
20-- embedding it saves some 14K bytecode in the format.
21--
22-- We keep the \LUA\ code commented because it is what I started with from the \PNG\
23-- specification. It was one fo the first things needed for dropping the backend so
24-- actually this was part of the first \LUA\ based \PDF\ backend, the one that for a
25-- while was part of \MKIV. That bit of development was not widely advertized and
26-- just for me to make the transition and prove that it could be done. At some point
27-- I decided to not provide a generic backend so that cdoe went away. Reminder:
28-- there ended up some code here that was needed for font related png too (and I'd
29-- already forgotten about: I need to document that).
30
31local type = type
32local concat, move = table.concat, table.move
33local ceil, min = math.ceil, math.min
34local char, byte, find, gmatch = string.char, string.byte, string.find, string.gmatch
35----- idiv = number.idiv
36----- band, rshift = bit32.band, bit32.rshift
37
38local loaddata             = io.loaddata
39local setmetatableindex    = table.setmetatableindex
40local formatters           = string.formatters
41
42local streams              = utilities.streams
43local openstring           = streams.openstring
44local readstring           = streams.readstring
45local readbytetable        = streams.readbytetable
46
47local newreader            = io.newreader
48
49local tobytetable          = string.bytetable
50
51local lpdf                 = lpdf or { }
52local pdfdictionary        = lpdf.dictionary
53local pdfarray             = lpdf.array
54local pdfconstant          = lpdf.constant
55local pdfstring            = lpdf.string
56local pdfreference         = lpdf.reference
57local pdfverbose           = lpdf.verbose
58local pdfflushstreamobject = lpdf.flushstreamobject
59local pdfmajorversion      = lpdf.majorversion
60local pdfminorversion      = lpdf.minorversion
61
62local codeinjections       = backends.registered.pdf.codeinjections
63
64local createimage          = images.create
65
66local zlibcompress         = xzip.compress
67local zlibdecompress       = xzip.decompress
68
69local trace                = false
70local cleanvirtual         = resolvers.cleaners.virtual -- false -- for now
71
72local report_jpg           = logs.reporter("graphics","jpg")
73local report_jp2           = logs.reporter("graphics","jp2")
74local report_png           = logs.reporter("graphics","png")
75
76trackers.register("graphics.backend", function(v) trace = v end)
77
78directives.register("graphics.cleanvirtuals", function(v) cleanvirtual = v and resolvers.cleaners.virtual or false end)
79
80local injectors = { }
81lpdf.injectors  = injectors
82
83-- local function loadcontent(filename,method)
84--     return method == "string" and filename or loaddata(filename)
85-- end
86
87-- local function newcontent(filename,method)
88--     return newreader(filename,method)
89-- end
90
91local function loadcontent(filename,method,wipe)
92    if method == "string" then
93        return filename
94    else
95        local found, data = resolvers.loadbinfile(filename)
96        if wipe then
97            resolvers.cleanupbinfile(filename)
98        end
99        return data
100    end
101end
102
103local function newcontent(filename,method,wipe)
104    if method == "string" then
105        return newreader(filename,method)
106    else
107        local found, data = resolvers.loadbinfile(filename)
108        if wipe then
109            resolvers.cleanupbinfile(filename)
110        end
111        return newreader(data or "", "string")
112    end
113end
114
115--
116
117local chars = setmetatableindex(function(t,k) -- share this one
118    local v = (k <= 0 and "\000") or (k >= 255 and "\255") or char(k)
119    t[k] = v
120    return v
121end)
122
123-- newmask = 150
124--
125-- newmask = {
126--     {   0, 100, 0x00 },
127--     { 101, 200, 0x7F },
128--     { 201, 255, 0xFF },
129-- }
130
131-- newranges = .75
132--
133-- newranges = { 0, 0.4, 0, 0.5, 0, 0.6 } }
134-- newranges = { 0.4, 0.5, 0.6 }
135-- newranges = { 0.5 }
136
137local function todecode(newranges,colors)
138    local kind = type(newranges)
139    if colors == 1 then
140        if kind == "table" then
141            local n = #newranges
142            if n == 1 then
143                return { 0, newranges[1] }
144            elseif n == 2 then
145                return { newranges[1], newranges[2] }
146            end
147        else
148            return { 0, newranges }
149        end
150    elseif colors == 3 then
151        if kind == "table" then
152            local n = #newranges
153            if n == 1 then
154                return { 0, newranges, 0, newranges, 0, newranges }
155            elseif n == 2 then
156                local n1, n2 = newranges[1], newranges[2]
157                return { n1, n2, n1, n2, n1, n2 }
158            elseif n == 6 then
159                return newranges
160            end
161        else
162            return { 0, newranges, 0, newranges, 0, newranges }
163        end
164    elseif colors == 4 then
165        if kind == "table" then
166            local n = #newranges
167            if n == 1 then
168                return { 0, newranges, 0, newranges, 0, newranges, 0, newranges }
169            elseif n == 2 then
170                local n1, n2 = newranges[1], newranges[2]
171                return { n1, n2, n1, n2, n1, n2, n1, n2 }
172            elseif n == 8 then
173                return newranges
174            end
175        else
176            return { 0, newranges, 0, newranges, 0, newranges, 0, newranges }
177        end
178    end
179end
180
181--
182
183do
184
185    function injectors.jpg(specification,method)
186        if specification.error then
187            return
188        end
189        local filename = specification.filename
190        if not filename then
191            return
192        end
193        local colorspace  = specification.colorspace or jpg_gray
194        local decodearray = nil
195        local colors      = 1
196        ----- procset     = colorspace == 0 and "image b" or "image c"
197        if colorspace == 1 then
198            colorspace = "DeviceGray"
199        elseif colorspace == 2 then
200            colorspace = "DeviceRGB"
201            colors     = 3
202        elseif colorspace == 3 then
203            colorspace  = "DeviceCMYK"
204            colors     = 4
205            decodearray = pdfarray { 1, 0, 1, 0, 1, 0, 1, 0 }
206        end
207        -- todo: set filename
208        local newranges = specification.newranges
209        if newranges then
210            decodearray = newranges and pdfarray(todecode(newranges,colors)) or nil
211        end
212        -- todo: set filename
213        local xsize      = specification.xsize
214        local ysize      = specification.ysize
215        local colordepth = specification.colordepth
216        local content    = loadcontent(filename,method,true)
217        local xobject    = pdfdictionary {
218            Type             = pdfconstant("XObject"),
219            Subtype          = pdfconstant("Image"),
220         -- BBox             = pdfarray { 0, 0, xsize, ysize },
221            Width            = xsize,
222            Height           = ysize,
223            BitsPerComponent = colordepth,
224            Filter           = pdfconstant("DCTDecode"),
225            ColorSpace       = pdfconstant(colorspace),
226            Decode           = decodearray,
227            Length           = #content, -- specification.length
228        } + specification.attr
229        if trace then
230            report_jpg("%s: width %i, height %i, colordepth %i, size %i",filename,xsize,ysize,colordepth,#content)
231        end
232        if cleanvirtual then
233            cleanvirtual(filename)
234        end
235        return createimage {
236            bbox      = { 0, 0, specification.width/xsize, specification.height/ysize }, -- mandate
237            transform = specification.transform,
238            nolength  = true,
239            nobbox    = true,
240            notype    = true,
241            stream    = content,
242            attr      = xobject(),
243        }
244    end
245
246end
247
248do
249
250    function injectors.jp2(specification,method)
251        if specification.error then
252            return
253        end
254        local filename = specification.filename
255        if not filename then
256            return
257        end
258        -- todo: set filename
259        local xsize   = specification.xsize
260        local ysize   = specification.ysize
261        local content = loadcontent(filename,method,true)
262        local xobject = pdfdictionary {
263            Type    = pdfconstant("XObject"),
264            Subtype = pdfconstant("Image"),
265            BBox    = pdfarray { 0, 0, xsize, ysize },
266            Width   = xsize,
267            Height  = ysize,
268            Filter  = pdfconstant("JPXDecode"),
269            Length  = #content, -- specification.length
270        } + specification.attr
271        if trace then
272            report_jp2("%s: width %i, height %i, size %i",filename,xsize,ysize,#content)
273        end
274        if cleanvirtual then
275            cleanvirtual(filename)
276        end
277        return createimage {
278            bbox      = { 0, 0, specification.width/xsize, specification.height/ysize }, -- mandate
279            transform = specification.transform,
280            nolength  = true,
281            nobbox    = true,
282            notype    = true,
283            stream    = content,
284            attr      = xobject(),
285        }
286    end
287
288end
289
290do
291
292    -- We don't like interlaced files. You can deinterlace them beforehand because otherwise
293    -- each run you add runtime. Actually, even masked images can best be converted to PDF
294    -- beforehand.
295
296    -- The amount of code is larger that I like and looks somewhat redundant but we sort of
297    -- optimize a few combinations that happen often.
298
299    local pngapplyfilter = pngdecode.applyfilter
300    local pngsplitmask   = pngdecode.splitmask
301    local pnginterlace   = pngdecode.interlace
302    local pngexpand      = pngdecode.expand
303    local pngtocmyk      = pngdecode.tocmyk
304    local pngtomask      = pngdecode.tomask
305    local pngmakemask    = pngdecode.makemask
306
307    local filtermask, decodemask, decodestrip, transpose, expand, tocmyk
308
309    local newindex = lua.newindex
310    local newtable = lua.newtable
311
312    local function newoutput(size)
313        if newindex then
314            return newindex(size,char(0))
315        end
316        local t = newtable and newtable(size,0) or { }
317        for i=1,size do
318            t[i] = 0
319        end
320        return t
321    end
322
323    local function convert(t)
324        if type(t) == "table" then
325            for i=1,#t do
326                local ti = t[i]
327                if ti ~= "" then -- soon gone
328                    t[i] = chars[ti]
329                end
330            end
331            return concat(t)
332        else
333            return t
334        end
335    end
336
337    local function zero(t,k)
338        return 0
339    end
340
341    local function applyfilter(t,xsize,ysize,bpp)
342        local len = xsize * bpp + 1
343        local n   = 1
344        local m   = len - 1
345        for i=1,ysize do
346            local filter = t[n]
347            t[n] = ""
348            if filter == 0 then
349            elseif filter == 1 then
350                for j=n+bpp+1,n+m do
351                    t[j] = (t[j] + t[j-bpp]) % 256
352                end
353            elseif filter == 2 then
354                for j=n+1,n+m do
355                    t[j] = (t[j] + t[j-len]) % 256
356                end
357            elseif filter == 3 then
358                for j=n+1,n+bpp do
359                    t[j] = (t[j] + (t[j-len] // 2)) % 256
360                end
361                for j=n+bpp+1,n+m do
362                    t[j] = (t[j] + (t[j-bpp] + t[j-len]) // 2) % 256
363                end
364            elseif filter == 4 then
365                for j=n+1,n+bpp do
366                    local p = j - len
367                    local b = t[p]
368                    if b > 0 then
369                        t[j] = (t[j] + b) % 256
370                    end
371                end
372                for j=n+bpp+1,n+m do
373                    local p = j - len
374                    local a = t[j-bpp]
375                    local b = t[p]
376                    local c = t[p-bpp]
377                    local pa = b - c
378                    local pb = a - c
379                    local pc = pa + pb
380                    if pa < 0 then pa = - pa end
381                    if pb < 0 then pb = - pb end
382                    if pc < 0 then pc = - pc end
383                    t[j] = (t[j] + ((pa <= pb and pa <= pc and a) or (pb <= pc and b) or c)) % 256
384                end
385            end
386            n = n + len
387        end
388        return t
389    end
390
391 -- local filtermask_l = function (content,xsize,ysize,colordepth,colorspace,hasfilter)
392 --     local mask   = { }
393 --     local bytes  = colordepth == 16 and 2 or 1
394 --     local bpp    = colorspace == "DeviceRGB" and 3 or 1
395 --     local length = #content
396 --     local size   = ysize * xsize * ((bpp+1)*bytes + (hasfilter and 1 or 0))
397 --     local n     = 1
398 --     local l     = 1
399 --     if bytes == 2 then
400 --         if bpp == 1 then
401 --             for i=1,ysize do
402 --                 if hasfilter then
403 --                     content[n] = "" ; n = n + 1
404 --                 end
405 --                 for j=1,xsize do
406 --                     content[n] = chars[content[n]] ; n = n + 1
407 --                     content[n] = chars[content[n]] ; n = n + 1
408 --                     mask[l]    = chars[content[n]] ; l = l + 1
409 --                     content[n] = ""                ; n = n + 1
410 --                     mask[l]    = chars[content[n]] ; l = l + 1
411 --                     content[n] = ""                ; n = n + 1
412 --                 end
413 --             end
414 --         elseif bpp == 3 then
415 --             for i=1,ysize do
416 --                 if hasfilter then
417 --                     content[n] = "" ; n = n + 1
418 --                 end
419 --                 for j=1,xsize do
420 --                     content[n] = chars[content[n]] ; n = n + 1
421 --                     content[n] = chars[content[n]] ; n = n + 1
422 --                     content[n] = chars[content[n]] ; n = n + 1
423 --                     content[n] = chars[content[n]] ; n = n + 1
424 --                     content[n] = chars[content[n]] ; n = n + 1
425 --                     content[n] = chars[content[n]] ; n = n + 1
426 --                     mask[l]    = chars[content[n]] ; l = l + 1
427 --                     content[n] = ""                ; n = n + 1
428 --                     mask[l]    = chars[content[n]] ; l = l + 1
429 --                     content[n] = ""                ; n = n + 1
430 --                 end
431 --             end
432 --         else
433 --             return "", ""
434 --         end
435 --     else
436 --         if bpp == 1 then
437 --             for i=1,ysize do
438 --                 if hasfilter then
439 --                     content[n] = "" ; n = n + 1
440 --                 end
441 --                 for j=1,xsize do
442 --                     content[n] = chars[content[n]] ; n = n + 1
443 --                     mask[l]    = chars[content[n]] ; l = l + 1
444 --                     content[n] = ""                ; n = n + 1
445 --                 end
446 --             end
447 --         elseif bpp == 3 then
448 --             for i=1,ysize do
449 --                 if hasfilter then
450 --                     content[n] = "" ; n = n + 1
451 --                 end
452 --                 for j=1,xsize do
453 --                     content[n] = chars[content[n]] ; n = n + 1
454 --                     content[n] = chars[content[n]] ; n = n + 1
455 --                     content[n] = chars[content[n]] ; n = n + 1
456 --                     mask[l]    = chars[content[n]] ; l = l + 1
457 --                     content[n] = ""                ; n = n + 1
458 --                 end
459 --             end
460 --         else
461 --             return "", ""
462 --         end
463 --     end
464 --     return concat(content), concat(mask)
465 -- end
466
467 -- local decodemask_l = function(content,xsize,ysize,colordepth,colorspace)
468 --     local bytes  = colordepth == 16 and 2 or 1
469 --     local bpp    = colorspace == "DeviceRGB" and 3 or 1
470 --     local slice  = bytes*(bpp+1)
471 --     local length = #content
472 --     local size   = ysize * xsize * ((bpp+1)*bytes + 1) -- assume filter
473 --     content = openstring(content)
474 --     content = readbytetable(content,length)
475 --     setmetatableindex(content,zero)
476 --     applyfilter(content,xsize,ysize,slice)
477 --     content, mask = filtermask(content,xsize,ysize,colordepth,colorspace,true)
478 --     return content, mask
479 -- end
480
481    local filtermask_c = function(content,xsize,ysize,colordepth,colorspace)
482        local bytes = colordepth == 16 and 2 or 1
483        local bpp   = colorspace == "DeviceRGB" and 3 or 1
484        return pngsplitmask(content,xsize,ysize,bpp,bytes)
485    end
486
487    local decodemask_c = function(content,xsize,ysize,colordepth,colorspace)
488        local mask   = true
489        local filter = false
490        local bytes  = colordepth == 16 and 2 or 1
491        local bpp    = colorspace == "DeviceRGB" and 3 or 1
492        local slice  = bytes * (bpp + 1) -- always a mask
493        content      = pngapplyfilter(content,xsize,ysize,slice)
494        return pngsplitmask(content,xsize,ysize,bpp,bytes,mask,filter)
495    end
496
497 -- local function decodestrip_l(s,nx,ny,slice)
498 --     local input = readbytetable(s,ny*(nx*slice+1))
499 --     setmetatableindex(input,zero)
500 --     applyfilter(input,nx,ny,slice)
501 --     return input, true
502 -- end
503
504    local function decodestrip_c(s,nx,ny,slice)
505        local input = readstring(s,ny*(nx*slice+1))
506        input = pngapplyfilter(input,nx,ny,slice)
507        return input, false
508    end
509
510    local xstart = { 0, 4, 0, 2, 0, 1, 0 }
511    local ystart = { 0, 0, 4, 0, 2, 0, 1 }
512    local xstep  = { 8, 8, 4, 4, 2, 2, 1 }
513    local ystep  = { 8, 8, 8, 4, 4, 2, 2 }
514
515    local xblock = { 8, 4, 4, 2, 2, 1, 1 }
516    local yblock = { 8, 8, 4, 4, 2, 2, 1 }
517
518 -- local function transpose_l(xsize,ysize,slice,pass,input,output,filter)
519 --     local xstart = xstart[pass]
520 --     local xstep  = xstep[pass]
521 --     local ystart = ystart[pass]
522 --     local ystep  = ystep[pass]
523 --     local nx     = idiv(xsize + xstep - xstart - 1,xstep)
524 --     local ny     = idiv(ysize + ystep - ystart - 1,ystep)
525 --     local offset = filter and 1 or 0
526 --     local xstep  = xstep * slice
527 --     local xstart = xstart * slice
528 --     local xsize  = xsize * slice
529 --     local target = ystart * xsize + xstart + 1
530 --     local ystep  = ystep * xsize
531 --     local start  = 1
532 --     local plus   = nx * xstep
533 --     local step   = plus - xstep
534 --     if not output then
535 --         output = newoutput(xsize*(parts or slice)*ysize)
536 --     end
537 --     if slice == 1 then
538 --         for j=0,ny-1 do
539 --             start = start + offset
540 --             local target = target + j * ystep
541 --             for target=target,target+step,xstep do
542 --                 output[target] = input[start]
543 --                 start = start + slice
544 --             end
545 --         end
546 --     elseif slice == 2 then
547 --         for j=0,ny-1 do
548 --             start = start + offset
549 --             local target = target + j * ystep
550 --             for target=target,target+step,xstep do
551 --                 output[target]   = input[start]
552 --                 output[target+1] = input[start+1]
553 --                 start = start + slice
554 --             end
555 --         end
556 --     elseif slice == 3 then
557 --         for j=0,ny-1 do
558 --             start = start + offset
559 --             local target = target + j * ystep
560 --             for target=target,target+step,xstep do
561 --                 output[target]   = input[start]
562 --                 output[target+1] = input[start+1]
563 --                 output[target+2] = input[start+2]
564 --                 start = start + slice
565 --             end
566 --         end
567 --     elseif slice == 4 then
568 --         for j=0,ny-1 do
569 --             start = start + offset
570 --             local target = target + j * ystep
571 --             for target=target,target+step,xstep do
572 --                 output[target]   = input[start]
573 --                 output[target+1] = input[start+1]
574 --                 output[target+2] = input[start+2]
575 --                 output[target+3] = input[start+3]
576 --                 start = start + slice
577 --             end
578 --         end
579 --     else
580 --         local delta = slice - 1
581 --         for j=0,ny-1 do
582 --             start = start + offset
583 --             local target = target + j * ystep
584 --             for target=target,target+step,xstep do
585 --                 move(input,start,start+delta,target,output)
586 --                 start = start + slice
587 --             end
588 --         end
589 --     end
590 --     return output;
591 -- end
592
593    local transpose_c = pnginterlace
594
595 -- print(band(rshift(v,4),0x03),extract(v,4,2))
596 -- print(band(rshift(v,6),0x03),extract(v,6,2))
597
598 -- local function expand_l(t,xsize,ysize,parts,run,factor,filter)
599 --     local size  = ysize * xsize + 1 -- a bit of overshoot, needs testing, probably a few bytes us ok
600 --     local xline = filter and (run+1) or run
601 --     local f     = filter and 1 or 0
602 --     local l     = xline - 1
603 --     local n     = 1
604 --     local o     = newoutput(size)
605 --     local k     = 0
606 --     if factor then
607 --         if parts == 4 then
608 --             for i=1,ysize do
609 --                 for j=n+f,n+l do
610 --                     local v = t[j]
611 --                     if v == 0 then
612 --                         k = k + 2
613 --                     else
614 --                         k = k + 1 ; o[k] = extract4(v,4) * 0x11
615 --                         k = k + 1 ; o[k] = extract4(v,0) * 0x11
616 --                     end
617 --                 end
618 --                 k = i * xsize
619 --                 n = n + xline
620 --             end
621 --         elseif parts == 2 then
622 --             for i=1,ysize do
623 --                 for j=n+f,n+l do
624 --                     local v = t[j]
625 --                     if v == 0 then
626 --                         k = k + 4
627 --                     else
628 --                         k = k + 1 ; o[k] = extract2(v,6) * 0x55
629 --                         k = k + 1 ; o[k] = extract2(v,4) * 0x55
630 --                         k = k + 1 ; o[k] = extract2(v,2) * 0x55
631 --                         k = k + 1 ; o[k] = extract2(v,0) * 0x55
632 --                     end
633 --                 end
634 --                 k = i * xsize
635 --                 n = n + xline
636 --             end
637 --         else
638 --             for i=1,ysize do
639 --                 for j=n+f,n+l do
640 --                     local v = t[j]
641 --                     if v == 0 then
642 --                         k = k + 8
643 --                     else
644 --                         k = k + 1 ; if band(v,0x80) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,7) * 0xFF
645 --                         k = k + 1 ; if band(v,0x40) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,6) * 0xFF
646 --                         k = k + 1 ; if band(v,0x20) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,5) * 0xFF
647 --                         k = k + 1 ; if band(v,0x10) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,4) * 0xFF
648 --                         k = k + 1 ; if band(v,0x08) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,3) * 0xFF
649 --                         k = k + 1 ; if band(v,0x04) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,2) * 0xFF
650 --                         k = k + 1 ; if band(v,0x02) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,1) * 0xFF
651 --                         k = k + 1 ; if band(v,0x01) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,0) * 0xFF
652 --                     end
653 --                 end
654 --                 k = i * xsize
655 --                 n = n + xline
656 --             end
657 --         end
658 --     else
659 --         if parts == 4 then
660 --             for i=1,ysize do
661 --                 for j=n+f,n+l do
662 --                     local v = t[j]
663 --                     if v == 0 then
664 --                         k = k + 2
665 --                     else
666 --                         k = k + 1 ; o[k] = extract4(v,4)
667 --                         k = k + 1 ; o[k] = extract4(v,0)
668 --                     end
669 --                 end
670 --                 k = i * xsize
671 --                 n = n + xline
672 --             end
673 --         elseif parts == 2 then
674 --             for i=1,ysize do
675 --                 for j=n+f,n+l do
676 --                     local v = t[j]
677 --                     if v == 0 then
678 --                         k = k + 4
679 --                     else
680 --                         k = k + 1 ; o[k] = extract2(v,6)
681 --                         k = k + 1 ; o[k] = extract2(v,4)
682 --                         k = k + 1 ; o[k] = extract2(v,2)
683 --                         k = k + 1 ; o[k] = extract2(v,0)
684 --                     end
685 --                 end
686 --                 k = i * xsize
687 --                 n = n + xline
688 --             end
689 --         else
690 --             for i=1,ysize do
691 --                 for j=n+f,n+l do
692 --                     local v = t[j]
693 --                     if v == 0 then
694 --                         k = k + 8
695 --                     else
696 --                         k = k + 1 ; if band(v,0x80) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,7)
697 --                         k = k + 1 ; if band(v,0x40) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,6)
698 --                         k = k + 1 ; if band(v,0x20) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,5)
699 --                         k = k + 1 ; if band(v,0x10) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,4)
700 --                         k = k + 1 ; if band(v,0x08) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,3)
701 --                         k = k + 1 ; if band(v,0x04) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,2)
702 --                         k = k + 1 ; if band(v,0x02) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,1)
703 --                         k = k + 1 ; if band(v,0x01) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,0)
704 --                     end
705 --                 end
706 --                 k = i * xsize
707 --                 n = n + xline
708 --             end
709 --         end
710 --     end
711 --     for i=size,xsize * ysize +1,-1 do
712 --         o[i] = nil
713 --     end
714 --     return o, false
715 -- end
716
717    local expand_c = pngexpand
718
719    local function analyze(colordepth,colorspace,palette,mask)
720     -- return bytes, parts, factor
721        if palette then
722            if colordepth == 16 then
723                return 2, false, false
724            elseif colordepth == 8 then
725                return 1, false, false
726            elseif colordepth == 4 then
727                return 1, 4, false
728            elseif colordepth == 2 then
729                return 1, 2, false
730            elseif colordepth == 1 then
731                return 1, 1, false
732            end
733        elseif colorspace == "DeviceGray" then
734            if colordepth == 16 then
735                return mask and 4 or 2, false, false
736            elseif colordepth == 8 then
737                return mask and 2 or 1, false, false
738            elseif colordepth == 4 then
739                return 1, 4, true
740            elseif colordepth == 2 then
741                return 1, 2, true
742            elseif colordepth == 1 then
743                return 1, 1, true
744            end
745        else
746            if colordepth == 16 then
747                return mask and 8 or 6, false, false
748            elseif colordepth == 8 then
749                return mask and 4 or 3, false, false
750            elseif colordepth == 4 then
751                return 3, 4, true
752            elseif colordepth == 2 then
753                return 3, 2, true
754            elseif colordepth == 1 then
755                return 3, 1, true
756            end
757        end
758        return false, false, false
759    end
760
761    -- 1 6 4 6 2 6 4 6
762    -- 7 7 7 7 7 7 7 7
763    -- 5 6 5 6 5 6 5 6
764    -- 7 7 7 7 7 7 7 7
765    -- 3 6 4 6 3 6 4 6
766    -- 7 7 7 7 7 7 7 7
767    -- 5 6 5 6 5 6 5 6
768    -- 7 7 7 7 7 7 7 7
769
770    local function deinterlace(content,xsize,ysize,colordepth,colorspace,palette,mask)
771        local slice, parts, factor = analyze(colordepth,colorspace,palette,mask)
772        if slice then
773            content = openstring(zlibdecompress(content))
774            local filter = false
775            local output = false
776            for pass=1,7 do
777                local xstart = xstart[pass]
778                local xstep  = xstep[pass]
779                local ystart = ystart[pass]
780                local ystep  = ystep[pass]
781                local nx     = (xsize + xstep - xstart - 1) // xstep
782                local ny     = (ysize + ystep - ystart - 1) // ystep
783                if nx > 0 and ny > 0 then
784                    local input, filter
785                    if parts then
786                        local nxx = ceil(nx*parts/8)
787                        input, filter = decodestrip(content,nxx,ny,slice)
788                        input, filter = expand(input,nx,ny,parts,nxx,factor,filter)
789                    else
790                        input, filter = decodestrip(content,nx,ny,slice)
791                    end
792                    output = transpose(xsize,ysize,slice,pass,input,output,filter)
793                end
794             -- if pass == 3 then
795             --     break -- still looks ok, could be nice for a preroll
796             -- end
797            end
798            return output, parts and 8 or false
799        end
800    end
801
802    -- 1 (palette used), 2 (color used), and 4 (alpha channel used)
803
804    -- paeth:
805    --
806    -- p  = a + b - c
807    -- pa = abs(p - a) => a + b - c - a => b - c
808    -- pb = abs(p - b) => a + b - c - b => a - c
809    -- pc = abs(p - c) => a + b - c - c => a + b - c - c => a - c + b - c => pa + pb
810
811    local function full(t,k) local v = "\xFF" t[k] = v return v end
812
813    local function expandvector(transparent)
814        local s = openstring(transparent)
815        local n = #transparent
816        local r = { }
817        for i=0,n-1 do
818            r[i] = readstring(s,1) -- readchar
819        end
820        setmetatableindex(r,full)
821        return r
822    end
823
824 -- local function createmask_l(content,palette,transparent,xsize,ysize,colordepth,colorspace)
825 --     if palette then
826 --         local r    = expandvector(transparent)
827 --         local size = xsize*ysize
828 --         local len  = ceil(xsize*colordepth/8) + 1
829 --         local o    = newoutput(xsize*ysize)
830 --         local u    = setmetatableindex(zero)
831 --         content    = zlibdecompress(content)
832 --         content    = openstring(content)
833 --         for i=0,ysize-1 do
834 --             local t = readbytetable(content,len)
835 --             local k = i * xsize
836 --             local filter = t[1]
837 --             if filter == 0 then
838 --             elseif filter == 1 then
839 --                 for j=3,len do
840 --                     t[j] = (t[j] + t[j-1]) % 256
841 --                 end
842 --             elseif filter == 2 then
843 --                 for j=2,len do
844 --                     t[j] = (t[j] + u[j]) % 256
845 --                 end
846 --             elseif filter == 3 then
847 --                 local j = 2
848 --                 t[j] = (t[j] + idiv(u[j],2)) % 256
849 --                 for j=3,len do
850 --                     t[j] = (t[j] + idiv(t[j-1] + u[j],2)) % 256
851 --                 end
852 --             elseif filter == 4 then
853 --                 local j = 2
854 --                 local p = j - len
855 --                 local b = t[p]
856 --                 if b < 0 then
857 --                     b = - b
858 --                 end
859 --                 if b > 0 then
860 --                     t[j] = (t[j] + b) % 256
861 --                 end
862 --                 for j=3,len do
863 --                     local p = j - len
864 --                     local a = t[j-1]
865 --                     local b = t[p]
866 --                     local c = t[p-1]
867 --                     local pa = b - c
868 --                     local pb = a - c
869 --                     local pc = pa + pb
870 --                     if pa < 0 then pa = - pa end
871 --                     if pb < 0 then pb = - pb end
872 --                     if pc < 0 then pc = - pc end
873 --                     t[j] = (t[j] + ((pa <= pb and pa <= pc and a) or (pb <= pc and b) or c)) % 256
874 --                 end
875 --             end
876 --             if colordepth == 8 then
877 --                 for j=2,len do
878 --                     local v = t[j]
879 --                     k = k + 1 ; o[k] = r[v]
880 --                 end
881 --             elseif colordepth == 4 then
882 --                 for j=2,len do
883 --                     local v = t[j]
884 --                     k = k + 1 ; o[k] = r[extract4(v,4)]
885 --                     k = k + 1 ; o[k] = r[extract4(v,0)]
886 --                 end
887 --             elseif colordepth == 2 then
888 --                 for j=2,len do
889 --                     local v = t[j]
890 --                     k = k + 1 ; o[k] = r[extract2(v,6)]
891 --                     k = k + 1 ; o[k] = r[extract2(v,4)]
892 --                     k = k + 1 ; o[k] = r[extract2(v,2)]
893 --                     k = k + 1 ; o[k] = r[extract2(v,0)]
894 --                 end
895 --             else
896 --                 for j=2,len do
897 --                     local v = t[j]
898 --                     k = k + 1 ; o[k] = r[extract1(v,7)]
899 --                     k = k + 1 ; o[k] = r[extract1(v,6)]
900 --                     k = k + 1 ; o[k] = r[extract1(v,5)]
901 --                     k = k + 1 ; o[k] = r[extract1(v,4)]
902 --                     k = k + 1 ; o[k] = r[extract1(v,3)]
903 --                     k = k + 1 ; o[k] = r[extract1(v,2)]
904 --                     k = k + 1 ; o[k] = r[extract1(v,1)]
905 --                     k = k + 1 ; o[k] = r[extract1(v,0)]
906 --                 end
907 --             end
908 --             u = t
909 --         end
910 --         return concat(o,"",1,size)
911 --     end
912 -- end
913
914 -- We had this for a while (the reference now):
915 --
916 -- local function createmask_c(content,palette,transparent,xsize,ysize,colordepth,colorspace)
917 --     if palette then
918 --         local r    = expandvector(transparent)
919 --         local size = xsize*ysize
920 --         local len  = ceil(xsize*colordepth/8)
921 --         local o    = newoutput(size)
922 --         content    = zlibdecompress(content)
923 --         content    = pngapplyfilter(content,len,ysize,1) -- nostrip (saves copy)
924 --         content    = openstring(content)
925 --         for i=0,ysize-1 do
926 --             local t = readbytetable(content,len)
927 --             local k = i * xsize
928 --             if colordepth == 8 then
929 --                 for j=1,len do
930 --                     local v = t[j]
931 --                     k = k + 1 ; o[k] = r[v]
932 --                 end
933 --             elseif colordepth == 4 then
934 --                 for j=1,len do
935 --                     local v = t[j]
936 --                     k = k + 1 ; o[k] = r[(v >> 4) & 0x0F] -- r[extract4(v,4)]
937 --                     k = k + 1 ; o[k] = r[(v >> 0) & 0x0F] -- r[extract4(v,0)]
938 --                 end
939 --             elseif colordepth == 2 then
940 --                 for j=1,len do
941 --                     local v = t[j]
942 --                     k = k + 1 ; o[k] = r[(v >> 6) & 0x03] -- r[extract2(v,6)]
943 --                     k = k + 1 ; o[k] = r[(v >> 4) & 0x03] -- r[extract2(v,4)]
944 --                     k = k + 1 ; o[k] = r[(v >> 2) & 0x03] -- r[extract2(v,2)]
945 --                     k = k + 1 ; o[k] = r[(v >> 0) & 0x03] -- r[extract2(v,0)]
946 --                 end
947 --             else
948 --                 for j=1,len do
949 --                     local v = t[j]
950 --                     k = k + 1 ; o[k] = r[(v >> 7) & 0x01] -- r[extract1(v,7)]
951 --                     k = k + 1 ; o[k] = r[(v >> 6) & 0x01] -- r[extract1(v,6)]
952 --                     k = k + 1 ; o[k] = r[(v >> 5) & 0x01] -- r[extract1(v,5)]
953 --                     k = k + 1 ; o[k] = r[(v >> 4) & 0x01] -- r[extract1(v,4)]
954 --                     k = k + 1 ; o[k] = r[(v >> 3) & 0x01] -- r[extract1(v,3)]
955 --                     k = k + 1 ; o[k] = r[(v >> 2) & 0x01] -- r[extract1(v,2)]
956 --                     k = k + 1 ; o[k] = r[(v >> 1) & 0x01] -- r[extract1(v,1)]
957 --                     k = k + 1 ; o[k] = r[(v >> 0) & 0x01] -- r[extract1(v,0)]
958 --                 end
959 --             end
960 --         end
961 --         return concat(o,"",1,size)
962 --     end
963 -- end
964 --
965 -- But this is nicer for memory usage:
966
967    local function createmask_c(content,palette,transparent,xsize,ysize,colordepth,colorspace)
968        if palette then
969            local len = ceil(xsize*colordepth/8)
970            content = zlibdecompress(content)
971            content = pngapplyfilter(content,len,ysize,1) -- nostrip (saves copy)
972            return pngtomask(content,transparent,xsize,ysize,colordepth)
973        end
974    end
975
976 -- local function tocmyk_l(content,colordepth)
977 --     local l = #content
978 --     local t = { }
979 --     local n = 0
980 --     if colordepth == 8 then
981 --         for i=1,l,3 do
982 --             local r, g, b = byte(content,i,i+2)
983 --             n = n + 1 ; t[n] = char(255-r,255-g,255-b,0) -- a tad faster than chars[...]
984 --         end
985 --     else
986 --         for i=1,l,6 do
987 --             local r1, r2, g1, g2, b1, b2 = byte(content,i,i+5)
988 --             n = n + 1 ; t[n] = char(255-r1,255-r2,255-g1,255-g2,255-b1,255-b2,0,0)
989 --         end
990 --     end
991 --     return concat(t)
992 -- end
993
994    local tocmyk_c = pngtocmyk
995
996    local function converttocmyk(content,colorspace,colordepth)
997        if colorspace == "DeviceRGB" and colordepth == 8 or colordepth == 16 then
998            local done = tocmyk(content,colordepth)
999            if done then
1000                content    = done
1001                colorspace = "DeviceCMYK"
1002            end
1003        end
1004        return content, colorspace
1005    end
1006
1007 -- local function switch(v)
1008 --     if v then
1009 --         filtermask  = filtermask_l
1010 --         decodemask  = decodemask_l
1011 --         decodestrip = decodestrip_l
1012 --         transpose   = transpose_l
1013 --         expand      = expand_l
1014 --         createmask  = createmask_l
1015 --         tocmyk      = tocmyk_l
1016 --     else
1017            filtermask  = filtermask_c
1018            decodemask  = decodemask_c
1019            decodestrip = decodestrip_c
1020            transpose   = transpose_c
1021            expand      = expand_c
1022            createmask  = createmask_c
1023            tocmyk      = tocmyk_c
1024 --     end
1025 -- end
1026
1027 -- if pngapplyfilter then
1028 --     switch(false)
1029 --     directives.register("graphics.png.purelua",switch)
1030 -- else
1031 --     switch(true)
1032 -- end
1033
1034    local alwaysdecode  = false -- trucky with palettes
1035    local compresslevel = 3
1036
1037    directives.register("graphics.png.recompress", function(v)
1038        alwaysdecode = v
1039    end)
1040
1041    directives.register("graphics.png.compresslevel", function(v)
1042        v = tonumber(v)
1043        if compresslevel >= 0 or compresslevel <= 9 then
1044            compresslevel = v
1045        end
1046    end)
1047
1048    function injectors.png(specification,method) -- todo: method in specification
1049        if specification.error then
1050            return
1051        end
1052        local filename = specification.filename
1053        if not filename then
1054            return
1055        end
1056        local colorspace = specification.colorspace
1057        if not colorspace then
1058            return
1059        end
1060        local interlace = specification.interlace or 0
1061        if interlace == 1 then
1062            interlace = true
1063        elseif interlace == 0 then
1064            interlace = false
1065        else
1066            report_png("unknown interlacing %i",interlace)
1067            return
1068        end
1069        local tables = specification.tables
1070        if not tables then
1071            return
1072        end
1073        local idat = tables.idat
1074        if not idat then
1075            return
1076        end
1077        local pngfile = newcontent(filename,method,true)
1078        if not pngfile then
1079            return
1080        end
1081        local content = idat(pngfile,true)
1082        tables.idat = false
1083        --
1084     -- if tables.gama then
1085     --     report_png("ignoring gamma correction")
1086     -- end
1087        --
1088        local xsize       = specification.xsize
1089        local ysize       = specification.ysize
1090        local colordepth  = specification.colordepth or 8
1091        local mask        = false
1092        local transparent = false
1093        local palette     = false
1094        local enforcecmyk = specification.enforcecmyk
1095        local colors      = 1
1096        if     colorspace == 0 then    -- gray | image b
1097            colorspace  = "DeviceGray"
1098            transparent = true
1099        elseif colorspace == 2 then    -- rgb | image c
1100            colorspace  = "DeviceRGB"
1101            colors      = 3
1102            transparent = true
1103        elseif colorspace == 3 then    -- palette | image c+i
1104            colorspace  = "DeviceRGB"
1105            palette     = true
1106            transparent = true
1107        elseif colorspace == 4 then    -- gray | alpha | image b
1108            colorspace = "DeviceGray"
1109            mask       = true
1110        elseif colorspace == 6 then    -- rgb | alpha | image c
1111            colorspace = "DeviceRGB"
1112            colors     = 3
1113            mask       = true
1114        else
1115            report_png("unknown colorspace %i",colorspace)
1116            return
1117        end
1118        --
1119        if transparent then
1120            local trns = tables.trns
1121            if trns then
1122                transparent = trns(pngfile,true)
1123                if transparent == "" then
1124                    transparent = false
1125                end
1126                tables.trns = false
1127            else
1128                transparent = false
1129            end
1130        end
1131        --
1132        local decode = alwaysdecode -- tricky, might go away
1133        local filter = pdfconstant("FlateDecode")
1134        local major  = pdfmajorversion()
1135        local minor  = pdfminorversion()
1136        if major > 1 then
1137            -- we're okay
1138        elseif minor < 5 and colordepth == 16 then
1139            report_png("16 bit colordepth not supported in pdf < 1.5")
1140            return
1141        elseif minor < 4 and (mask or transparent) then
1142            report_png("alpha channels not supported in pdf < 1.4")
1143            return
1144        elseif minor < 2 then
1145            report_png("you'd better use a version > 1.2")
1146            return
1147         -- decode = true
1148        end
1149        --
1150        -- todo: compresslevel (or delegate)
1151        --
1152        if palette then
1153            local plte = tables.plte
1154            if plte then
1155                palette = plte(pngfile,true)
1156                if palette == "" then
1157                    palette = false
1158                end
1159                tables.plte = false
1160            else
1161                palette = false
1162            end
1163        end
1164        --
1165        local newmask   = specification.newmask
1166        local newranges = specification.newranges
1167        --
1168        if newranges then
1169            newranges = todecode(newranges,colors)
1170        end
1171        --
1172        if interlace then
1173            local r, p = deinterlace(content,xsize,ysize,colordepth,colorspace,palette,mask)
1174            if not r then
1175                return
1176            end
1177            if p then
1178                colordepth = p
1179            end
1180            if mask then
1181                if not (colordepth == 8 or colordepth == 16) then
1182                    report_png("mask can't be split from the image")
1183                    return
1184                end -- get rid of bpp:
1185                content, mask = filtermask(r,xsize,ysize,colordepth,colorspace,false)
1186            else
1187                content = convert(r) -- can be in deinterlace if needed
1188            end
1189            if enforcecmyk then
1190                content, colorspace = converttocmyk(content,colorspace,colordepth)
1191            end
1192            if compresslevel > 0 then
1193                content = zlibcompress(content,compresslevel)
1194            else
1195                filter = nil
1196            end
1197            decode = true
1198        elseif mask then
1199            if not (colordepth == 8 or colordepth == 16) then
1200                report_png("mask can't be split from the image")
1201                return
1202            end
1203            content = zlibdecompress(content)
1204            content, mask = decodemask(content,xsize,ysize,colordepth,colorspace)
1205            if enforcecmyk and not palette then
1206                content, colorspace = converttocmyk(content,colorspace,colordepth)
1207            end
1208            if compresslevel > 0 then
1209                content = zlibcompress(content,compresslevel)
1210            else
1211                filter = nil
1212            end
1213            decode  = true -- we don't copy the filter byte
1214        elseif transparent then
1215            -- in test suite
1216            -- how about decode/recompress here
1217            if palette then
1218                mask = createmask(content,palette,transparent,xsize,ysize,colordepth,colorspace)
1219            else
1220                pallette = false
1221            end
1222        elseif decode or (enforcecmyk and not palette) then
1223            -- this one needs checking
1224            local bytes = analyze(colordepth,colorspace)
1225            if bytes then
1226                content = zlibdecompress(content)
1227                content = decodestrip(openstring(content),xsize,ysize,bytes)
1228                if enforcecmyk and not palette then
1229                    content, colorspace = converttocmyk(content,colorspace,colordepth)
1230                end
1231                if compresslevel > 0 then
1232                    content = zlibcompress(content,compresslevel)
1233                else
1234                    filter = nil
1235                end
1236            else
1237                return
1238            end
1239            decode = true -- due to enforcecmyk
1240        elseif newmask and colordepth == 8 and colorspace == "DeviceGray" then
1241            local bytes = analyze(colordepth,colorspace)
1242            if bytes then
1243                content = zlibdecompress(content)
1244                content = decodestrip(openstring(content),xsize,ysize,bytes)
1245                mask = pngmakemask(content,newmask)
1246                if compresslevel > 0 then
1247                    content = zlibcompress(content,compresslevel)
1248                else
1249                    filter = nil
1250                end
1251            else
1252                return
1253            end
1254            decode = true -- due to enforcecmyk
1255        else
1256         -- print("PASS ON")
1257        end
1258        if palette then
1259            local colorspace = "DeviceRGB"
1260            local nofbytes   = 3
1261            if enforcecmyk then
1262                palette    = converttocmyk(palette,colorspace,8)
1263                colorspace = "DeviceCMYK"
1264                nofbytes   = 4
1265            end
1266            palette = pdfarray {
1267                pdfconstant("Indexed"),
1268                pdfconstant(colorspace),
1269                #palette // nofbytes,
1270                pdfreference(pdfflushstreamobject(palette)),
1271            }
1272        end
1273        pngfile:close()
1274        local xobject = pdfdictionary {
1275            Type             = pdfconstant("XObject"),
1276            Subtype          = pdfconstant("Image"),
1277         -- BBox             = pdfarray { 0, 0, xsize, ysize },
1278            Width            = xsize,
1279            Height           = ysize,
1280            BitsPerComponent = colordepth,
1281            Filter           = filter,
1282            ColorSpace       = palette or pdfconstant(colorspace),
1283            Length           = #content,
1284            Decode           = newranges and pdfarray(newranges) or nil,
1285        } + specification.attr
1286        if mask then
1287            local d = pdfdictionary {
1288                Type             = pdfconstant("XObject"),
1289                Subtype          = pdfconstant("Image"),
1290                Width            = xsize,
1291                Height           = ysize,
1292                BitsPerComponent = palette and 8 or colordepth,
1293                ColorSpace       = pdfconstant("DeviceGray"),
1294                Decode           = newranges and pdfarray(newranges) or nil,
1295            }
1296            xobject.SMask = pdfreference(pdfflushstreamobject(mask,d()))
1297        end
1298        if not decode then
1299            -- not if Decode set?
1300            xobject.DecodeParms  = pdfdictionary {
1301                Colors           = colors,
1302                Columns          = xsize,
1303                BitsPerComponent = colordepth,
1304                Predictor        = 15,
1305            }
1306        end
1307        if trace then
1308            report_png("%s: width %i, height %i, colordepth %i, size %i, palette %l, mask %l, transparent %l, decode %l",filename,xsize,ysize,colordepth,#content,palette,mask,transparent,decode)
1309        end
1310        if specification.colorref then
1311            xobject.ColorSpace = pdfreference(specification.colorref)
1312        end
1313        local width  = specification.width  or xsize * 65536
1314        local height = specification.height or ysize * 65536
1315        if cleanvirtual then
1316            cleanvirtual(filename)
1317        end
1318        return createimage {
1319            bbox      = { 0, 0, width/xsize, height/ysize }, -- mandate
1320            transform = specification.transform,
1321            nolength  = true,
1322            nobbox    = true,
1323            notype    = true,
1324            stream    = content,
1325            attr      = xobject(),
1326        }
1327    end
1328
1329end
1330
1331do
1332
1333    local function pack(specification,what)
1334        local t = { }
1335        local n = 0
1336        local s = specification.colorspace
1337        local d = specification.data
1338        local x = specification.xsize
1339        local y = specification.ysize
1340        if what == "mask" then
1341            d = specification.mask
1342            s = 1
1343        elseif what == "indexed" then
1344            s = 1
1345        elseif what == "index" then
1346            d = specification.index
1347            s = - s
1348        end
1349        if s > 0 then
1350         -- if true then
1351                return string.packrowscolumns(d)
1352         -- end
1353         -- if s == 1 then
1354         --     for i=1,y do
1355         --         local r = d[i]
1356         --         for j=1,x do
1357         --             n = n + 1 ; t[n] = chars[r[j]]
1358         --         end
1359         --     end
1360         -- elseif s == 2 then
1361         --     for i=1,y do
1362         --         local r = d[i]
1363         --         for j=1,x do
1364         --             local c = r[j]
1365         --             n = n + 1 ; t[n] = chars[c[1]]
1366         --             n = n + 1 ; t[n] = chars[c[2]]
1367         --             n = n + 1 ; t[n] = chars[c[3]]
1368         --          -- n = n + 1 ; t[n] = char(c[1],c[2],c[3]) -- test this
1369         --         end
1370         --     end
1371         -- elseif s == 3 then
1372         --     for i=1,y do
1373         --         local r = d[i]
1374         --         for j=1,x do
1375         --             local c = r[j]
1376         --             n = n + 1 ; t[n] = chars[c[1]]
1377         --             n = n + 1 ; t[n] = chars[c[2]]
1378         --             n = n + 1 ; t[n] = chars[c[3]]
1379         --             n = n + 1 ; t[n] = chars[c[4]]
1380         --          -- n = n + 1 ; t[n] = char(c[1],c[2],c[3],c[4]) -- test this
1381         --         end
1382         --     end
1383         -- end
1384         -- return concat(t)
1385        else
1386            local z = d[0] and 0 or 1
1387            if s == -1 then
1388                local f = formatters["%02X"]
1389                for i=z,#d do
1390                    n = n + 1 ; t[n] = f(d[i])
1391                end
1392            elseif s == -2 then
1393                local f = formatters["%02X%02X%02X"]
1394                for i=z,#d do
1395                    local c = d[i]
1396                    n = n + 1 ; t[n] = f(c[1],c[2],c[3])
1397                end
1398            elseif s == -3 then
1399                local f = formatters["%02X%02X%02X%02X"]
1400                for i=z,#d do
1401                    local c = d[i]
1402                    n = n + 1 ; t[n] = f(c[1],c[2],c[3],c[4])
1403                end
1404            end
1405            return "<" .. concat(t," ") .. ">"
1406        end
1407        return ""
1408    end
1409
1410    function injectors.bitmap(specification)
1411        local data = specification.data
1412        if not data then
1413            return
1414        end
1415        local xsize = specification.xsize or 0
1416        local ysize = specification.ysize or 0
1417        if xsize == 0 or ysize == 0 then
1418            return
1419        end
1420        local colorspace = specification.colorspace or 1
1421        if colorspace == 1 then
1422            colorspace = "DeviceGray"
1423        elseif colorspace == 2 then
1424            colorspace = "DeviceRGB"
1425        elseif colorspace == 3 then
1426            colorspace = "DeviceCMYK"
1427        end
1428        local colordepth = (specification.colordepth or 2) == 16 or 8
1429        local index      = specification.index
1430        local content    = pack(specification,index and "indexed" or "data")
1431        local mask       = specification.mask
1432        local colorspace = pdfconstant(colorspace)
1433        if index then
1434            colorspace = pdfarray {
1435                pdfconstant("Indexed"),
1436                colorspace,
1437                #index + (index[0] and 0 or -1), -- upper index
1438                pdfverbose(pack(specification,"index"))
1439            }
1440        end
1441        local xobject    = pdfdictionary {
1442            Type             = pdfconstant("XObject"),
1443            Subtype          = pdfconstant("Image"),
1444            BBox             = pdfarray { 0, 0, xsize, ysize },
1445            Width            = xsize,
1446            Height           = ysize,
1447            BitsPerComponent = colordepth,
1448            ColorSpace       = colorspace,
1449            Length           = #content, -- specification.length
1450        }
1451        if mask then
1452            local d = pdfdictionary {
1453                Type             = pdfconstant("XObject"),
1454                Subtype          = pdfconstant("Image"),
1455                Width            = xsize,
1456                Height           = ysize,
1457                BitsPerComponent = colordepth,
1458                ColorSpace       = pdfconstant("DeviceGray"),
1459            }
1460            xobject.SMask = pdfreference(pdfflushstreamobject(pack(specification,"mask"),d()))
1461        end
1462        local w = specification.width
1463        local h = specification.height
1464        return createimage {
1465            bbox     = { 0, 0, w and (w/xsize) or xsize, h and (h/ysize) or ysize }, -- mandate
1466         -- nolength = true,
1467            nobbox   = true,
1468            notype   = true,
1469            stream   = content,
1470            attr     = xobject(),
1471        }
1472    end
1473
1474    codeinjections.bitmap = injectors.bitmap
1475
1476end
1477
1478-- local function validcompression(data)
1479--     local d  = utilities.streams.openstring(data)
1480--     local b1 = utilities.streams.readbyte(d)
1481--     local b2 = utilities.streams.readbyte(d)
1482--     print(b1,b2)
1483--     if (b1 * 256 + b2) % 31 ~= 0 then
1484--         return false, "no zlib compressed file"
1485--     end
1486--     local method = band(b1,15)
1487--     if method ~= 8 then
1488--         return false, "method 8 expected"
1489--     end
1490--     local detail = band(rshift(b1,4),15)
1491--     if detail > 7 then
1492--         return false, "window 32 expected"
1493--     end
1494--     local preset = band(rshift(b2,5),1)
1495--     if preset ~= 0 then
1496--         return false, "unexpected preset dictionary"
1497--     end
1498--     return true
1499-- end
1500
1501codeinjections.jpg = lpdf.injectors.jpg
1502codeinjections.jp2 = lpdf.injectors.jp2
1503codeinjections.png = lpdf.injectors.png
1504