Advertisement
Guest User

oc-pngview

a guest
Jul 17th, 2018
97
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 8.95 KB | None | 0 0
  1. --[[
  2. pngview: Simple high-resolution PNG viewer
  3. Copyright (c) 2016, 2017, 2018 asie
  4.  
  5. Permission is hereby granted, free of charge, to any person obtaining a copy of
  6. this software and associated documentation files (the "Software"), to deal in
  7. the Software without restriction, including without limitation the rights to
  8. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  9. of the Software, and to permit persons to whom the Software is furnished to do
  10. so, subject to the following conditions:
  11.  
  12. The above copyright notice and this permission notice shall be included in all
  13. copies or substantial portions of the Software.
  14.  
  15. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. SOFTWARE.
  22. ]]
  23.  
  24. local args = {...}
  25. local ocpng = require("png")
  26. local component = require("component")
  27. local event = require("event")
  28. local gpu = component.gpu
  29. local unicode = require("unicode")
  30. local keyboard = require("keyboard")
  31. local text = require("text")
  32. local os = require("os")
  33. local pal = {}
  34.  
  35. -- 4x4 dithermask
  36. --local oDither = {
  37. --  0, 8, 2, 10,
  38. --  12, 4, 14, 6,
  39. --  3, 11, 1, 9,
  40. --  15, 7, 13, 5
  41. --}
  42. -- 2x2 dithermask
  43. local oDither = {
  44.     0, 2,
  45.     3, 1
  46. }
  47. local oDSize = #oDither
  48. local oDWidth = math.floor(math.sqrt(#oDither))
  49.  
  50. local q = {}
  51. for i=0,255 do
  52.   local dat = (i & 0x01) << 7
  53.   dat = dat | (i & 0x02) >> 1 << 6
  54.   dat = dat | (i & 0x04) >> 2 << 5
  55.   dat = dat | (i & 0x08) >> 3 << 2
  56.   dat = dat | (i & 0x10) >> 4 << 4
  57.   dat = dat | (i & 0x20) >> 5 << 1
  58.   dat = dat | (i & 0x40) >> 6 << 3
  59.   dat = dat | (i & 0x80) >> 7
  60.   q[i + 1] = unicode.char(0x2800 | dat)
  61. end
  62.  
  63. local function round(v)
  64.   return math.floor(v + 0.5)
  65. end
  66.  
  67. function resetPalette(data)
  68.  for i=0,255 do
  69.   if (i < 16) then
  70. --    if data == nil or data[3] == nil or data[3][i] == nil then
  71.       pal[i] = (i * 15) << 16 | (i * 15) << 8 | (i * 15)
  72. --    else
  73. --      pal[i] = data[3][i]
  74. --      gpu.setPaletteColor(i, data[3][i])
  75. --    end
  76.   else
  77.     local j = i - 16
  78.     local b = math.floor((j % 5) * 255 / 4.0)
  79.     local g = math.floor((math.floor(j / 5.0) % 8) * 255 / 7.0)
  80.     local r = math.floor((math.floor(j / 40.0) % 6) * 255 / 5.0)
  81.     pal[i] = r << 16 | g << 8 | b
  82.   end
  83.  end
  84. end
  85.  
  86. resetPalette(nil)
  87.  
  88. local function pngSafeGet(png, x, y, paletted)
  89.   if x >= png.w then x = png.w - 1 elseif x < 0 then x = 0 end
  90.   if y >= png.h then y = png.h - 1 elseif y < 0 then y = 0 end
  91.   return png:get(x, y, paletted)
  92. end
  93.  
  94. local function maxAbs(r, g, b)
  95.   local d1 = r - g
  96.   local d2 = g - b
  97.   if d1 < 0 then d1 = -d1 end
  98.   if d2 < 0 then d2 = -d2 end
  99.   if d1 >= d2 then return d1 else return d2 end
  100. end
  101.  
  102. local function getOCPalEntry(png, data, rgb)
  103.   -- TODO: more cleverness
  104.   local r = (rgb >> 16) & 0xFF
  105.   local g = (rgb >> 8) & 0xFF
  106.   local b = (rgb) & 0xFF
  107.   if maxAbs(r, g, b) <= 6 then
  108.     local i = round(g * 16.0 / 255.0)
  109.     if i <= 0 then return 16 elseif i >= 16 then return 255 else return i - 1 end
  110.   else
  111.     return 16 + (round(r * 5.0 / 255.0) * 40) + (round(g * 7.0 / 255.0) * 5) + round(b * 4.0 / 255.0)
  112.   end
  113. end
  114.  
  115. local function colorDistSq(rgb1, rgb2)
  116.   if rgb1 == rgb2 then return 0 end
  117.  
  118.   local r1 = (rgb1 >> 16) & 0xFF
  119.   local g1 = (rgb1 >> 8) & 0xFF
  120.   local b1 = (rgb1) & 0xFF
  121.   local r2 = (rgb2 >> 16) & 0xFF
  122.   local g2 = (rgb2 >> 8) & 0xFF
  123.   local b2 = (rgb2) & 0xFF
  124.   local rs = (r1 - r2) * (r1 - r2)
  125.   local gs = (g1 - g2) * (g1 - g2)
  126.   local bs = (b1 - b2) * (b1 - b2)
  127.   local rAvg = math.floor((r1 + r2) / 2)
  128.  
  129.   return (((512 + rAvg) * rs) >> 8) + (4 * gs) + (((767 - rAvg) * bs) >> 8)
  130. end
  131.  
  132. -- inspired by the teachings of Bisqwit
  133. -- http://bisqwit.iki.fi/story/howto/dither/jy/
  134. local function ditherDistance(rgb, rgb1, rgb2)
  135.   if rgb1 == rgb2 then return 0.5 end
  136.  
  137.   local r = (rgb >> 16) & 0xFF
  138.   local g = (rgb >> 8) & 0xFF
  139.   local b = (rgb) & 0xFF
  140.   local r1 = (rgb1 >> 16) & 0xFF
  141.   local g1 = (rgb1 >> 8) & 0xFF
  142.   local b1 = (rgb1) & 0xFF
  143.   local r2 = (rgb2 >> 16) & 0xFF
  144.   local g2 = (rgb2 >> 8) & 0xFF
  145.   local b2 = (rgb2) & 0xFF
  146.  
  147.   return (r * r1 - r * r2 - r1 * r2 + r2 * r2 +
  148.     g * g1 - g * g2 - g1 * g2 + g2 * g2 +
  149.     b * b1 - b * b2 - b1 * b2 + b2 * b2) /
  150.     ((r1 - r2) * (r1 - r2) +
  151.       (g1 - g2) * (g1 - g2) +
  152.       (b1 - b2) * (b1 - b2));
  153. end
  154.  
  155. function loadImage(filename)
  156.   local png = ocpng.loadPNG(filename)
  157.  
  158.   local charH = math.ceil(png.h / 4.0)
  159.   local charW = math.ceil(png.w / 2.0)
  160.   local data = {}
  161.  
  162.   data[1] = {}
  163.   data[2] = {charW, charH}
  164.   data[3] = {}
  165.  
  166.   -- todo: populate with customColors [3][0]...
  167.  
  168.   local j = 1
  169.   for y=0,charH-1 do
  170.     for x=0,charW-1 do
  171.       local pixelsRGB = {
  172.         pngSafeGet(png, x*2+1, y*4+3),
  173.         pngSafeGet(png, x*2, y*4+3),
  174.         pngSafeGet(png, x*2+1, y*4+2),
  175.         pngSafeGet(png, x*2, y*4+2),
  176.         pngSafeGet(png, x*2+1, y*4+1),
  177.         pngSafeGet(png, x*2, y*4+1),
  178.         pngSafeGet(png, x*2+1, y*4),
  179.         pngSafeGet(png, x*2, y*4)
  180.       }
  181.       local pixels = {}
  182.       for i=1,#pixelsRGB do pixels[i] = getOCPalEntry(png, data, pixelsRGB[i]) end
  183.       -- identify most common colors
  184.       local pixelCount = {}
  185.       local pixelColors = 0
  186.       for i=1,#pixels do
  187.         if pixelCount[pixels[i]] == nil then pixelColors = pixelColors + 1 end
  188.         pixelCount[pixels[i]] = (pixelCount[pixels[i]] or 0) + 1
  189.       end
  190.       if pixelColors == 1 then
  191.         data[1][j] = (0 << 16) | (0 << 8) | pixels[1]
  192.       else
  193.         local bg = -1
  194.         local bgc = -1
  195.         local fg = -1
  196.         local fgc = -1
  197.         for k,v in pairs(pixelCount) do
  198.           if v > bgc then
  199.             bg = k
  200.             bgc = v
  201.           end
  202.         end
  203.         for k,v in pairs(pixelCount) do
  204.           local contrast = colorDistSq(pal[bg], pal[k]) * v
  205.           if k ~= bg and contrast > fgc then
  206.             fg = k
  207.             fgc = contrast
  208.           end
  209.         end
  210.         local chr = 0
  211.         for i=1,#pixels do
  212.           local px = x * 2 + ((i - 1) & 1)
  213.           local py = y * 4 + ((i - 1) >> 1)
  214.           local dDist = ditherDistance(pixelsRGB[i], pal[bg], pal[fg])
  215.           local dDi = oDSize - round(dDist * oDSize)
  216.           local dThr = oDither[1 + ((py % oDWidth) * oDWidth) + (px % oDWidth)]
  217.           if dThr < dDi then
  218.             chr = chr | (1 << (i - 1))
  219.           end
  220.         end
  221.         data[1][j] = bg | (fg << 8) | (chr << 16)
  222.       end
  223.       j = j + 1
  224.     end
  225.   end
  226.  
  227.   return data
  228. end
  229.  
  230. function gpuBG()
  231.   local a, al = gpu.getBackground()
  232.   if al then
  233.     return gpu.getPaletteColor(a)
  234.   else
  235.     return a
  236.   end
  237. end
  238. function gpuFG()
  239.   local a, al = gpu.getForeground()
  240.   if al then
  241.     return gpu.getPaletteColor(a)
  242.   else
  243.     return a
  244.   end
  245. end
  246.  
  247. function drawImage(data, offx, offy)
  248.   if offx == nil then offx = 0 end
  249.   if offy == nil then offy = 0 end
  250.  
  251.   local WIDTH = data[2][1]
  252.   local HEIGHT = data[2][2]
  253.  
  254.   gpu.setResolution(WIDTH, HEIGHT)
  255.   resetPalette(data)
  256.  
  257.   local bg = 0
  258.   local fg = 0
  259.   local cw = 1
  260.   local noBG = false
  261.   local noFG = false
  262.   local ind = 1
  263.  
  264.   local gBG = gpuBG()
  265.   local gFG = gpuFG()
  266.  
  267.   for y=0,HEIGHT-1 do
  268.     local str = ""
  269.     for x=0,WIDTH-1 do
  270.       ind = (y * WIDTH) + x + 1
  271.       bg = pal[data[1][ind] & 0xFF]
  272.       fg = pal[(data[1][ind] >> 8) & 0xFF]
  273.       cw = ((data[1][ind] >> 16) & 0xFF) + 1
  274.       noBG = (cw == 256)
  275.       noFG = (cw == 1)
  276.       if (noFG or (gBG == fg)) and (noBG or (gFG == bg)) then
  277.         str = str .. q[257 - cw]
  278. --        str = str .. "I"
  279.       elseif (noBG or (gBG == bg)) and (noFG or (gFG == fg)) then
  280.         str = str .. q[cw]
  281.       else
  282.         if #str > 0 then
  283.           gpu.set(x + 1 + offx - unicode.wlen(str), y + 1 + offy, str)
  284.         end
  285.         if (gBG == fg and gFG ~= bg) or (gFG == bg and gBG ~= fg) then
  286.           cw = 257 - cw
  287.           local t = bg
  288.           bg = fg
  289.           fg = t
  290.         end
  291.         if gBG ~= bg then
  292.           gpu.setBackground(bg)
  293.           gBG = bg
  294.         end
  295.         if gFG ~= fg then
  296.           gpu.setForeground(fg)
  297.           gFG = fg
  298.         end
  299.         str = q[cw]
  300. --        if (not noBG) and (not noFG) then str = "C" elseif (not noBG) then str = "B" elseif (not noFG) then str = "F" else str = "c" end
  301.       end
  302.     end
  303.     if #str > 0 then
  304.       gpu.set(WIDTH + 1 - unicode.wlen(str) + offx, y + 1 + offy, str)
  305.     end
  306.   end
  307. end
  308.  
  309. local image = loadImage(args[1])
  310. drawImage(image)
  311.  
  312. while true do
  313.     local name,addr,char,key,player = event.pull("key_down")
  314.     if key == 0x10 then
  315.         break
  316.     end
  317. end
  318.  
  319. gpu.setBackground(0, false)
  320. gpu.setForeground(16777215, false)
  321. gpu.setResolution(80, 25)
  322. gpu.fill(1, 1, 80, 25, " ")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement