if not modules then modules = { } end modules ['lpdf-img'] = {
    version   = 1.001,
    optimize  = true,
    comment   = "companion to lpdf-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- This started as an experiment but has potential for some (cached) optimizations.
-- At some point we can also use it for fonts. For small images performance is ok
-- with pure lua but for bigger images we can use some helpers. Normally in a
-- typesetting workflow non-interlaced images are used. One should convert
-- interlaced images to more efficient non-interlaced ones (ok, we can cache them if
-- needed).
--
-- The \LUA\ code is slightly optimized so we could have done with less lines if we
-- wanted but best gain a little. The idea is that we collect striped (in stages) so
-- that we can play with substitutions. We keep this variant commented but not
-- embedding it saves some 14K bytecode in the format.
--
-- We keep the \LUA\ code commented because it is what I started with from the \PNG\
-- specification. It was one fo the first things needed for dropping the backend so
-- actually this was part of the first \LUA\ based \PDF\ backend, the one that for a
-- while was part of \MKIV. That bit of development was not widely advertized and
-- just for me to make the transition and prove that it could be done. At some point
-- I decided to not provide a generic backend so that cdoe went away. Reminder:
-- there ended up some code here that was needed for font related png too (and I'd
-- already forgotten about: I need to document that).

local type = type
local concat, move = table.concat, table.move
local ceil, min = math.ceil, math.min
local char, byte, find, gmatch = string.char, string.byte, string.find, string.gmatch
----- idiv = number.idiv
----- band, rshift = bit32.band, bit32.rshift

local loaddata             = io.loaddata
local setmetatableindex    = table.setmetatableindex
local formatters           = string.formatters

local streams              = utilities.streams
local openstring           = streams.openstring
local readstring           = streams.readstring
local readbytetable        = streams.readbytetable

local newreader            = io.newreader

local tobytetable          = string.bytetable

local lpdf                 = lpdf or { }
local pdfdictionary        = lpdf.dictionary
local pdfarray             = lpdf.array
local pdfconstant          = lpdf.constant
local pdfreference         = lpdf.reference
local pdfverbose           = lpdf.verbose
local pdfflushobject       = lpdf.flushobject
local pdfflushstreamobject = lpdf.flushstreamobject
local pdfmajorversion      = lpdf.majorversion
local pdfminorversion      = lpdf.minorversion

local codeinjections       = backends.registered.pdf.codeinjections

local createimage          = images.create

local zlibcompress         = xzip.compress
local zlibdecompress       = xzip.decompress

local trace                = false
local cleanvirtual         = resolvers.cleaners.virtual -- false -- for now

local report_jpg           = logs.reporter("graphics","jpg")
local report_jp2           = logs.reporter("graphics","jp2")
local report_pngpng           = logs.reporter("graphics","png")

trackers.register("graphics.backend", function(v) trace = v end)

directives.register("graphics.cleanvirtuals", function(v) cleanvirtual = v and resolvers.cleaners.virtual or false end)

local injectors = { }
lpdf.injectors  = injectors

-- local function loadcontent(filename,method)
--     return method == "string" and filename or loaddata(filename)
-- end

-- local function newcontent(filename,method)
--     return newreader(filename,method)
-- end

local function loadcontent(filename,method,wipe)
    if method == "string" then
        return filename
    else
        local found, data = resolvers.loadbinfile(filename)
        if wipe then
            resolvers.cleanupbinfile(filename)
        end
        return data
    end
end

local function newcontent(filename,method,wipe)
    if method == "string" then
        return newreader(filename,method)
    else
        local found, data = resolvers.loadbinfile(filename)
        if wipe then
            resolvers.cleanupbinfile(filename)
        end
        return newreader(data or "", "string")
    end
end

--

local chars = setmetatableindex(function(t,k) -- share this one
    local v = (k <= 0 and "\000") or (k >= 255 and "\255") or char(k)
    t[k] = v
    return v
end)

-- newmask = 150
--
-- newmask = {
--     {   0, 100, 0x00 },
--     { 101, 200, 0x7F },
--     { 201, 255, 0xFF },
-- }

-- newranges = .75
--
-- newranges = { 0, 0.4, 0, 0.5, 0, 0.6 } }
-- newranges = { 0.4, 0.5, 0.6 }
-- newranges = { 0.5 }

local function todecode(newranges,colors)
    local kind = type(newranges)
    if colors == 1 then
        if kind == "table" then
            local n = #newranges
            if n == 1 then
                return { 0, newranges[1] }
            elseif n == 2 then
                return { newranges[1], newranges[2] }
            end
        else
            return { 0, newranges }
        end
    elseif colors == 3 then
        if kind == "table" then
            local n = #newranges
            if n == 1 then
                return { 0, newranges, 0, newranges, 0, newranges }
            elseif n == 2 then
                local n1, n2 = newranges[1], newranges[2]
                return { n1, n2, n1, n2, n1, n2 }
            elseif n == 6 then
                return newranges
            end
        else
            return { 0, newranges, 0, newranges, 0, newranges }
        end
    elseif colors == 4 then
        if kind == "table" then
            local n = #newranges
            if n == 1 then
                return { 0, newranges, 0, newranges, 0, newranges, 0, newranges }
            elseif n == 2 then
                local n1, n2 = newranges[1], newranges[2]
                return { n1, n2, n1, n2, n1, n2, n1, n2 }
            elseif n == 8 then
                return newranges
            end
        else
            return { 0, newranges, 0, newranges, 0, newranges, 0, newranges }
        end
    end
end

--

do

    function injectors.jpg(specification,method)
        if specification.error then
            return
        end
        local filename = specification.filename
        if not filename then
            return
        end
        local colorspace  = specification.colorspace or jpg_gray
        local decodearray = nil
        local colors      = 1
        ----- procset     = colorspace == 0 and "image b" or "image c"
        if colorspace == 1 then
            colorspace = "DeviceGray"
        elseif colorspace == 2 then
            colorspace = "DeviceRGB"
            colors     = 3
        elseif colorspace == 3 then
            colorspace  = "DeviceCMYK"
            colors     = 4
            decodearray = pdfarray { 1, 0, 1, 0, 1, 0, 1, 0 }
        end
        -- todo: set filename
        local newranges = specification.newranges
        if newranges then
            decodearray = newranges and pdfarray(todecode(newranges,colors)) or nil
        end
        -- todo: set filename
        local xsize      = specification.xsize
        local ysize      = specification.ysize
        local colordepth = specification.colordepth
        local content    = loadcontent(filename,method,true)
        local xobject    = pdfdictionary {
            Type             = pdfconstant("XObject"),
            Subtype          = pdfconstant("Image"),
         -- BBox             = pdfarray { 0, 0, xsize, ysize },
            Width            = xsize,
            Height           = ysize,
            BitsPerComponent = colordepth,
            Filter           = pdfconstant("DCTDecode"),
            ColorSpace       = pdfconstant(colorspace),
            Decode           = decodearray,
            Length           = #content, -- specification.length
        } + specification.attr
        if trace then
            report_jpg("%s: width %i, height %i, colordepth %i, size %i",filename,xsize,ysize,colordepth,#content)
        end
        if cleanvirtual then
            cleanvirtual(filename)
        end
        return createimage {
            bbox      = { 0, 0, specification.width/xsize, specification.height/ysize }, -- mandate
            transform = specification.transform,
            nolength  = true,
            nobbox    = true,
            notype    = true,
            stream    = content,
            attr      = xobject(),
        }
    end

end

do

    function injectors.jp2(specification,method)
        if specification.error then
            return
        end
        local filename = specification.filename
        if not filename then
            return
        end
        -- todo: set filename
        local xsize   = specification.xsize
        local ysize   = specification.ysize
        local content = loadcontent(filename,method,true)
        local xobject = pdfdictionary {
            Type    = pdfconstant("XObject"),
            Subtype = pdfconstant("Image"),
            BBox    = pdfarray { 0, 0, xsize, ysize },
            Width   = xsize,
            Height  = ysize,
            Filter  = pdfconstant("JPXDecode"),
            Length  = #content, -- specification.length
        } + specification.attr
        if trace then
            report_jp2("%s: width %i, height %i, size %i",filename,xsize,ysize,#content)
        end
        if cleanvirtual then
            cleanvirtual(filename)
        end
        return createimage {
            bbox      = { 0, 0, specification.width/xsize, specification.height/ysize }, -- mandate
            transform = specification.transform,
            nolength  = true,
            nobbox    = true,
            notype    = true,
            stream    = content,
            attr      = xobject(),
        }
    end

end

do

    -- We don't like interlaced files. You can deinterlace them beforehand because otherwise
    -- each run you add runtime. Actually, even masked images can best be converted to PDF
    -- beforehand.

    -- The amount of code is larger that I like and looks somewhat redundant but we sort of
    -- optimize a few combinations that happen often.

    -- This will go to its own module as we also want to do bytemaps.

    local pngapplyfilter = pngdecode.applyfilter
    local pngsplitmask   = pngdecode.splitmask
    local pnginterlace   = pngdecode.interlace
    local pngexpand      = pngdecode.expand
    local pngtocmyk      = pngdecode.tocmyk
    local pngtomask      = pngdecode.tomask
    local pngmakemask    = pngdecode.makemask
    local frompalette    = pngdecode.frompalette
    local palettemask    = pngdecode.palettemask

    local createmask, filtermask, decodemask, decodestrip, transpose, expand, tocmyk

    local newindex = lua.newindex
    local newtable = lua.newtable

    local function newoutput(size)
        if newindex then
            return newindex(size,char(0))
        end
        local t = newtable and newtable(size,0) or { }
        for i=1,size do
            t[i] = 0
        end
        return t
    end

    local function convert(t)
        if type(t) == "table" then
            for i=1,#t do
                local ti = t[i]
                if ti ~= "" then -- soon gone
                    t[i] = chars[ti]
                end
            end
            return concat(t)
        else
            return t
        end
    end

    local function zero(t,k)
        return 0
    end

    local function applyfilter(t,xsize,ysize,bpp)
        local len = xsize * bpp + 1
        local n   = 1
        local m   = len - 1
        for i=1,ysize do
            local filter = t[n]
            t[n] = ""
            if filter == 0 then
            elseif filter == 1 then
                for j=n+bpp+1,n+m do
                    t[j] = (t[j] + t[j-bpp]) % 256
                end
            elseif filter == 2 then
                for j=n+1,n+m do
                    t[j] = (t[j] + t[j-len]) % 256
                end
            elseif filter == 3 then
                for j=n+1,n+bpp do
                    t[j] = (t[j] + (t[j-len] // 2)) % 256
                end
                for j=n+bpp+1,n+m do
                    t[j] = (t[j] + (t[j-bpp] + t[j-len]) // 2) % 256
                end
            elseif filter == 4 then
                for j=n+1,n+bpp do
                    local p = j - len
                    local b = t[p]
                    if b > 0 then
                        t[j] = (t[j] + b) % 256
                    end
                end
                for j=n+bpp+1,n+m do
                    local p = j - len
                    local a = t[j-bpp]
                    local b = t[p]
                    local c = t[p-bpp]
                    local pa = b - c
                    local pb = a - c
                    local pc = pa + pb
                    if pa < 0 then pa = - pa end
                    if pb < 0 then pb = - pb end
                    if pc < 0 then pc = - pc end
                    t[j] = (t[j] + ((pa <= pb and pa <= pc and a) or (pb <= pc and b) or c)) % 256
                end
            end
            n = n + len
        end
        return t
    end

 -- local filtermask_l = function (content,xsize,ysize,colordepth,colorspace,hasfilter)
 --     local mask   = { }
 --     local bytes  = colordepth == 16 and 2 or 1
 --     local bpp    = colorspace == "DeviceRGB" and 3 or 1
 --     local length = #content
 --     local size   = ysize * xsize * ((bpp+1)*bytes + (hasfilter and 1 or 0))
 --     local n     = 1
 --     local l     = 1
 --     if bytes == 2 then
 --         if bpp == 1 then
 --             for i=1,ysize do
 --                 if hasfilter then
 --                     content[n] = "" ; n = n + 1
 --                 end
 --                 for j=1,xsize do
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     mask[l]    = chars[content[n]] ; l = l + 1
 --                     content[n] = ""                ; n = n + 1
 --                     mask[l]    = chars[content[n]] ; l = l + 1
 --                     content[n] = ""                ; n = n + 1
 --                 end
 --             end
 --         elseif bpp == 3 then
 --             for i=1,ysize do
 --                 if hasfilter then
 --                     content[n] = "" ; n = n + 1
 --                 end
 --                 for j=1,xsize do
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     mask[l]    = chars[content[n]] ; l = l + 1
 --                     content[n] = ""                ; n = n + 1
 --                     mask[l]    = chars[content[n]] ; l = l + 1
 --                     content[n] = ""                ; n = n + 1
 --                 end
 --             end
 --         else
 --             return "", ""
 --         end
 --     else
 --         if bpp == 1 then
 --             for i=1,ysize do
 --                 if hasfilter then
 --                     content[n] = "" ; n = n + 1
 --                 end
 --                 for j=1,xsize do
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     mask[l]    = chars[content[n]] ; l = l + 1
 --                     content[n] = ""                ; n = n + 1
 --                 end
 --             end
 --         elseif bpp == 3 then
 --             for i=1,ysize do
 --                 if hasfilter then
 --                     content[n] = "" ; n = n + 1
 --                 end
 --                 for j=1,xsize do
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     content[n] = chars[content[n]] ; n = n + 1
 --                     mask[l]    = chars[content[n]] ; l = l + 1
 --                     content[n] = ""                ; n = n + 1
 --                 end
 --             end
 --         else
 --             return "", ""
 --         end
 --     end
 --     return concat(content), concat(mask)
 -- end

 -- local decodemask_l = function(content,xsize,ysize,colordepth,colorspace)
 --     local bytes  = colordepth == 16 and 2 or 1
 --     local bpp    = colorspace == "DeviceRGB" and 3 or 1
 --     local slice  = bytes*(bpp+1)
 --     local length = #content
 --     local size   = ysize * xsize * ((bpp+1)*bytes + 1) -- assume filter
 --     content = openstring(content)
 --     content = readbytetable(content,length)
 --     setmetatableindex(content,zero)
 --     applyfilter(content,xsize,ysize,slice)
 --     content, mask = filtermask(content,xsize,ysize,colordepth,colorspace,true)
 --     return content, mask
 -- end

    local filtermask_c = function(content,xsize,ysize,colordepth,colorspace)
        local bytes = colordepth == 16 and 2 or 1
        local bpp   = colorspace == "DeviceRGB" and 3 or 1
        return pngsplitmask(content,xsize,ysize,bpp,bytes)
    end

    local decodemask_c = function(content,xsize,ysize,colordepth,colorspace)
        local mask   = true
        local filter = false
        local bytes  = colordepth == 16 and 2 or 1
        local bpp    = colorspace == "DeviceRGB" and 3 or 1
        local slice  = bytes * (bpp + 1) -- always a mask
        content      = pngapplyfilter(content,xsize,ysize,slice)
        return pngsplitmask(content,xsize,ysize,bpp,bytes,mask,filter)
    end

 -- local function decodestrip_l(s,nx,ny,slice)
 --     local input = readbytetable(s,ny*(nx*slice+1))
 --     setmetatableindex(input,zero)
 --     applyfilter(input,nx,ny,slice)
 --     return input, true
 -- end

    local function decodestrip_c(s,nx,ny,slice)
        local input = readstring(s,ny*(nx*slice+1))
        input = pngapplyfilter(input,nx,ny,slice)
        return input, false
    end

    local xstart = { 0, 4, 0, 2, 0, 1, 0 }
    local ystart = { 0, 0, 4, 0, 2, 0, 1 }
    local xstep  = { 8, 8, 4, 4, 2, 2, 1 }
    local ystep  = { 8, 8, 8, 4, 4, 2, 2 }

    local xblock = { 8, 4, 4, 2, 2, 1, 1 }
    local yblock = { 8, 8, 4, 4, 2, 2, 1 }

 -- local function transpose_l(xsize,ysize,slice,pass,input,output,filter)
 --     local xstart = xstart[pass]
 --     local xstep  = xstep[pass]
 --     local ystart = ystart[pass]
 --     local ystep  = ystep[pass]
 --     local nx     = idiv(xsize + xstep - xstart - 1,xstep)
 --     local ny     = idiv(ysize + ystep - ystart - 1,ystep)
 --     local offset = filter and 1 or 0
 --     local xstep  = xstep * slice
 --     local xstart = xstart * slice
 --     local xsize  = xsize * slice
 --     local target = ystart * xsize + xstart + 1
 --     local ystep  = ystep * xsize
 --     local start  = 1
 --     local plus   = nx * xstep
 --     local step   = plus - xstep
 --     if not output then
 --         output = newoutput(xsize*(parts or slice)*ysize)
 --     end
 --     if slice == 1 then
 --         for j=0,ny-1 do
 --             start = start + offset
 --             local target = target + j * ystep
 --             for target=target,target+step,xstep do
 --                 output[target] = input[start]
 --                 start = start + slice
 --             end
 --         end
 --     elseif slice == 2 then
 --         for j=0,ny-1 do
 --             start = start + offset
 --             local target = target + j * ystep
 --             for target=target,target+step,xstep do
 --                 output[target]   = input[start]
 --                 output[target+1] = input[start+1]
 --                 start = start + slice
 --             end
 --         end
 --     elseif slice == 3 then
 --         for j=0,ny-1 do
 --             start = start + offset
 --             local target = target + j * ystep
 --             for target=target,target+step,xstep do
 --                 output[target]   = input[start]
 --                 output[target+1] = input[start+1]
 --                 output[target+2] = input[start+2]
 --                 start = start + slice
 --             end
 --         end
 --     elseif slice == 4 then
 --         for j=0,ny-1 do
 --             start = start + offset
 --             local target = target + j * ystep
 --             for target=target,target+step,xstep do
 --                 output[target]   = input[start]
 --                 output[target+1] = input[start+1]
 --                 output[target+2] = input[start+2]
 --                 output[target+3] = input[start+3]
 --                 start = start + slice
 --             end
 --         end
 --     else
 --         local delta = slice - 1
 --         for j=0,ny-1 do
 --             start = start + offset
 --             local target = target + j * ystep
 --             for target=target,target+step,xstep do
 --                 move(input,start,start+delta,target,output)
 --                 start = start + slice
 --             end
 --         end
 --     end
 --     return output;
 -- end

    local transpose_c = pnginterlace

 -- print(band(rshift(v,4),0x03),extract(v,4,2))
 -- print(band(rshift(v,6),0x03),extract(v,6,2))

 -- local function expand_l(t,xsize,ysize,parts,run,factor,filter)
 --     local size  = ysize * xsize + 1 -- a bit of overshoot, needs testing, probably a few bytes us ok
 --     local xline = filter and (run+1) or run
 --     local f     = filter and 1 or 0
 --     local l     = xline - 1
 --     local n     = 1
 --     local o     = newoutput(size)
 --     local k     = 0
 --     if factor then
 --         if parts == 4 then
 --             for i=1,ysize do
 --                 for j=n+f,n+l do
 --                     local v = t[j]
 --                     if v == 0 then
 --                         k = k + 2
 --                     else
 --                         k = k + 1 ; o[k] = extract4(v,4) * 0x11
 --                         k = k + 1 ; o[k] = extract4(v,0) * 0x11
 --                     end
 --                 end
 --                 k = i * xsize
 --                 n = n + xline
 --             end
 --         elseif parts == 2 then
 --             for i=1,ysize do
 --                 for j=n+f,n+l do
 --                     local v = t[j]
 --                     if v == 0 then
 --                         k = k + 4
 --                     else
 --                         k = k + 1 ; o[k] = extract2(v,6) * 0x55
 --                         k = k + 1 ; o[k] = extract2(v,4) * 0x55
 --                         k = k + 1 ; o[k] = extract2(v,2) * 0x55
 --                         k = k + 1 ; o[k] = extract2(v,0) * 0x55
 --                     end
 --                 end
 --                 k = i * xsize
 --                 n = n + xline
 --             end
 --         else
 --             for i=1,ysize do
 --                 for j=n+f,n+l do
 --                     local v = t[j]
 --                     if v == 0 then
 --                         k = k + 8
 --                     else
 --                         k = k + 1 ; if band(v,0x80) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,7) * 0xFF
 --                         k = k + 1 ; if band(v,0x40) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,6) * 0xFF
 --                         k = k + 1 ; if band(v,0x20) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,5) * 0xFF
 --                         k = k + 1 ; if band(v,0x10) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,4) * 0xFF
 --                         k = k + 1 ; if band(v,0x08) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,3) * 0xFF
 --                         k = k + 1 ; if band(v,0x04) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,2) * 0xFF
 --                         k = k + 1 ; if band(v,0x02) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,1) * 0xFF
 --                         k = k + 1 ; if band(v,0x01) ~= 0 then o[k] = 0xFF end -- o[k] = extract1(v,0) * 0xFF
 --                     end
 --                 end
 --                 k = i * xsize
 --                 n = n + xline
 --             end
 --         end
 --     else
 --         if parts == 4 then
 --             for i=1,ysize do
 --                 for j=n+f,n+l do
 --                     local v = t[j]
 --                     if v == 0 then
 --                         k = k + 2
 --                     else
 --                         k = k + 1 ; o[k] = extract4(v,4)
 --                         k = k + 1 ; o[k] = extract4(v,0)
 --                     end
 --                 end
 --                 k = i * xsize
 --                 n = n + xline
 --             end
 --         elseif parts == 2 then
 --             for i=1,ysize do
 --                 for j=n+f,n+l do
 --                     local v = t[j]
 --                     if v == 0 then
 --                         k = k + 4
 --                     else
 --                         k = k + 1 ; o[k] = extract2(v,6)
 --                         k = k + 1 ; o[k] = extract2(v,4)
 --                         k = k + 1 ; o[k] = extract2(v,2)
 --                         k = k + 1 ; o[k] = extract2(v,0)
 --                     end
 --                 end
 --                 k = i * xsize
 --                 n = n + xline
 --             end
 --         else
 --             for i=1,ysize do
 --                 for j=n+f,n+l do
 --                     local v = t[j]
 --                     if v == 0 then
 --                         k = k + 8
 --                     else
 --                         k = k + 1 ; if band(v,0x80) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,7)
 --                         k = k + 1 ; if band(v,0x40) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,6)
 --                         k = k + 1 ; if band(v,0x20) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,5)
 --                         k = k + 1 ; if band(v,0x10) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,4)
 --                         k = k + 1 ; if band(v,0x08) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,3)
 --                         k = k + 1 ; if band(v,0x04) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,2)
 --                         k = k + 1 ; if band(v,0x02) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,1)
 --                         k = k + 1 ; if band(v,0x01) ~= 0 then o[k] = 1 end -- o[k] = extract1(v,0)
 --                     end
 --                 end
 --                 k = i * xsize
 --                 n = n + xline
 --             end
 --         end
 --     end
 --     for i=size,xsize * ysize +1,-1 do
 --         o[i] = nil
 --     end
 --     return o, false
 -- end

    local expand_c = pngexpand

    local function analyze(colordepth,colorspace,palette,mask)
     -- return bytes, parts, factor
        if palette then
            if colordepth == 16 then
                return 2, false, false
            elseif colordepth == 8 then
                return 1, false, false
            elseif colordepth == 4 then
                return 1, 4, false
            elseif colordepth == 2 then
                return 1, 2, false
            elseif colordepth == 1 then
                return 1, 1, false
            end
        elseif colorspace == "DeviceGray" then
            if colordepth == 16 then
                return mask and 4 or 2, false, false
            elseif colordepth == 8 then
                return mask and 2 or 1, false, false
            elseif colordepth == 4 then
                return 1, 4, true
            elseif colordepth == 2 then
                return 1, 2, true
            elseif colordepth == 1 then
                return 1, 1, true
            end
        else
            if colordepth == 16 then
                return mask and 8 or 6, false, false
            elseif colordepth == 8 then
                return mask and 4 or 3, false, false
            elseif colordepth == 4 then
                return 3, 4, true
            elseif colordepth == 2 then
                return 3, 2, true
            elseif colordepth == 1 then
                return 3, 1, true
            end
        end
        return false, false, false
    end

    -- 1 6 4 6 2 6 4 6
    -- 7 7 7 7 7 7 7 7
    -- 5 6 5 6 5 6 5 6
    -- 7 7 7 7 7 7 7 7
    -- 3 6 4 6 3 6 4 6
    -- 7 7 7 7 7 7 7 7
    -- 5 6 5 6 5 6 5 6
    -- 7 7 7 7 7 7 7 7

    local function deinterlace(content,xsize,ysize,colordepth,colorspace,palette,mask)
        local slice, parts, factor = analyze(colordepth,colorspace,palette,mask)
        if slice then
            content = openstring(zlibdecompress(content))
            local filter = false
            local output = false
            for pass=1,7 do
                local xstart = xstart[pass]
                local xstep  = xstep[pass]
                local ystart = ystart[pass]
                local ystep  = ystep[pass]
                local nx     = (xsize + xstep - xstart - 1) // xstep
                local ny     = (ysize + ystep - ystart - 1) // ystep
                if nx > 0 and ny > 0 then
                    local input, filter
                    if parts then
                        local nxx = ceil(nx*parts/8)
                        input, filter = decodestrip(content,nxx,ny,slice)
                        input, filter = expand(input,nx,ny,parts,nxx,factor,filter)
                    else
                        input, filter = decodestrip(content,nx,ny,slice)
                    end
                    output = transpose(xsize,ysize,slice,pass,input,output,filter)
                end
             -- if pass == 3 then
             --     break -- still looks ok, could be nice for a preroll
             -- end
            end
            return output, parts and 8 or false
        end
    end

    -- 1 (palette used), 2 (color used), and 4 (alpha channel used)

    -- paeth:
    --
    -- p  = a + b - c
    -- pa = abs(p - a) => a + b - c - a => b - c
    -- pb = abs(p - b) => a + b - c - b => a - c
    -- pc = abs(p - c) => a + b - c - c => a + b - c - c => a - c + b - c => pa + pb

    local function full(t,k) local v = "\xFF" t[k] = v return v end

    local function expandvector(transparent)
        local s = openstring(transparent)
        local n = #transparent
        local r = { }
        for i=0,n-1 do
            r[i] = readstring(s,1) -- readchar
        end
        setmetatableindex(r,full)
        return r
    end

 -- local function createmask_l(content,palette,transparent,xsize,ysize,colordepth,colorspace)
 --     if palette then
 --         local r    = expandvector(transparent)
 --         local size = xsize*ysize
 --         local len  = ceil(xsize*colordepth/8) + 1
 --         local o    = newoutput(xsize*ysize)
 --         local u    = setmetatableindex(zero)
 --         content    = zlibdecompress(content)
 --         content    = openstring(content)
 --         for i=0,ysize-1 do
 --             local t = readbytetable(content,len)
 --             local k = i * xsize
 --             local filter = t[1]
 --             if filter == 0 then
 --             elseif filter == 1 then
 --                 for j=3,len do
 --                     t[j] = (t[j] + t[j-1]) % 256
 --                 end
 --             elseif filter == 2 then
 --                 for j=2,len do
 --                     t[j] = (t[j] + u[j]) % 256
 --                 end
 --             elseif filter == 3 then
 --                 local j = 2
 --                 t[j] = (t[j] + idiv(u[j],2)) % 256
 --                 for j=3,len do
 --                     t[j] = (t[j] + idiv(t[j-1] + u[j],2)) % 256
 --                 end
 --             elseif filter == 4 then
 --                 local j = 2
 --                 local p = j - len
 --                 local b = t[p]
 --                 if b < 0 then
 --                     b = - b
 --                 end
 --                 if b > 0 then
 --                     t[j] = (t[j] + b) % 256
 --                 end
 --                 for j=3,len do
 --                     local p = j - len
 --                     local a = t[j-1]
 --                     local b = t[p]
 --                     local c = t[p-1]
 --                     local pa = b - c
 --                     local pb = a - c
 --                     local pc = pa + pb
 --                     if pa < 0 then pa = - pa end
 --                     if pb < 0 then pb = - pb end
 --                     if pc < 0 then pc = - pc end
 --                     t[j] = (t[j] + ((pa <= pb and pa <= pc and a) or (pb <= pc and b) or c)) % 256
 --                 end
 --             end
 --             if colordepth == 8 then
 --                 for j=2,len do
 --                     local v = t[j]
 --                     k = k + 1 ; o[k] = r[v]
 --                 end
 --             elseif colordepth == 4 then
 --                 for j=2,len do
 --                     local v = t[j]
 --                     k = k + 1 ; o[k] = r[extract4(v,4)]
 --                     k = k + 1 ; o[k] = r[extract4(v,0)]
 --                 end
 --             elseif colordepth == 2 then
 --                 for j=2,len do
 --                     local v = t[j]
 --                     k = k + 1 ; o[k] = r[extract2(v,6)]
 --                     k = k + 1 ; o[k] = r[extract2(v,4)]
 --                     k = k + 1 ; o[k] = r[extract2(v,2)]
 --                     k = k + 1 ; o[k] = r[extract2(v,0)]
 --                 end
 --             else
 --                 for j=2,len do
 --                     local v = t[j]
 --                     k = k + 1 ; o[k] = r[extract1(v,7)]
 --                     k = k + 1 ; o[k] = r[extract1(v,6)]
 --                     k = k + 1 ; o[k] = r[extract1(v,5)]
 --                     k = k + 1 ; o[k] = r[extract1(v,4)]
 --                     k = k + 1 ; o[k] = r[extract1(v,3)]
 --                     k = k + 1 ; o[k] = r[extract1(v,2)]
 --                     k = k + 1 ; o[k] = r[extract1(v,1)]
 --                     k = k + 1 ; o[k] = r[extract1(v,0)]
 --                 end
 --             end
 --             u = t
 --         end
 --         return concat(o,"",1,size)
 --     end
 -- end

 -- We had this for a while (the reference now):
 --
 -- local function createmask_c(content,palette,transparent,xsize,ysize,colordepth,colorspace)
 --     if palette then
 --         local r    = expandvector(transparent)
 --         local size = xsize*ysize
 --         local len  = ceil(xsize*colordepth/8)
 --         local o    = newoutput(size)
 --         content    = zlibdecompress(content)
 --         content    = pngapplyfilter(content,len,ysize,1) -- nostrip (saves copy)
 --         content    = openstring(content)
 --         for i=0,ysize-1 do
 --             local t = readbytetable(content,len)
 --             local k = i * xsize
 --             if colordepth == 8 then
 --                 for j=1,len do
 --                     local v = t[j]
 --                     k = k + 1 ; o[k] = r[v]
 --                 end
 --             elseif colordepth == 4 then
 --                 for j=1,len do
 --                     local v = t[j]
 --                     k = k + 1 ; o[k] = r[(v >> 4) & 0x0F] -- r[extract4(v,4)]
 --                     k = k + 1 ; o[k] = r[(v >> 0) & 0x0F] -- r[extract4(v,0)]
 --                 end
 --             elseif colordepth == 2 then
 --                 for j=1,len do
 --                     local v = t[j]
 --                     k = k + 1 ; o[k] = r[(v >> 6) & 0x03] -- r[extract2(v,6)]
 --                     k = k + 1 ; o[k] = r[(v >> 4) & 0x03] -- r[extract2(v,4)]
 --                     k = k + 1 ; o[k] = r[(v >> 2) & 0x03] -- r[extract2(v,2)]
 --                     k = k + 1 ; o[k] = r[(v >> 0) & 0x03] -- r[extract2(v,0)]
 --                 end
 --             else
 --                 for j=1,len do
 --                     local v = t[j]
 --                     k = k + 1 ; o[k] = r[(v >> 7) & 0x01] -- r[extract1(v,7)]
 --                     k = k + 1 ; o[k] = r[(v >> 6) & 0x01] -- r[extract1(v,6)]
 --                     k = k + 1 ; o[k] = r[(v >> 5) & 0x01] -- r[extract1(v,5)]
 --                     k = k + 1 ; o[k] = r[(v >> 4) & 0x01] -- r[extract1(v,4)]
 --                     k = k + 1 ; o[k] = r[(v >> 3) & 0x01] -- r[extract1(v,3)]
 --                     k = k + 1 ; o[k] = r[(v >> 2) & 0x01] -- r[extract1(v,2)]
 --                     k = k + 1 ; o[k] = r[(v >> 1) & 0x01] -- r[extract1(v,1)]
 --                     k = k + 1 ; o[k] = r[(v >> 0) & 0x01] -- r[extract1(v,0)]
 --                 end
 --             end
 --         end
 --         return concat(o,"",1,size)
 --     end
 -- end
 --
 -- But this is nicer for memory usage:

    local function createmask_c(content,palette,transparent,xsize,ysize,colordepth,colorspace)
        if palette then
            local len = ceil(xsize*colordepth/8)
            content = zlibdecompress(content)
            content = pngapplyfilter(content,len,ysize,1) -- nostrip (saves copy)
            return pngtomask(content,transparent,xsize,ysize,colordepth)
        end
    end

 -- local function tocmyk_l(content,colordepth)
 --     local l = #content
 --     local t = { }
 --     local n = 0
 --     if colordepth == 8 then
 --         for i=1,l,3 do
 --             local r, g, b = byte(content,i,i+2)
 --             n = n + 1 ; t[n] = char(255-r,255-g,255-b,0) -- a tad faster than chars[...]
 --         end
 --     else
 --         for i=1,l,6 do
 --             local r1, r2, g1, g2, b1, b2 = byte(content,i,i+5)
 --             n = n + 1 ; t[n] = char(255-r1,255-r2,255-g1,255-g2,255-b1,255-b2,0,0)
 --         end
 --     end
 --     return concat(t)
 -- end

    local tocmyk_c = pngtocmyk

    local function converttocmyk(content,colorspace,colordepth)
        if colorspace == "DeviceRGB" and colordepth == 8 or colordepth == 16 then
            local done = tocmyk(content,colordepth)
            if done then
                content    = done
                colorspace = "DeviceCMYK"
            end
        end
        return content, colorspace
    end

 -- local function switch(v)
 --     if v then
 --         filtermask  = filtermask_l
 --         decodemask  = decodemask_l
 --         decodestrip = decodestrip_l
 --         transpose   = transpose_l
 --         expand      = expand_l
 --         createmask  = createmask_l
 --         tocmyk      = tocmyk_l
 --     else
            filtermask  = filtermask_c
            decodemask  = decodemask_c
            decodestrip = decodestrip_c
            transpose   = transpose_c
            expand      = expand_c
            createmask  = createmask_c
            tocmyk      = tocmyk_c
 --     end
 -- end

 -- if pngapplyfilter then
 --     switch(false)
 --     directives.register("graphics.png.purelua",switch)
 -- else
 --     switch(true)
 -- end

    local alwaysdecode  = false -- trucky with palettes
    local compresslevel = 3
    local expandpalette = false

    directives.register("graphics.png.recompress", function(v)
        alwaysdecode = v
    end)

    directives.register("graphics.png.compresslevel", function(v)
        v = tonumber(v)
        if compresslevel >= 0 or compresslevel <= 9 then
            compresslevel = v
        else
         -- compresslevel = 3
        end
    end)

    directives.register("graphics.png.expandpalette", function(v)
        expandpalette = v
    end)

    function injectors.png(specification,method) -- todo: method in specification
        if specification.error then
            return
        end
        local filename = specification.filename
        if not filename then
            return
        end
        local colorspace = specification.colorspace
        if not colorspace then
            return
        end
        local interlace = specification.interlace or 0
        if interlace == 1 then
            interlace = true
        elseif interlace == 0 then
            interlace = false
        else
            report_png("unknown interlacing %i",interlace)
            return
        end
        local tables = specification.tables
        if not tables then
            return
        end
        local idat = tables.idat
        if not idat then
            return
        end
        local pngfile = newcontent(filename,method,true)
        if not pngfile then
            return
        end
        local content = idat(pngfile,true)
        tables.idat = false
        --
     -- if tables.gama then
     --     report_png("ignoring gamma correction")
     -- end
        --
        local lmtxcmyk = false
        local lmtxmask = false
        local lmtxcomp = false
        --
        local xsize       = specification.xsize
        local ysize       = specification.ysize
        local colordepth  = specification.colordepth or 8
        local devicespace = false
        local mask        = false
        local transparent = false
        local palette     = false
        local enforcecmyk = specification.enforcecmyk
        local colors      = 1
        local lmtxdata = pdfdictionary {
            Format = pdfconstant("PNG"),
            Colors = colors,
            XSize  = xsize,
            YSize  = ysize,
        }
        if     colorspace == 0 then    -- gray | image b
            devicespace = "DeviceGray"
            colorspace  = "DeviceGray"
            transparent = true
        elseif colorspace == 2 then    -- rgb | image c
            devicespace = "DeviceRGB"
            colorspace  = "DeviceRGB"
            colors      = 3
            transparent = true
        elseif colorspace == 3 then    -- palette | image c+i
            devicespace = "DeviceRGB"
            devicespace = "DeviceRGB"
            palette     = true
            transparent = true
        elseif colorspace == 4 then    -- gray | alpha | image b
            colorspace  = "DeviceGray"
            devicespace = "DeviceGray"
            mask        = true
        elseif colorspace == 6 then    -- rgb | alpha | image c
            devicespace = "DeviceRGB"
            colorspace  = "DeviceRGB"
            colors      = 3
            mask        = true
        else
            report_png("unknown colorspace %i",colorspace)
            return
        end
        --
        if transparent then
            local trns = tables.trns
            if trns then
                transparent = trns(pngfile,true)
                if transparent == "" then
                    transparent = false
                end
                tables.trns = false
            else
                transparent = false
            end
            if transparent then
                lmtxdata.Transparent = true
            end
        end
        --
        local decode = alwaysdecode -- tricky, might go away
        local filter = pdfconstant("FlateDecode")
        local major  = pdfmajorversion()
        local minor  = pdfminorversion()
        if major > 1 then
            -- we're okay
        elseif minor < 5 and colordepth == 16 then
            report_png("16 bit colordepth not supported in pdf < 1.5")
            return
        elseif minor < 4 and (mask or transparent) then
            report_png("alpha channels not supported in pdf < 1.4")
            return
        elseif minor < 2 then
            report_png("you'd better use a version > 1.2")
            return
         -- decode = true
        end
        if decode then
            lmtxdata.Decoded = true
        end
        --
        -- todo: compresslevel (or delegate)
        --
        if palette then
            local plte = tables.plte
            if plte then
                palette = plte(pngfile,true)
                if palette == "" then
                    palette = false
                end
                tables.plte = false
            else
                palette = false
            end
            if palette then
                lmtxdata.Palette = true
            end
        end
        --
        local newmask   = specification.newmask
        local newranges = specification.newranges
        --
        if newranges then
            newranges = todecode(newranges,colors)
            lmtxdata.Remapped = true
        end
        --
-- print(interlace,colordepth,transparent and true or false,palette and true or false,mask and true or false)
        if interlace then
            local r, p = deinterlace(content,xsize,ysize,colordepth,devicespace,palette,mask)
            if not r then
                return
            end
            lmtxdata.Deinterlaced = true
            if p then
                colordepth = p
            end
            if mask then
                if not (colordepth == 8 or colordepth == 16) then
                    report_png("mask can't be split from the image (%s)","depth")
                    return
                end -- get rid of bpp:
                content, mask = filtermask(r,xsize,ysize,colordepth,devicespace,false)
            elseif frompalette and transparent and palette then
                if devicespace == "DeviceRGB" and colordepth == 8 then
                    local bytes = 3 -- index -> mapped -> r g b
                    local parts = 1 -- whole byte
                    if expandpalette then
                        local c, m = frompalette(r,palette,bytes,parts,transparent)
                        if c and m then
                            content  = c
                            palette  = false
                            mask     = m
                            lmtxmask = "Expanded"
                        else
                            report_png("mask can't be split from the image (%s)","error")
                            content = convert(r) -- can be in deinterlace if needed
                            lmtxmask = "Discarded"
                        end
                    else
                        mask = palettemask(r,bytes,parts,transparent)
                        if mask then
                            lmtxmask = "Separated"
                        else
                            report_png("mask can't be split from the image (%s)","error")
                            lmtxmask = "Discarded"
                        end
                        content = convert(r) -- can be in deinterlace if needed
                    end
                else
                    report_png("mask can't be split from the image (%s)","colorspace")
                    content = convert(r) -- can be in deinterlace if needed
                    lmtxmask = "Discarded"
                end
            else
                content = convert(r) -- can be in deinterlace if needed
            end
            if enforcecmyk then
                content, devicespace = converttocmyk(content,devicespace,colordepth)
                lmtxcmyk = true
            end
            if compresslevel > 0 then
                content = zlibcompress(content,compresslevel)
                lmtxcomp = true
            else
                filter = nil
            end
            decode = true
        elseif mask then
            if not (colordepth == 8 or colordepth == 16) then
                report_png("mask can't be split from the image")
                return
            end
            content = zlibdecompress(content)
            content, mask = decodemask(content,xsize,ysize,colordepth,devicespace)
            if enforcecmyk and not palette then
                content, devicespace = converttocmyk(content,devicespace,colordepth)
                lmtxcmyk = true
            end
            if compresslevel > 0 then
                content  = zlibcompress(content,compresslevel)
                lmtxcomp = true
            else
                filter = nil
            end
            lmtxmask = "Decoded"
            decode  = true -- we don't copy the filter byte
        elseif transparent then
            -- in test suite
            -- how about decode/recompress here
            if palette then
                mask = createmask(content,palette,transparent,xsize,ysize,colordepth,devicespace)
                lmtxmask = "Created"
            else
                -- maybe a todo
                lmtxmask = "Ignored"
            end
        elseif decode or (enforcecmyk and not palette) then
            -- this one needs checking
            local bytes = analyze(colordepth,devicespace)
            if bytes then
                content = zlibdecompress(content)
                content = decodestrip(openstring(content),xsize,ysize,bytes)
                if enforcecmyk and not palette then
                    content, devicespace = converttocmyk(content,devicespace,colordepth)
                    lmtxcmyk = true
                end
                if compresslevel > 0 then
                    content  = zlibcompress(content,compresslevel)
                    lmtxcomp = compresslevel
                else
                    filter = nil
                end
            else
                return
            end
            decode = true -- due to enforcecmyk
        elseif newmask and colordepth == 8 and devicespace == "DeviceGray" then
            local bytes = analyze(colordepth,devicespace)
            if bytes then
                content = zlibdecompress(content)
                content = decodestrip(openstring(content),xsize,ysize,bytes)
                mask = pngmakemask(content,newmask)
                if compresslevel > 0 then
                    content  = zlibcompress(content,compresslevel)
                    lmtxcomp = compresslevel
                else
                    filter = nil
                end
                lmtxmask = "Created"
            else
                return
            end
            decode = true -- due to enforcecmyk
        else
         -- print("PASS ON")
        end
        if palette then
            local devicespace = "DeviceRGB"
            local nofbytes    = 3
            if enforcecmyk then
                palette       = converttocmyk(palette,devicespace,8)
                devicespace   = "DeviceCMYK"
                nofbytes      = 4
                lmtxcmyk = true
            end
            palette = pdfarray {
                pdfconstant("Indexed"),
                pdfconstant(devicespace),
                #palette // nofbytes,
                pdfreference(pdfflushstreamobject(palette)),
            }
        end
        if lmtxcmyk then
            lmtxdata.CMYK = true
        end
        if lmtxmask then
            lmtxdata.Mask = pdfconstant(lmtxmask)
        end
        if lmtxcomp then
            lmtxdata.Compresslevel = compresslevel
        end
        pngfile:close()
        local xobject = pdfdictionary {
            Type             = pdfconstant("XObject"),
            Subtype          = pdfconstant("Image"),
         -- BBox             = pdfarray { 0, 0, xsize, ysize },
            Width            = xsize,
            Height           = ysize,
            BitsPerComponent = colordepth,
            Filter           = filter,
            ColorSpace       = palette or pdfconstant(devicespace),
            Length           = #content,
            Decode           = newranges and pdfarray(newranges) or nil,
            LMTX_Graphicdata = pdfreference(pdfflushobject(lmtxdata)),
        } + specification.attr
        if mask then
            local d = pdfdictionary {
                Type             = pdfconstant("XObject"),
                Subtype          = pdfconstant("Image"),
                Width            = xsize,
                Height           = ysize,
                BitsPerComponent = palette and 8 or colordepth,
                ColorSpace       = pdfconstant("DeviceGray"),
                Decode           = newranges and pdfarray(newranges) or nil,
            }
            xobject.SMask = pdfreference(pdfflushstreamobject(mask,d()))
        end
        if not decode then
            -- not if Decode set?
            xobject.DecodeParms  = pdfdictionary {
                Colors           = colors,
                Columns          = xsize,
                BitsPerComponent = colordepth,
                Predictor        = 15,
            }
        end
        if trace then
            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)
        end
        if specification.colorref then
            xobject.ColorSpace = pdfreference(specification.colorref)
        end
        local width  = specification.width  or xsize * 65536
        local height = specification.height or ysize * 65536
        if cleanvirtual then
            cleanvirtual(filename)
        end
        return createimage {
            bbox      = { 0, 0, width/xsize, height/ysize }, -- mandate
            transform = specification.transform,
            nolength  = true,
            nobbox    = true,
            notype    = true,
            stream    = content,
            attr      = xobject(),
        }
    end

