Advertisement
Schneemann

png

Sep 15th, 2021
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.25 KB | None | 0 0
  1. local crc = dofile("./crc32.lua")
  2. local inflate = dofile("./inflate.lua")
  3.  
  4. local ocpng = {}
  5.  
  6. function ocpng.loadPNG(fname)
  7. assert(fname, "provide a filename as an argument")
  8. -- PNG magic header
  9. fp = io.open(fname, "rb")
  10. if fp == nil then
  11. error("file not found: " .. fname)
  12. end
  13. if fp:read(8) ~= "\x89PNG\x0D\x0A\x1A\x0A" then
  14. error("invalid PNG magic")
  15. end
  16.  
  17. -- chunks
  18. local png_w, png_h
  19. local png_bpc, png_cm
  20. local png_compr, png_filt, png_inter
  21. local png_ccount
  22. local png_fstride
  23. local png_bwidth
  24. local png_trns = {}
  25.  
  26. local idat_accum = ""
  27. while true do
  28. local clen_s = fp:read(4)
  29. local clen = string.unpack(">I4", clen_s)
  30. local ctyp = fp:read(4)
  31. local cdat = fp:read(clen)
  32. local ccrc = string.unpack(">I4", fp:read(4))
  33.  
  34. --print(ctyp, string.format("%08X", ccrc), clen)
  35. local acrc = crc.crc32(ctyp..cdat)
  36. if acrc ~= ccrc then
  37. print(string.format("%08X %08X", acrc, ccrc))
  38. error("CRC mismatch")
  39. end
  40.  
  41. if ctyp == "IHDR" then
  42. png_w, png_h, png_bpc, png_cm, png_compr, png_filt, png_inter = string.unpack(">I4 >I4 B B B B B", cdat)
  43. if png_compr ~= 0 then error("unsupported compression mode") end
  44. if png_filt ~= 0 then error("unsupported filter mode") end
  45. if png_inter ~= 0 then error("we don't support interlacing (yet)") end
  46.  
  47. -- decipher colour mode
  48. if (png_cm & ~7) ~= 0 or png_cm == 1 or (png_cm&~2) == 5 then
  49. error("unsupported colour mode")
  50. end
  51. if (png_cm&3) == 2 then png_ccount = 3 else png_ccount = 1 end
  52. if (png_cm&4) == 4 then png_ccount = png_ccount + 1 end
  53.  
  54. if png_ccount ~= 1 and png_bpc < 8 then
  55. error("bpc must be >= 8 for colour modes with more than one component")
  56. end
  57.  
  58. if png_cm == 3 and png_cm > 8 then
  59. error("bpc must be <= 8 for indexed colour modes")
  60. end
  61.  
  62. png_fstride = 1
  63. if png_bpc == 1 then
  64. png_bwidth = (png_w+7)>>3
  65. elseif png_bpc == 2 then
  66. png_bwidth = (png_w+3)>>2
  67. elseif png_bpc == 4 then
  68. png_bwidth = (png_w+1)>>1
  69. else
  70. if png_bpc == 8 then
  71. png_fstride = 1*png_ccount
  72. elseif png_bpc == 16 then
  73. png_fstride = 2*png_ccount
  74. end
  75.  
  76. png_bwidth = png_fstride*png_w
  77. end
  78.  
  79. elseif ctyp == "PLTE" then
  80. print(png_cm, png_bpc, png_bwidth, #cdat)
  81. PALETTE = {}
  82. local i
  83. for i=0,#cdat//3-1 do
  84. PALETTE[i+1] = (0
  85. + (cdat:byte(3*i+1,3*i+1)<<16)
  86. + (cdat:byte(3*i+2,3*i+2)<<8)
  87. + (cdat:byte(3*i+3,3*i+3)))
  88. end
  89.  
  90. elseif ctyp == "tRNS" then
  91. if png_cm == 3 then
  92. -- indexed + transparency
  93. for i=0,#cdat do
  94. local c = cdat:byte(i+1,i+1)
  95. table.insert(png_trns, c)
  96. end
  97. elseif png_cm == 2 and png_bpc == 8 then
  98. -- rgb + transparency
  99. for i=0,#cdat//3-1 do
  100. png_trns[(cdat:byte(3*i+1,3*i+1)<<16)
  101. + (cdat:byte(3*i+2,3*i+2)<<8)
  102. + (cdat:byte(3*i+3,3*i+3))] = true
  103. end
  104. else
  105. error(string.format("TODO: support this tRNS colour mode setup: cm=%d bpc=%d", png_cm, png_bpc))
  106. end
  107.  
  108. elseif ctyp == "IDAT" then
  109. idat_accum = idat_accum .. cdat
  110.  
  111. elseif ctyp == "IEND" then
  112. break
  113. else
  114. if (ctyp:byte(1) & 0x20) == 0 then
  115. error("unhandled compulsory chunk")
  116. end
  117. end
  118. end
  119.  
  120. fp:close()
  121.  
  122. -- actually decompress image
  123. if sysnative then print("Inflating...") end
  124. local png_data = inflate.inflate(idat_accum)
  125.  
  126. local function paeth(a, b, c)
  127. local p = a+b-c
  128. local pa = math.tointeger(math.abs(p-a))
  129. local pb = math.tointeger(math.abs(p-b))
  130. local pc = math.tointeger(math.abs(p-c))
  131.  
  132. if pa <= pb and pa <= pc then return a
  133. elseif pb <= pc then return b
  134. else return c
  135. end
  136. end
  137.  
  138. -- defilter
  139. if sysnative then print("Defiltering...") end
  140. local x,y
  141. local unpackedcomb = ""
  142. local unpacked = ""
  143. --for x=1,png_bwidth do pline = pline .. string.char(0) end
  144. for y=0,png_h-1 do
  145. if false and #unpacked > png_bwidth*6 then
  146. unpackedcomb = unpackedcomb .. unpacked:sub(1, #unpacked - (png_bwidth*3) - 1)
  147. unpacked = unpacked:sub(#unpacked - (png_bwidth*3))
  148. end
  149. local line = png_data:sub(1+y*(png_bwidth+1), (y+1)*(png_bwidth+1))
  150. local ftyp = line:byte(1)
  151.  
  152. --print(ftyp)
  153. if ftyp == 0 then
  154. -- stored
  155. unpacked = unpacked .. line:sub(2)
  156.  
  157. elseif ftyp == 1 then
  158. -- dx
  159. unpacked = unpacked .. line:sub(2, 2+png_fstride-1)
  160. for x=2+png_fstride,#line do
  161. unpacked = unpacked .. string.char(0xFF&(
  162. line:byte(x) +
  163. unpacked:byte(#unpacked-png_fstride+1)))
  164. end
  165.  
  166. elseif ftyp == 2 then
  167. -- dy
  168. for x=2,#line do
  169. unpacked = unpacked .. string.char(0xFF&(
  170. line:byte(x) +
  171. unpacked:byte(#unpacked-png_bwidth+1)))
  172. end
  173.  
  174. elseif ftyp == 3 then
  175. -- average xy
  176. for x=2,2+png_fstride-1 do
  177. unpacked = unpacked .. string.char(0xFF&(
  178. line:byte(x) +
  179. (unpacked:byte(#unpacked-png_bwidth+1)>>1)))
  180. end
  181. for x=2+png_fstride,#line do
  182. unpacked = unpacked .. string.char(0xFF&(
  183. line:byte(x) +
  184. ((unpacked:byte(#unpacked-png_bwidth+1)
  185. + unpacked:byte(#unpacked-png_fstride+1)
  186. )>>1)))
  187. end
  188.  
  189. elseif ftyp == 4 then
  190. -- paeth
  191.  
  192. for x=2,2+png_fstride-1 do
  193. unpacked = unpacked .. string.char(0xFF&(
  194. line:byte(x) +
  195. paeth(0, unpacked:byte(#unpacked-png_bwidth+1), 0)))
  196. end
  197. for x=2+png_fstride,#line do
  198. unpacked = unpacked .. string.char(0xFF&(
  199. line:byte(x) +
  200. paeth(
  201. unpacked:byte(#unpacked-png_fstride+1),
  202. unpacked:byte(#unpacked-png_bwidth+1),
  203. unpacked:byte(#unpacked-png_bwidth+1-png_fstride)
  204. )))
  205. end
  206.  
  207. else
  208. print(ftyp)
  209. error("unhandled filter selection")
  210. end
  211. end
  212.  
  213. -- bail out if not OC
  214. if sysnative then return end
  215.  
  216. -- convert to screen
  217. local gpu = require("component").gpu
  218. local term = require("term")
  219. gpu.setBackground(0x000000)
  220. gpu.setForeground(0xFFFFFF)
  221. term.clear()
  222. local getter
  223. local getalpha = function(self, x, y)
  224. local v = png_trns[getter(self, x, y)]
  225. if png_trns[v] then return 0 else return 255 end
  226. end
  227.  
  228. if png_cm == 3 then
  229. getalpha = function(self, x, y)
  230. local pidx = getter(self, x, y, true)
  231. return png_trns[pidx] or 255
  232. end
  233. end
  234.  
  235. if png_cm == 3 and png_bpc == 8 then
  236. getter = function(self, x, y, paletted)
  237. local v = unpacked:byte(y*self.png_bwidth+x+1)
  238. if paletted then
  239. return v+1
  240. else
  241. return PALETTE[v+1] or error("out of range palette index")
  242. end
  243. end
  244. elseif png_cm == 3 and png_bpc == 4 then
  245. getter = function(self, x, y, paletted)
  246. local v = unpacked:byte(y*self.png_bwidth+(x>>1)+1)
  247. v = (v>>(4*(1~(x&1)))) & 0x0F
  248. if paletted then
  249. return v+1
  250. else
  251. return PALETTE[v+1] or error("out of range palette index")
  252. end
  253. end
  254. elseif png_cm == 3 and png_bpc == 2 then
  255. getter = function(self, x, y, paletted)
  256. local v = unpacked:byte(y*self.png_bwidth+(x>>2)+1)
  257. v = (v>>(2*(3~(x&3)))) & 0x03
  258. if paletted then
  259. return v+1
  260. else
  261. return PALETTE[v+1] or error("out of range palette index")
  262. end
  263. end
  264. elseif png_cm == 3 and png_bpc == 1 then
  265. getter = function(self, x, y, paletted)
  266. local v = unpacked:byte(y*self.png_bwidth+(x>>3)+1)
  267. v = (v>>(1*(7~(x&7)))) & 0x01
  268. if paletted then
  269. return v+1
  270. else
  271. return PALETTE[v+1] or error("out of range palette index")
  272. end
  273. end
  274. elseif png_cm == 2 and png_bpc == 8 then
  275. getter = function(self, x, y, paletted)
  276. if paletted then error("not a paletted image") end
  277. local r = unpacked:byte(y*self.png_bwidth+x*3+1)
  278. local g = unpacked:byte(y*self.png_bwidth+x*3+2)
  279. local b = unpacked:byte(y*self.png_bwidth+x*3+3)
  280. return (r<<16)|(g<<8)|b
  281. end
  282. elseif png_cm == 6 and png_bpc == 8 then
  283. getter = function(self, x, y, paletted)
  284. if paletted then error("not a paletted image") end
  285. local r = unpacked:byte(y*self.png_bwidth+x*4+1)
  286. local g = unpacked:byte(y*self.png_bwidth+x*4+2)
  287. local b = unpacked:byte(y*self.png_bwidth+x*4+3)
  288. return (r<<16)|(g<<8)|b
  289. end
  290. getalpha = function(self, x, y)
  291. return unpacked:byte(y*self.png_bwidth+x*4+4)
  292. end
  293. else
  294. error(string.format("TODO: support this colour mode setup: cm=%d bpc=%d", png_cm, png_bpc))
  295. end
  296. return {
  297. w = png_w, h = png_h,
  298. get = getter, getAlpha = getalpha,
  299. isPaletted = function(self) return self.png_cm == 3 end,
  300. getPaletteCount = function(self)
  301. if self.png_cm == 3 then
  302. return 1 << self.png_bpc
  303. else
  304. return 0
  305. end
  306. end,
  307. getPaletteEntry = function(self, i)
  308. return PALETTE[i] or error("out of range palette index")
  309. end,
  310.  
  311. png_cm = png_cm,
  312. png_bpc = png_bpc,
  313. png_bwidth = png_bwidth
  314. }
  315. end
  316.  
  317. return ocpng
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement