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 pdfstring = lpdf.string local pdfreference = lpdf.reference local pdfverbose = lpdf.verbose 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_png = 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. 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 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 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 end 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 xsize = specification.xsize local ysize = specification.ysize local colordepth = specification.colordepth or 8 local mask = false local transparent = false local palette = false local enforcecmyk = specification.enforcecmyk local colors = 1 if colorspace == 0 then -- gray | image b colorspace = "DeviceGray" transparent = true elseif colorspace == 2 then -- rgb | image c colorspace = "DeviceRGB" colors = 3 transparent = true elseif colorspace == 3 then -- palette | image c+i colorspace = "DeviceRGB" palette = true transparent = true elseif colorspace == 4 then -- gray | alpha | image b colorspace = "DeviceGray" mask = true elseif colorspace == 6 then -- rgb | alpha | image c 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 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 -- -- 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 end -- local newmask = specification.newmask local newranges = specification.newranges -- if newranges then newranges = todecode(newranges,colors) end -- if interlace then local r, p = deinterlace(content,xsize,ysize,colordepth,colorspace,palette,mask) if not r then return end 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") return end -- get rid of bpp: content, mask = filtermask(r,xsize,ysize,colordepth,colorspace,false) else content = convert(r) -- can be in deinterlace if needed end if enforcecmyk then content, colorspace = converttocmyk(content,colorspace,colordepth) end if compresslevel > 0 then content = zlibcompress(content,compresslevel) 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,colorspace) if enforcecmyk and not palette then content, colorspace = converttocmyk(content,colorspace,colordepth) end if compresslevel > 0 then content = zlibcompress(content,compresslevel) else filter = nil end 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,colorspace) else pallette = false end elseif decode or (enforcecmyk and not palette) then -- this one needs checking local bytes = analyze(colordepth,colorspace) if bytes then content = zlibdecompress(content) content = decodestrip(openstring(content),xsize,ysize,bytes) if enforcecmyk and not palette then content, colorspace = converttocmyk(content,colorspace,colordepth) end if compresslevel > 0 then content = zlibcompress(content,compresslevel) else filter = nil end else return end decode = true -- due to enforcecmyk elseif newmask and colordepth == 8 and colorspace == "DeviceGray" then local bytes = analyze(colordepth,colorspace) 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) else filter = nil end else return end decode = true -- due to enforcecmyk else -- print("PASS ON") end if palette then local colorspace = "DeviceRGB" local nofbytes = 3 if enforcecmyk then palette = converttocmyk(palette,colorspace,8) colorspace = "DeviceCMYK" nofbytes = 4 end palette = pdfarray { pdfconstant("Indexed"), pdfconstant(colorspace), #palette // nofbytes, pdfreference(pdfflushstreamobject(palette)), } 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(colorspace), Length = #content, Decode = newranges and pdfarray(newranges) or nil, } + 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 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