1 if not modules then modules = { } end modules ['grph-img'] = {
2 version = 1.001,
3 comment = "companion to grph-inc.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14local lower, strip = string.lower, string.strip
15local round = math.round
16local concat = table.concat
17local suffixonly = file.suffix
18
19local newreader = io.newreader
20local setmetatableindex = table.setmetatableindex
21local setmetatablecall = table.setmetatablecall
22
23local graphics = graphics or { }
24local identifiers = { }
25graphics.identifiers = identifiers
26
27local function checkedmethod(filename,method)
28 if method ~= "string" then
29 local found, data = resolvers.loadbinfile(filename)
30 return data, "string"
31 else
32 return filename, method
33 end
34end
35
36do
37
38 local colorspaces = {
39 [1] = 1,
40 [3] = 2,
41 [4] = 3,
42 }
43
44 local tags = {
45 [0xC0] = { name = "SOF0", },
46 [0xC1] = { name = "SOF1", },
47 [0xC2] = { name = "SOF2", },
48 [0xC3] = { name = "SOF3", supported = false },
49
50 [0xC5] = { name = "SOF5", supported = false },
51 [0xC6] = { name = "SOF6", supported = false },
52 [0xC7] = { name = "SOF7", supported = false },
53
54 [0xC8] = { name = "JPG", },
55 [0xC9] = { name = "SOF9", },
56 [0xCA] = { name = "SOF10", supported = false },
57 [0xCB] = { name = "SOF11", supported = false },
58
59 [0xCD] = { name = "SOF13", supported = false },
60 [0xCE] = { name = "SOF14", supported = false },
61 [0xCF] = { name = "SOF15", supported = false },
62
63 [0xC4] = { name = "DHT" },
64
65 [0xCC] = { name = "DAC" },
66
67 [0xD0] = { name = "RST0", zerolength = true },
68 [0xD1] = { name = "RST1", zerolength = true },
69 [0xD2] = { name = "RST2", zerolength = true },
70 [0xD3] = { name = "RST3", zerolength = true },
71 [0xD4] = { name = "RST4", zerolength = true },
72 [0xD5] = { name = "RST5", zerolength = true },
73 [0xD6] = { name = "RST6", zerolength = true },
74 [0xD7] = { name = "RST7", zerolength = true },
75
76 [0xD8] = { name = "SOI", zerolength = true },
77 [0xD9] = { name = "EOI", zerolength = true },
78 [0xDA] = { name = "SOS" },
79 [0xDB] = { name = "DQT" },
80 [0xDC] = { name = "DNL" },
81 [0xDD] = { name = "DRI" },
82 [0xDE] = { name = "DHP" },
83 [0xDF] = { name = "EXP" },
84
85 [0xE0] = { name = "APP0" },
86 [0xE1] = { name = "APP1" },
87 [0xE2] = { name = "APP2" },
88 [0xE3] = { name = "APP3" },
89 [0xE4] = { name = "APP4" },
90 [0xE5] = { name = "APP5" },
91 [0xE6] = { name = "APP6" },
92 [0xE7] = { name = "APP7" },
93 [0xE8] = { name = "APP8" },
94 [0xE9] = { name = "APP9" },
95 [0xEA] = { name = "APP10" },
96 [0xEB] = { name = "APP11" },
97 [0xEC] = { name = "APP12" },
98 [0xED] = { name = "APP13" },
99 [0xEE] = { name = "APP14" },
100 [0xEF] = { name = "APP15" },
101
102 [0xF0] = { name = "JPG0" },
103 [0xFD] = { name = "JPG13" },
104 [0xFE] = { name = "COM" },
105
106 [0x01] = { name = "TEM", zerolength = true },
107 }
108
109 setmetatableindex(tags, function(t,k)
110
111 local v = "tag " .. k
112 t[k] = v
113 return v
114 end)
115
116
117
118
119 local function read_APP1_Exif(f, xres, yres, orientation)
120 local position = false
121 local littleendian = false
122
123 while true do
124 position = f:getposition()
125 local b = f:readbyte()
126 if b == 0 then
127
128 elseif b == 0x4D and f:readbyte() == 0x4D then
129
130 break
131 elseif b == 0x49 and f:readbyte() == 0x49 then
132
133 littleendian = true
134 break
135 else
136
137 return xres, yres, orientation
138 end
139 end
140
141 local version = littleendian and f:readcardinal2le() or f:readcardinal2()
142 if version ~= 42 then
143 return xres, yres, orientation
144 end
145
146 local offset = littleendian and f:readcardinal4le() or f:readcardinal4()
147 if not offset then
148 return xres, yres, orientation
149 end
150 f:setposition(position + offset)
151 local entries = littleendian and f:readcardinal2le() or f:readcardinal2()
152 if not entries or entries == 0 then
153 return xres, yres, orientation
154 end
155 local x_res, y_res, x_res_ms, y_res_ms, x_temp, y_temp
156 local res_unit, res_unit_ms
157 for i=1,entries do
158 local tag = littleendian and f:readcardinal2le() or f:readcardinal2()
159 local kind = littleendian and f:readcardinal2le() or f:readcardinal2()
160 local size = littleendian and f:readcardinal4le() or f:readcardinal4()
161 local value = 0
162 local num = 0
163 local den = 0
164 if kind == 1 or kind == 7 then
165 value = f:readbyte()
166 f:skip(3)
167 elseif kind == 3 or kind == 8 then
168 value = littleendian and f:readcardinal2le() or f:readcardinal2()
169 f:skip(2)
170 elseif kind == 4 or kind == 9 then
171 value = littleendian and f:readcardinal4le() or f:readcardinal4()
172 elseif kind == 5 or kind == 10 then
173 local offset = littleendian and f:readcardinal4le() or f:readcardinal4()
174 local saved = f:getposition()
175 f:setposition(position+offset)
176 num = littleendian and f:readcardinal4le() or f:readcardinal4()
177 den = littleendian and f:readcardinal4le() or f:readcardinal4()
178 f:setposition(saved)
179 else
180 f:skip(4)
181 end
182 if tag == 274 then
183 orientation = value
184 elseif tag == 282 then
185 if den ~= 0 then
186 x_res = num/den
187 end
188 elseif tag == 283 then
189 if den ~= 0 then
190 y_res = num/den
191 end
192 elseif tag == 296 then
193 if value == 2 then
194 res_unit = 1
195 elseif value == 3 then
196 res_unit = 2.54
197 end
198 elseif tag == 0x5110 then
199 res_unit_ms = value == 1
200 elseif tag == 0x5111 then
201 x_res_ms = value
202 elseif tag == 0x5112 then
203 y_res_ms = value
204 end
205 end
206 if x_res and y_res and res_unit and res_unit > 0 then
207 x_temp = round(x_res * res_unit)
208 y_temp = round(y_res * res_unit)
209 elseif x_res_ms and y_res_ms and res_unit_ms then
210 x_temp = round(x_res_ms * 0.0254)
211 y_temp = round(y_res_ms * 0.0254)
212 end
213 if x_temp and a_temp and x_temp > 0 and y_temp > 0 then
214 if (x_temp ~= x_res or y_temp ~= y_res) and x_res ~= 0 and y_res ~= 0 then
215
216 elseif x_temp == 1 or y_temp == 1 then
217
218 else
219 return x_temp, y_temp, orientation
220 end
221 end
222 return round(xres), round(yres), orientation
223 end
224
225 function identifiers.jpg(filename,method)
226 local specification = {
227 filename = filename,
228 filetype = "jpg",
229 }
230 if not filename or filename == "" then
231 specification.error = "invalid filename"
232 return specification
233 end
234 filename, method = checkedmethod(filename,method)
235 local f = newreader(filename,method)
236 if not f then
237 specification.error = "unable to open file"
238 return specification
239 end
240 specification.xres = 0
241 specification.yres = 0
242 specification.orientation = 1
243 specification.totalpages = 1
244 specification.pagenum = 1
245 specification.length = 0
246 local banner = f:readcardinal2()
247 if banner ~= 0xFFD8 then
248 specification.error = "no jpeg file"
249 return specification
250 end
251 local xres = 0
252 local yres = 0
253 local orientation = 1
254 local okay = false
255 local filesize = f:getsize()
256
257
258 while f:getposition() < filesize do
259 local b = f:readbyte()
260 if not b then
261 break
262 elseif b ~= 0xFF then
263 if not okay then
264
265 specification.error = "incomplete file"
266 end
267 break
268 end
269 local category = f:readbyte()
270 local position = f:getposition()
271 local length = 0
272 local tagdata = tags[category]
273 if not tagdata then
274 specification.error = "invalid tag " .. (category or "?")
275 break
276 elseif tagdata.supported == false then
277 specification.error = "unsupported " .. tagdata.comment
278 break
279 end
280 local name = tagdata.name
281 if name == "SOF0" or name == "SOF1" or name == "SOF2" then
282
283
284
285
286
287
288
289
290 length = f:readcardinal2()
291 specification.colordepth = f:readcardinal()
292 specification.ysize = f:readcardinal2()
293 specification.xsize = f:readcardinal2()
294 specification.colorspace = colorspaces[f:readcardinal()]
295 if not specification.colorspace then
296 specification.error = "unsupported color space"
297 break
298 end
299 okay = true
300 elseif name == "APP0" then
301 length = f:readcardinal2()
302 if length > 6 then
303 local format = f:readstring(5)
304 if format == "JFIF\000" then
305 f:skip(2)
306 units = f:readcardinal()
307 xres = f:readcardinal2()
308 yres = f:readcardinal2()
309 if units == 1 then
310
311 if xres == 1 or yres == 1 then
312
313 end
314 elseif units == 2 then
315
316 xres = xres * 2.54
317 yres = yres * 2.54
318 else
319 xres = 0
320 yres = 0
321 end
322 end
323 end
324 elseif name == "APP1" then
325 length = f:readcardinal2()
326 if length > 7 then
327 local format = f:readstring(5)
328 if format == "Exif\000" then
329 xres, yres, orientation = read_APP1_Exif(f,xres,yres,orientation)
330 end
331 end
332 elseif not tagdata.zerolength then
333 length = f:readcardinal2()
334 end
335 if length > 0 then
336 f:setposition(position+length)
337 end
338 end
339 f:close()
340 if not okay then
341 specification.error = specification.error or "invalid file"
342 elseif not specification.error then
343 if xres == 0 and yres ~= 0 then
344 xres = yres
345 end
346 if yres == 0 and xres ~= 0 then
347 yres = xres
348 end
349 end
350 specification.xres = xres
351 specification.yres = yres
352 specification.orientation = orientation
353 specification.length = filesize
354 return specification
355 end
356
357end
358
359do
360
361 local function read_boxhdr(specification,f)
362 local size = f:readcardinal4()
363 local kind = f:readstring(4)
364 if kind then
365 kind = strip(lower(kind))
366 else
367 kind = ""
368 end
369 if size == 1 then
370 size = f:readcardinal4() * 0xFFFF0000 + f:readcardinal4()
371 end
372 if size == 0 and kind ~= "jp2c" then
373 specification.error = "invalid size"
374 end
375 return kind, size
376 end
377
378 local function scan_ihdr(specification,f)
379 specification.ysize = f:readcardinal4()
380 specification.xsize = f:readcardinal4()
381 f:skip(2)
382 specification.colordepth = f:readcardinal() + 1
383 f:skip(3)
384 end
385
386 local function scan_resc_resd(specification,f)
387 local vr_n = f:readcardinal2()
388 local vr_d = f:readcardinal2()
389 local hr_n = f:readcardinal2()
390 local hr_d = f:readcardinal2()
391 local vr_e = f:readcardinal()
392 local hr_e = f:readcardinal()
393 specification.xres = math.round((hr_n / hr_d) * math.exp(hr_e * math.log(10.0)) * 0.0254)
394 specification.yres = math.round((vr_n / vr_d) * math.exp(vr_e * math.log(10.0)) * 0.0254)
395 end
396
397 local function scan_res(specification,f,last)
398 local pos = f:getposition()
399 while true do
400 local kind, size = read_boxhdr(specification,f)
401 pos = pos + size
402 if kind == "resc" then
403 if specification.xres == 0 and specification.yres == 0 then
404 scan_resc_resd(specification,f)
405 if f:getposition() ~= pos then
406 specification.error = "invalid resc"
407 return
408 end
409 end
410 elseif tpos == "resd" then
411 scan_resc_resd(specification,f)
412 if f:getposition() ~= pos then
413 specification.error = "invalid resd"
414 return
415 end
416 elseif pos > last then
417 specification.error = "invalid res"
418 return
419 elseif pos == last then
420 break
421 end
422 if specification.error then
423 break
424 end
425 f:setposition(pos)
426 end
427 end
428
429 local function scan_jp2h(specification,f,last)
430 local okay = false
431 local pos = f:getposition()
432 while true do
433 local kind, size = read_boxhdr(specification,f)
434 pos = pos + size
435 if kind == "ihdr" then
436 scan_ihdr(specification,f)
437 if f:getposition() ~= pos then
438 specification.error = "invalid ihdr"
439 return false
440 end
441 okay = true
442 elseif kind == "res" then
443 scan_res(specification,f,pos)
444 elseif pos > last then
445 specification.error = "invalid jp2h"
446 return false
447 elseif pos == last then
448 break
449 end
450 if specification.error then
451 break
452 end
453 f:setposition(pos)
454 end
455 return okay
456 end
457
458 function identifiers.jp2(filename,method)
459 local specification = {
460 filename = filename,
461 filetype = "jp2",
462 }
463 if not filename or filename == "" then
464 specification.error = "invalid filename"
465 return specification
466 end
467 filename, method = checkedmethod(filename,method)
468 local f = newreader(filename,method)
469 if not f then
470 specification.error = "unable to open file"
471 return specification
472 end
473 specification.xres = 0
474 specification.yres = 0
475 specification.orientation = 1
476 specification.totalpages = 1
477 specification.pagenum = 1
478 specification.length = 0
479 local xres = 0
480 local yres = 0
481 local orientation = 1
482 local okay = false
483 local filesize = f:getsize()
484
485 local pos = 0
486
487 local kind, size = read_boxhdr(specification,f)
488 pos = pos + size
489 f:setposition(pos)
490
491 local kind, size = read_boxhdr(specification,f)
492 if kind ~= "ftyp" then
493 specification.error = "missing ftyp box"
494 return specification
495 end
496 pos = pos + size
497 f:setposition(pos)
498 while not okay do
499 local kind, size = read_boxhdr(specification,f)
500 pos = pos + size
501 if kind == "jp2h" then
502 okay = scan_jp2h(specification,f,pos)
503 elseif kind == "jp2c" and not okay then
504 specification.error = "no ihdr box found"
505 return specification
506 end
507 f:setposition(pos)
508 end
509
510 f:close()
511 if not okay then
512 specification.error = "invalid file"
513 elseif not specification.error then
514 if xres == 0 and yres ~= 0 then
515 xres = yres
516 end
517 if yres == 0 and xres ~= 0 then
518 yres = xres
519 end
520 end
521 specification.xres = xres
522 specification.yres = yres
523 specification.orientation = orientation
524 specification.length = filesize
525 return specification
526 end
527
528end
529
530do
531
532
533
534
535
536
537
538
539
540
541
542 local function grab(t,f,once)
543 if once then
544 for i=1,#t do
545 local l = t[i]
546 f:setposition(l.offset)
547 t[i] = f:readstring(l.length)
548 end
549 local data = concat(t)
550
551 return data
552 else
553 local data = { }
554 for i=1,#t do
555 local l = t[i]
556 f:setposition(l.offset)
557 data[i] = f:readstring(l.length)
558 end
559 return concat(data)
560 end
561 end
562
563 function identifiers.png(filename,method)
564 local specification = {
565 filename = filename,
566 filetype = "png",
567 }
568 if not filename or filename == "" then
569 specification.error = "invalid filename"
570 return specification
571 end
572 filename, method = checkedmethod(filename,method)
573 local f = newreader(filename,method)
574 if not f then
575 specification.error = "unable to open file"
576 return specification
577 end
578 specification.xres = 0
579 specification.yres = 0
580 specification.orientation = 1
581 specification.totalpages = 1
582 specification.pagenum = 1
583 specification.offset = 0
584 specification.length = 0
585 local filesize = f:getsize()
586 local tables = { }
587 local banner = f:readstring(8)
588 if banner ~= "\137PNG\013\010\026\010" then
589 specification.error = "no png file"
590 return specification
591 end
592 while true do
593 local position = f:getposition()
594 if position >= filesize then
595 break
596 end
597 local length = f:readcardinal4()
598 if not length then
599 break
600 end
601 local kind = f:readstring(4)
602 if kind then
603 kind = lower(kind)
604 else
605 break
606 end
607 if kind == "ihdr" then
608 specification.xsize = f:readcardinal4()
609 specification.ysize = f:readcardinal4()
610 specification.colordepth = f:readcardinal()
611 specification.colorspace = f:readcardinal()
612 specification.compression = f:readcardinal()
613 specification.filter = f:readcardinal()
614 specification.interlace = f:readcardinal()
615 tables[kind] = true
616 elseif kind == "iend" then
617 tables[kind] = true
618 break
619 elseif kind == "phys" then
620 local x = f:readcardinal4()
621 local y = f:readcardinal4()
622 local u = f:readcardinal()
623 if u == 1 then
624
625 x = round(0.0254 * x)
626 y = round(0.0254 * y)
627 if x == 0 then x = 1 end
628 if y == 0 then y = 1 end
629 end
630 specification.xres = x
631 specification.yres = y
632 tables[kind] = true
633 elseif kind == "idat" or kind == "plte" or kind == "gama" or kind == "trns" then
634 local t = tables[kind]
635 if not t then
636 t = setmetatablecall(grab)
637 tables[kind] = t
638 end
639 t[#t+1] = {
640 offset = f:getposition(),
641 length = length,
642 }
643 else
644 tables[kind] = true
645 end
646 f:setposition(position+length+12)
647 end
648 f:close()
649 specification.tables = tables
650 return specification
651 end
652
653end
654
655do
656
657 local function gray(t,k)
658 local v = 0
659 t[k] = v
660 return v
661 end
662
663 local function rgb(t,k)
664 local v = { 0, 0, 0 }
665 t[k] = v
666 return v
667 end
668
669 local function cmyk(t,k)
670 local v = { 0, 0, 0, 0 }
671 t[k] = v
672 return v
673 end
674
675 function identifiers.bitmap(specification)
676 local xsize = specification.xsize or 0
677 local ysize = specification.ysize or 0
678 local width = specification.width or xsize * 65536
679 local height = specification.height or ysize * 65536
680 local colordepth = specification.colordepth or 1
681 local colorspace = specification.colorspace or 1
682 local pixel = false
683 local data = specification.data
684 local mask = specification.mask
685 local index = specification.index
686 if colorspace == 1 or colorspace == "gray" then
687 pixel = gray
688 colorspace = 1
689 elseif colorspace == 2 or colorspace == "rgb" then
690 pixel = rgb
691 colorspace = 2
692 elseif colorspace == 3 or colorspace == "cmyk" then
693 pixel = cmyk
694 colorspace = 3
695 else
696 return
697 end
698 if colordepth == 8 then
699 colordepth = 1
700 elseif colordepth == 16 then
701 colordepth = 2
702 end
703 if colordepth > 1 then
704
705 return
706 end
707 if data then
708
709 else
710 data = { }
711 for i=1,ysize do
712 data[i] = setmetatableindex(pixel)
713 end
714 end
715 if mask == true then
716 mask = { }
717 for i=1,ysize do
718 mask[i] = setmetatableindex(gray)
719 end
720 end
721 if index then
722 index = setmetatableindex(pixel)
723 end
724 local specification = {
725 xsize = xsize,
726 ysize = ysize,
727 width = width,
728 height = height,
729 colordepth = colordepth,
730 colorspace = colorspace,
731 data = data,
732 mask = mask,
733 index = index,
734 }
735 return specification
736 end
737
738end
739
740function graphics.identify(filename,filetype)
741 local identify = filetype and identifiers[filetype]
742 if not identify then
743 identify = identifiers[suffixonly(filename)]
744 end
745 if identify then
746 return identify(filename)
747 else
748 return {
749 filename = filename,
750 filetype = filetype,
751 error = "identification failed",
752 }
753 end
754end
755
756
757
758 |