From 64f6e8fc0c6cb9254a6fe3db0b4ab31c51cf8524 Mon Sep 17 00:00:00 2001 From: Hans Hagen Date: Thu, 4 Apr 2019 14:11:01 +0200 Subject: 2019-04-04 13:38:00 --- tex/context/base/mkiv/grph-img.lua | 748 +++++++++++++++++++++++++++++++++++++ 1 file changed, 748 insertions(+) create mode 100644 tex/context/base/mkiv/grph-img.lua (limited to 'tex/context/base/mkiv/grph-img.lua') diff --git a/tex/context/base/mkiv/grph-img.lua b/tex/context/base/mkiv/grph-img.lua new file mode 100644 index 000000000..9acd1a5de --- /dev/null +++ b/tex/context/base/mkiv/grph-img.lua @@ -0,0 +1,748 @@ + if not modules then modules = { } end modules ['grph-img'] = { + version = 1.001, + comment = "companion to grph-inc.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- The jpg identification and inclusion code is based on the code in \LUATEX\ but as we +-- use \LUA\ we can do it a bit cleaner. We can also use some helpers for reading from +-- file. We could make it even more lean and mean. When all works out ok I will clean +-- up this code a bit as we can divert more from luatex. + +local lower, strip = string.lower, string.strip +local round = math.round +local concat = table.concat +local suffixonly = file.suffix + +local files = utilities.files +local getsize = files.getsize +local readbyte = files.readbyte +local readstring = files.readstring +local readcardinal = files.readcardinal +local readcardinal2 = files.readcardinal2 +local readcardinal4 = files.readcardinal4 +local readcardinal2le = files.readcardinal2le +local readcardinal4le = files.readcardinal4le +local skipbytes = files.skip +local setposition = files.setposition +local getposition = files.getposition + +local setmetatableindex = table.setmetatableindex +local setmetatablecall = table.setmetatablecall + +local lpdf = lpdf or { } +local pdfmajorversion = lpdf.majorversion +local pdfminorversion = lpdf.minorversion + +local graphics = graphics or { } +local identifiers = { } +graphics.identifiers = identifiers + +do + + local colorspaces = { + [1] = 1, -- gray + [3] = 2, -- rgb + [4] = 3, -- cmyk + } + + local tags = { + [0xC0] = { name = "SOF0", }, -- baseline DCT + [0xC1] = { name = "SOF1", }, -- extended sequential DCT + [0xC2] = { name = "SOF2", }, -- progressive DCT + [0xC3] = { name = "SOF3", supported = false }, -- lossless (sequential) + + [0xC5] = { name = "SOF5", supported = false }, -- differential sequential DCT + [0xC6] = { name = "SOF6", supported = false }, -- differential progressive DCT + [0xC7] = { name = "SOF7", supported = false }, -- differential lossless (sequential) + + [0xC8] = { name = "JPG", }, -- reserved for JPEG extensions + [0xC9] = { name = "SOF9", }, -- extended sequential DCT + [0xCA] = { name = "SOF10", supported = false }, -- progressive DCT + [0xCB] = { name = "SOF11", supported = false }, -- lossless (sequential) + + [0xCD] = { name = "SOF13", supported = false }, -- differential sequential DCT + [0xCE] = { name = "SOF14", supported = false }, -- differential progressive DCT + [0xCF] = { name = "SOF15", supported = false }, -- differential lossless (sequential) + + [0xC4] = { name = "DHT" }, -- define Huffman table(s) + + [0xCC] = { name = "DAC" }, -- define arithmetic conditioning table + + [0xD0] = { name = "RST0", zerolength = true }, -- restart + [0xD1] = { name = "RST1", zerolength = true }, -- restart + [0xD2] = { name = "RST2", zerolength = true }, -- restart + [0xD3] = { name = "RST3", zerolength = true }, -- restart + [0xD4] = { name = "RST4", zerolength = true }, -- restart + [0xD5] = { name = "RST5", zerolength = true }, -- restart + [0xD6] = { name = "RST6", zerolength = true }, -- restart + [0xD7] = { name = "RST7", zerolength = true }, -- restart + + [0xD8] = { name = "SOI", zerolength = true }, -- start of image + [0xD9] = { name = "EOI", zerolength = true }, -- end of image + [0xDA] = { name = "SOS" }, -- start of scan + [0xDB] = { name = "DQT" }, -- define quantization tables + [0xDC] = { name = "DNL" }, -- define number of lines + [0xDD] = { name = "DRI" }, -- define restart interval + [0xDE] = { name = "DHP" }, -- define hierarchical progression + [0xDF] = { name = "EXP" }, -- expand reference image(s) + + [0xE0] = { name = "APP0" }, -- application marker, used for JFIF + [0xE1] = { name = "APP1" }, -- application marker + [0xE2] = { name = "APP2" }, -- application marker + [0xE3] = { name = "APP3" }, -- application marker + [0xE4] = { name = "APP4" }, -- application marker + [0xE5] = { name = "APP5" }, -- application marker + [0xE6] = { name = "APP6" }, -- application marker + [0xE7] = { name = "APP7" }, -- application marker + [0xE8] = { name = "APP8" }, -- application marker + [0xE9] = { name = "APP9" }, -- application marker + [0xEA] = { name = "APP10" }, -- application marker + [0xEB] = { name = "APP11" }, -- application marker + [0xEC] = { name = "APP12" }, -- application marker + [0xED] = { name = "APP13" }, -- application marker + [0xEE] = { name = "APP14" }, -- application marker, used by Adobe + [0xEF] = { name = "APP15" }, -- application marker + + [0xF0] = { name = "JPG0" }, -- reserved for JPEG extensions + [0xFD] = { name = "JPG13" }, -- reserved for JPEG extensions + [0xFE] = { name = "COM" }, -- comment + + [0x01] = { name = "TEM", zerolength = true }, -- temporary use + } + + -- More can be found in http://www.exif.org/Exif2-2.PDF but basically we have + -- good old tiff tags here. + + local function read_APP1_Exif(f, xres, yres, orientation) -- untested + local position = false + local readcardinal2 = readcardinal2 + local readcardinal4 = readcardinal4 + -- endian II|MM + while true do + position = getposition(f) + local b = readbyte(f) + if b == 0 then + -- next one + elseif b == 0x4D and readbyte(f) == 0x4D then -- M + -- big endian + break + elseif b == 0x49 and readbyte(f) == 0x49 then -- I + -- little endian + readcardinal2 = readcardinal2le + readcardinal4 = readcardinal4le + break + else + -- warning "bad exif data" + return xres, yres, orientation + end + end + -- version + local version = readcardinal2(f) + if version ~= 42 then + return xres, yres, orientation + end + -- offset to records + local offset = readcardinal4(f) + if not offset then + return xres, yres, orientation + end + setposition(f,position + offset) + local entries = readcardinal2(f) + if not entries or entries == 0 then + return xres, yres, orientation + end + local x_res, y_res, x_res_ms, y_res_ms, x_temp, y_temp + local res_unit, res_unit_ms + for i=1,entries do + local tag = readcardinal2(f) + local kind = readcardinal2(f) + local size = readcardinal4(f) + local value = 0 + local num = 0 + local den = 0 + if kind == 1 or kind == 7 then -- byte | undefined + value = readbyte(f) + skipbytes(f,3) + elseif kind == 3 or kind == 8 then -- (un)signed short + value = readcardinal2(f) + skipbytes(f,2) + elseif kind == 4 or kind == 9 then -- (un)signed long + value = readcardinal4(f) + elseif kind == 5 or kind == 10 then -- (s)rational + local offset = readcardinal4(f) + local saved = getposition(f) + setposition(f,position+offset) + num = readcardinal4(f) + den = readcardinal4(f) + setposition(f,saved) + else -- 2 -- ascii + skipbytes(f,4) + end + if tag == 274 then -- orientation + orientation = value + elseif tag == 282 then -- x resolution + if den ~= 0 then + x_res = num/den + end + elseif tag == 283 then -- y resolution + if den ~= 0 then + y_res = num/den + end + elseif tag == 296 then -- resolution unit + if value == 2 then + res_unit = 1 + elseif value == 3 then + res_unit = 2.54 + end + elseif tag == 0x5110 then -- pixel unit + res_unit_ms = value == 1 + elseif tag == 0x5111 then -- x pixels per unit + x_res_ms = value + elseif tag == 0x5112 then -- y pixels per unit + y_res_ms = value + end + end + if x_res and y_res and res_unit and res_unit > 0 then + x_temp = round(x_res * res_unit) + y_temp = round(y_res * res_unit) + elseif x_res_ms and y_res_ms and res_unit_ms then + x_temp = round(x_res_ms * 0.0254) -- in meters + y_temp = round(y_res_ms * 0.0254) -- in meters + end + if x_temp and a_temp and x_temp > 0 and y_temp > 0 then + if (x_temp ~= x_res or y_temp ~= y_res) and x_res ~= 0 and y_res ~= 0 then + -- exif resolution differs from already found resolution + elseif x_temp == 1 or y_temp == 1 then + -- exif resolution is kind of weird + else + return x_temp, y_temp, orientation + end + end + return round(xres), round(yres), orientation + end + + function identifiers.jpg(filename) + local specification = { + filename = filename, + filetype = "jpg", + } + if not filename or filename == "" then + specification.error = "invalid filename" + return specification -- error + end + local f = io.open(filename,"rb") + if not f then + specification.error = "unable to open file" + return specification -- error + end + specification.xres = 0 + specification.yres = 0 + specification.orientation = 1 + specification.totalpages = 1 + specification.pagenum = 1 + specification.length = 0 + local banner = readcardinal2(f) + if banner ~= 0xFFD8 then + specification.error = "no jpeg file" + return specification -- error + end + local xres = 0 + local yres = 0 + local orientation = 1 + local okay = false + local filesize = getsize(f) -- seek end + local majorversion = pdfmajorversion and pdfmajorversion() or 2 + local minorversion = pdfminorversion and pdfminorversion() or 2 + while getposition(f) < filesize do + local b = readbyte(f) + if not b then + break + elseif b ~= 0xFF then + if not okay then + -- or check for size + specification.error = "incomplete file" + end + break + end + local category = readbyte(f) + local position = getposition(f) + local length = 0 + local tagdata = tags[category] + if not tagdata then + specification.error = "invalid tag" + break + elseif tagdata.supported == false then + specification.error = "unsupported " .. tagdata.comment + break + end + local name = tagdata.name + if name == "SOF0" or name == "SOF1" or name == "SOF2" then + if majorversion == 1 and minorversion <= 2 then + specification.error = "no progressive DCT in PDF <= 1.2" + break + end + length = readcardinal2(f) + specification.colordepth = readcardinal(f) + specification.ysize = readcardinal2(f) + specification.xsize = readcardinal2(f) + specification.colorspace = colorspaces[readcardinal(f)] + if not specification.colorspace then + specification.error = "unsupported color space" + break + end + okay = true + elseif name == "APP0" then + length = readcardinal2(f) + if length > 6 then + local format = readstring(f,5) + if format == "JFIF\000" then + skipbytes(f,2) + units = readcardinal(f) + xres = readcardinal2(f) + yres = readcardinal2(f) + if units == 1 then + -- pixels per inch + if xres == 1 or yres == 1 then + -- warning + end + elseif units == 2 then + -- pixels per cm */ + xres = xres * 2.54 + yres = yres * 2.54 + else + xres = 0 + yres = 0 + end + end + end + elseif name == "APP1" then + length = readcardinal2(f) + if length > 7 then + local format = readstring(f,5) + if format == "Exif\000" then + xres, yres, orientation = read_APP1_Exif(f,xres,yres,orientation) + end + end + elseif not tagdata.zerolength then + length = readcardinal2(f) + end + if length > 0 then + setposition(f,position+length) + end + end + f:close() + if not okay then + specification.error = specification.error or "invalid file" + elseif not specification.error then + if xres == 0 and yres ~= 0 then + xres = yres + end + if yres == 0 and xres ~= 0 then + yres = xres + end + end + specification.xres = xres + specification.yres = yres + specification.orientation = orientation + specification.length = filesize + return specification + end + +end + +do + + local function read_boxhdr(specification,f) + local size = readcardinal4(f) + local kind = readstring(f,4) + if kind then + kind = strip(lower(kind)) + else + kind = "" + end + if size == 1 then + size = readcardinal4(f) * 0xFFFF0000 + readcardinal4(f) + end + if size == 0 and kind ~= "jp2c" then -- move this + specification.error = "invalid size" + end + return kind, size + end + + local function scan_ihdr(specification,f) + specification.ysize = readcardinal4(f) + specification.xsize = readcardinal4(f) + skipbytes(f,2) -- nc + specification.colordepth = readcardinal(f) + 1 + skipbytes(f,3) -- c unkc ipr + end + + local function scan_resc_resd(specification,f) + local vr_n = readcardinal2(f) + local vr_d = readcardinal2(f) + local hr_n = readcardinal2(f) + local hr_d = readcardinal2(f) + local vr_e = readcardinal(f) + local hr_e = readcardinal(f) + specification.xres = math.round((hr_n / hr_d) * math.exp(hr_e * math.log(10.0)) * 0.0254) + specification.yres = math.round((vr_n / vr_d) * math.exp(vr_e * math.log(10.0)) * 0.0254) + end + + local function scan_res(specification,f,last) + local pos = getposition(f) + while true do + local kind, size = read_boxhdr(specification,f) + pos = pos + size + if kind == "resc" then + if specification.xres == 0 and specification.yres == 0 then + scan_resc_resd(specification,f) + if getposition(f) ~= pos then + specification.error = "invalid resc" + return + end + end + elseif tpos == "resd" then + scan_resc_resd(specification,f) + if getposition(f) ~= pos then + specification.error = "invalid resd" + return + end + elseif pos > last then + specification.error = "invalid res" + return + elseif pos == last then + break + end + if specification.error then + break + end + setposition(f,pos) + end + end + + local function scan_jp2h(specification,f,last) + local okay = false + local pos = getposition(f) + while true do + local kind, size = read_boxhdr(specification,f) + pos = pos + size + if kind == "ihdr" then + scan_ihdr(specification,f) + if getposition(f) ~= pos then + specification.error = "invalid ihdr" + return false + end + okay = true + elseif kind == "res" then + scan_res(specification,f,pos) + elseif pos > last then + specification.error = "invalid jp2h" + return false + elseif pos == last then + break + end + if specification.error then + break + end + setposition(f,pos) + end + return okay + end + + function identifiers.jp2(filename) + local specification = { + filename = filename, + filetype = "jp2", + } + if not filename or filename == "" then + specification.error = "invalid filename" + return specification -- error + end + local f = io.open(filename,"rb") + if not f then + specification.error = "unable to open file" + return specification -- error + end + specification.xres = 0 + specification.yres = 0 + specification.orientation = 1 + specification.totalpages = 1 + specification.pagenum = 1 + specification.length = 0 + local xres = 0 + local yres = 0 + local orientation = 1 + local okay = false + local filesize = getsize(f) -- seek end + local majorversion = pdfmajorversion and pdfmajorversion() or 2 + local minorversion = pdfminorversion and pdfminorversion() or 2 + -- + local pos = 0 + -- signature + local kind, size = read_boxhdr(specification,f) + pos = pos + size + setposition(f,pos) + -- filetype + local kind, size = read_boxhdr(specification,f) + if kind ~= "ftyp" then + specification.error = "missing ftyp box" + return specification + end + pos = pos + size + setposition(f,pos) + while not okay do + local kind, size = read_boxhdr(specification,f) + pos = pos + size + if kind == "jp2h" then + okay = scan_jp2h(specification,f,pos) + elseif kind == "jp2c" and not okay then + specification.error = "no ihdr box found" + return specification + end + setposition(f,pos) + end + -- + f:close() + if not okay then + specification.error = "invalid file" + elseif not specification.error then + if xres == 0 and yres ~= 0 then + xres = yres + end + if yres == 0 and xres ~= 0 then + yres = xres + end + end + specification.xres = xres + specification.yres = yres + specification.orientation = orientation + specification.length = filesize + return specification + end + +end + +do + + -- 0 = gray "image b" + -- 2 = rgb "image c" + -- 3 = palette "image c" + "image i" + -- 4 = gray + alpha "image b" + -- 6 = rgb + alpha "image c" + + -- for i=1,length/3 do + -- palette[i] = readstring(f,3) + -- end + + local function grab(t,f,once) + if once then + for i=1,#t do + local l = t[i] + setposition(f,l.offset) + t[i] = readstring(f,l.length) + end + local data = concat(t) + -- t wiped in caller + return data + else + local data = { } + for i=1,#t do + local l = t[i] + setposition(f,l.offset) + data[i] = readstring(f,l.length) + end + return concat(data) + end + end + + function identifiers.png(filename) + local specification = { + filename = filename, + filetype = "png", + } + if not filename or filename == "" then + specification.error = "invalid filename" + return specification -- error + end + local f = io.open(filename,"rb") + if not f then + specification.error = "unable to open file" + return specification -- error + end + specification.xres = 0 + specification.yres = 0 + specification.orientation = 1 + specification.totalpages = 1 + specification.pagenum = 1 + specification.offset = 0 + specification.length = 0 + local filesize = getsize(f) -- seek end + local tables = { } + local banner = readstring(f,8) + if banner ~= "\137PNG\013\010\026\010" then + specification.error = "no png file" + return specification -- error + end + while true do + local position = getposition(f) + if position >= filesize then + break + end + local length = readcardinal4(f) + if not length then + break + end + local kind = readstring(f,4) + if kind then + kind = lower(kind) + else + break + end + if kind == "ihdr" then -- metadata + specification.xsize = readcardinal4(f) + specification.ysize = readcardinal4(f) + specification.colordepth = readcardinal(f) + specification.colorspace = readcardinal(f) + specification.compression = readcardinal(f) + specification.filter = readcardinal(f) + specification.interlace = readcardinal(f) + tables[kind] = true + elseif kind == "iend" then + tables[kind] = true + break + elseif kind == "phys" then + local x = readcardinal4(f) + local y = readcardinal4(f) + local u = readcardinal(f) + if u == 1 then -- meters + -- x = round(0.0254 * x) + -- y = round(0.0254 * y) + end + specification.xres = x + specification.yres = y + tables[kind] = true + elseif kind == "idat" or kind == "plte" or kind == "gama" or kind == "trns" then + local t = tables[kind] + if not t then + t = setmetatablecall(grab) + tables[kind] = t + end + t[#t+1] = { + offset = getposition(f), + length = length, + } + else + tables[kind] = true + end + setposition(f,position+length+12) -- #size #kind #crc + end + specification.tables = tables + return specification + end + +end + +do + + local function gray(t,k) + local v = 0 + t[k] = v + return v + end + + local function rgb(t,k) + local v = { 0, 0, 0 } + t[k] = v + return v + end + + local function cmyk(t,k) + local v = { 0, 0, 0, 0 } + t[k] = v + return v + end + + function identifiers.bitmap(specification) + local xsize = specification.xsize or 0 + local ysize = specification.ysize or 0 + local width = specification.width or xsize * 65536 + local height = specification.height or ysize * 65536 + local colordepth = specification.colordepth or 1 -- 1 .. 2 + local colorspace = specification.colorspace or 1 -- 1 .. 3 + local pixel = false + local data = specification.data + local mask = specification.mask + if colorspace == 1 or colorspace == "gray" then + pixel = gray + colorspace = 1 + elseif colorspace == 2 or colorspace == "rgb" then + pixel = rgb + colorspace = 2 + elseif colorspace == 3 or colorspace == "cmyk" then + pixel = cmyk + colorspace = 3 + else + return + end + if colordepth == 8 then + colordepth = 1 + elseif colordepth == 16 then + colordepth = 2 + end + if colordepth > 1 then + -- not yet + return + end + if data then + -- assume correct data + else + data = { } + for i=1,ysize do + data[i] = setmetatableindex(pixel) + end + end + if mask == true then + mask = { } + for i=1,ysize do + mask[i] = setmetatableindex(gray) + end + end + local specification = { + xsize = xsize, + ysize = ysize, + width = width, + height = height, + colordepth = colordepth, + colorspace = colorspace, + data = data, + mask = mask, + } + return specification + end + +end + +function graphics.identify(filename,filetype) + local identify = filetype and identifiers[filetype] + if identify then + return identify(filename) + end + local identify = identifiers[suffixonly(filename)] + if identify then + identify = identify(filename) + else + identify = { + filename = filename, + filetype = filetype, + error = "identification failed", + } + end + -- inspect(identify) + return identify +end + +-- inspect(identifiers.jpg("t:/sources/hacker.jpg")) +-- inspect(identifiers.png("t:/sources/mill.png")) -- cgit v1.2.3