end

do

    -- we could have a helper for this

    local function pack(specification,what)
        local t = { }
        local n = 0
        local s = specification.colorspace
        local d = specification.data
        local x = specification.xsize
        local y = specification.ysize
        if what == "mask" then
            d = specification.mask
            s = 1
        elseif what == "indexed" then
            s = 1
        elseif what == "index" then
            d = specification.index
            s = - s
        end
        if s > 0 then
         -- if true then
                return string.packrowscolumns(d)
         -- end
         -- if s == 1 then
         --     for i=1,y do
         --         local r = d[i]
         --         for j=1,x do
         --             n = n + 1 ; t[n] = chars[r[j]]
         --         end
         --     end
         -- elseif s == 2 then
         --     for i=1,y do
         --         local r = d[i]
         --         for j=1,x do
         --             local c = r[j]
         --             n = n + 1 ; t[n] = chars[c[1]]
         --             n = n + 1 ; t[n] = chars[c[2]]
         --             n = n + 1 ; t[n] = chars[c[3]]
         --          -- n = n + 1 ; t[n] = char(c[1],c[2],c[3]) -- test this
         --         end
         --     end
         -- elseif s == 3 then
         --     for i=1,y do
         --         local r = d[i]
         --         for j=1,x do
         --             local c = r[j]
         --             n = n + 1 ; t[n] = chars[c[1]]
         --             n = n + 1 ; t[n] = chars[c[2]]
         --             n = n + 1 ; t[n] = chars[c[3]]
         --             n = n + 1 ; t[n] = chars[c[4]]
         --          -- n = n + 1 ; t[n] = char(c[1],c[2],c[3],c[4]) -- test this
         --         end
         --     end
         -- end
         -- return concat(t)
        else
            local z = d[0] and 0 or 1
            if s == -1 then
                local f = formatters["%02X"]
                for i=z,#d do
                    n = n + 1 ; t[n] = f(d[i])
                end
            elseif s == -2 then
                local f = formatters["%02X%02X%02X"]
                for i=z,#d do
                    local c = d[i]
                    n = n + 1 ; t[n] = f(c[1],c[2],c[3])
                end
            elseif s == -3 then
                local f = formatters["%02X%02X%02X%02X"]
                for i=z,#d do
                    local c = d[i]
                    n = n + 1 ; t[n] = f(c[1],c[2],c[3],c[4])
                end
            end
            return "<" .. concat(t," ") .. ">"
        end
        return ""
    end

    function injectors.bitmap(specification)
        local data = specification.data
        if not data then
            return
        end
        local xsize = specification.xsize or 0
        local ysize = specification.ysize or 0
        if xsize == 0 or ysize == 0 then
            return
        end
        local colorspace = specification.colorspace or 1
        if colorspace == 1 then
            colorspace = "DeviceGray"
        elseif colorspace == 2 then
            colorspace = "DeviceRGB"
        elseif colorspace == 3 then
            colorspace = "DeviceCMYK"
        end
        local colordepth = (specification.colordepth or 2) == 16 or 8
        local index      = specification.index
        local content    = pack(specification,index and "indexed" or "data")
        local mask       = specification.mask
        local colorspace = pdfconstant(colorspace)
        if index then
            colorspace = pdfarray {
                pdfconstant("Indexed"),
                colorspace,
                #index + (index[0] and 0 or -1), -- upper index
                pdfverbose(pack(specification,"index"))
            }
        end
        local xobject    = pdfdictionary {
            Type             = pdfconstant("XObject"),
            Subtype          = pdfconstant("Image"),
            BBox             = pdfarray { 0, 0, xsize, ysize },
            Width            = xsize,
            Height           = ysize,
            BitsPerComponent = colordepth,
            ColorSpace       = colorspace,
            Length           = #content, -- specification.length
        }
        if mask then
            local d = pdfdictionary {
                Type             = pdfconstant("XObject"),
                Subtype          = pdfconstant("Image"),
                Width            = xsize,
                Height           = ysize,
                BitsPerComponent = colordepth,
                ColorSpace       = pdfconstant("DeviceGray"),
            }
            xobject.SMask = pdfreference(pdfflushstreamobject(pack(specification,"mask"),d()))
        end
        local w = specification.width
        local h = specification.height
        return createimage {
            bbox     = { 0, 0, w and (w/xsize) or xsize, h and (h/ysize) or ysize }, -- mandate
         -- nolength = true,
            nobbox   = true,
            notype   = true,
            stream   = content,
            attr     = xobject(),
        }
    end

    codeinjections.bitmap = injectors.bitmap

end

-- local function validcompression(data)
--     local d  = utilities.streams.openstring(data)
--     local b1 = utilities.streams.readbyte(d)
--     local b2 = utilities.streams.readbyte(d)
--     print(b1,b2)
--     if (b1 * 256 + b2) % 31 ~= 0 then
--         return false, "no zlib compressed file"
--     end
--     local method = band(b1,15)
--     if method ~= 8 then
--         return false, "method 8 expected"
--     end
--     local detail = band(rshift(b1,4),15)
--     if detail > 7 then
--         return false, "window 32 expected"
--     end
--     local preset = band(rshift(b2,5),1)
--     if preset ~= 0 then
--         return false, "unexpected preset dictionary"
--     end
--     return true
-- end

codeinjections.jpg = lpdf.injectors.jpg
codeinjections.jp2 = lpdf.injectors.jp2
codeinjections.png = lpdf.injectors.png
