Advertisement
MeXaN1cK

PNGlib

Feb 3rd, 2017
163
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.38 KB | None | 0 0
  1. -- libPNGimage by TehSomeLuigi
  2. -- Revision: 1
  3. --
  4. -- A library to load, edit and save PNGs for OpenComputers
  5. --
  6.  
  7. --[[
  8.    
  9.     Feel free to use however you wish.
  10.     This header must however be preserved should this be redistributed, even
  11.     if in a modified form.
  12.    
  13.     This software comes with no warranties whatsoever.
  14.    
  15.     2014 TehSomeLuigi
  16.  
  17. ]]--
  18. local PNGImage = {}                                    ----1
  19. local PNGImagemetatable = {}
  20. PNGImagemetatable.__index = PNGImage
  21.  
  22. local function __unpack_msb_uint32(s)                  ----56
  23.     local a,b,c,d = s:byte(1,#s)
  24.     local num = (((a*256) + b) * 256 + c) * 256 + d
  25.     return num
  26. end
  27.  
  28. -- Read 32-bit unsigned integer (most-significant-byte, MSB, first) from file.
  29. local function __read_msb_uint32(fh)                                               ----110
  30.     return __unpack_msb_uint32(fh:read(4))
  31. end
  32.  
  33. -- Read unsigned byte (integer) from file
  34. local function __read_byte(fh)                       ----115
  35.     return fh:read(1):byte()
  36. end
  37.  
  38. local function getBitWidthPerPixel(ihdr)             ----121
  39.     if ihdr.color_type == 0 then -- Greyscale
  40.         return ihdr.bit_depth
  41.     end
  42.     if ihdr.color_type == 2 then -- Truecolour
  43.         return ihdr.bit_depth * 3
  44.     end
  45.     if ihdr.color_type == 3 then -- Indexed-colour
  46.         return ihdr.bit_depth
  47.     end
  48.     if ihdr.color_type == 4 then -- Greyscale + Alpha
  49.         return ihdr.bit_depth * 2
  50.     end
  51.     if ihdr.color_type == 6 then -- Truecolour + Alpha
  52.         return ihdr.bit_depth * 4
  53.     end
  54. end
  55.  
  56. local function getByteWidthPerScanline(ihdr)         ----139
  57.     return math.ceil((ihdr.width * getBitWidthPerPixel(ihdr)) / 8)
  58. end
  59.  
  60. local outssmt = {}                                     ----143
  61.  
  62. function outssmt:__call(write)
  63.     self.str = self.str .. string.char(write)
  64. end
  65.  
  66. function outssmt.OutStringStream()
  67.     local outss = {str=""}
  68.     setmetatable(outss, outssmt)
  69.     return outss
  70. end
  71.  
  72. local function __parse_IHDR(fh, len)                 ----158
  73.     if len ~= 13 then
  74.         error("PNG IHDR Corrupt - should be 13 bytes long")
  75.     end  
  76.     local ihdr = {}  
  77.     ihdr.width = __read_msb_uint32(fh)
  78.     ihdr.height = __read_msb_uint32(fh)
  79.     ihdr.bit_depth = __read_byte(fh)
  80.     ihdr.color_type = __read_byte(fh)
  81.     ihdr.compression_method = __read_byte(fh)
  82.     ihdr.filter_method = __read_byte(fh)
  83.     ihdr.interlace_method = __read_byte(fh)      
  84.     return ihdr
  85. end
  86.  
  87. local function __parse_IDAT(fh, len, commeth, outss)   ----206
  88.     if commeth ~= 0 then
  89.         error("Only zlib/DEFLATE compression supported")
  90.     end
  91.     local input = fh:read(len)
  92.     local cfg = {input=input, output=outss, disable_crc=true}
  93.     inflate_zlib(cfg)
  94.     return true
  95. end
  96.  
  97. local function getPNGStdByteAtXY(ihdr, oss, x, y)               ----237
  98.     local bpsl = getByteWidthPerScanline(ihdr) -- don't include filterType byte -- we don't store that after it has been read
  99.     if (x <= 0) or (y <= 0) then
  100.         return 0 -- this is what the spec says we should return when the coordinate is out of bounds -- in this part of the code, the coordinates are ONE-BASED like in good Lua
  101.     end
  102.     local offset_by_y = (y - 1) * bpsl
  103.     -- now read it!
  104.     local idx = offset_by_y + x
  105.     return oss.str:sub(idx, idx):byte()
  106. end
  107.  
  108. local function __paeth_predictor(a, b, c) --249
  109.     local p = a + b - c
  110.     local pa = math.abs(p - a)
  111.     local pb = math.abs(p - b)
  112.     local pc = math.abs(p - c)
  113.     if pa <= pb and pa <= pc then
  114.         return a
  115.     elseif pb <= pc then
  116.         return b
  117.     else
  118.         return c
  119.     end
  120. end
  121.  
  122. local function __parse_IDAT_effective_bytes(outss, ihdr)    ----265
  123.     local bpsl = getByteWidthPerScanline(ihdr)
  124.     local bypsl = math.ceil(getBitWidthPerPixel(ihdr) / 8)  
  125.     if outss.str:len() == 0 then
  126.         error("Empty string: outss")
  127.     end  
  128.     local bys = bytestream_from_string(outss.str)
  129.    
  130.     if not bys then
  131.         error("Did not get a bytestream from string", bys, outss)
  132.     end  
  133.     local out2 = outssmt.OutStringStream() -- __callable table with metatable that stores what you give it  
  134.     local y = 0  
  135.     -- x the byte being filtered;
  136.     -- a the byte corresponding to x in the pixel immediately before the pixel containing x (or the byte immediately before x, when the bit depth is less than 8);
  137.     -- b the byte corresponding to x in the previous scanline;
  138.     -- c the byte corresponding to b in the pixel immediately before the pixel containing b (or the byte immediately before b, when the bit depth is less than 8).
  139.     while true do
  140.         local filterType = bys:read()
  141.         if filterType == nil then
  142.             break
  143.         end
  144.         y = y + 1
  145.         for x = 1, bpsl do
  146.             local a = getPNGStdByteAtXY(ihdr, out2, x - bypsl, y)
  147.             local b = getPNGStdByteAtXY(ihdr, out2, x, y - 1)
  148.             local c = getPNGStdByteAtXY(ihdr, out2, x - bypsl, y - 1)        
  149.             local outVal = 0          
  150.             if filterType == 0 then outVal = bys:read()
  151.             elseif filterType == 1 then outVal = bys:read() + a
  152.             elseif filterType == 2 then outVal = bys:read() + b
  153.             elseif filterType == 3 then outVal = bys:read() + math.floor((a + b) / 2)
  154.             elseif filterType == 4 then outVal = bys:read() + __paeth_predictor(a, b, c)
  155.             else
  156.                 error("Unsupported Filter Type: " .. tostring(filterType))
  157.             end
  158.             outVal = outVal % 256
  159.             out2(outVal)
  160.         end
  161.     end  
  162.     return out2
  163. end
  164.  
  165. -- Warning: Co-ordinates are Zero-based but strings are 1-based
  166. function PNGImage:getByteOffsetForPixel(x, y)      ----498
  167.     return (((y * self.ihdr.width) + x) * 4) + 1
  168. end
  169.  
  170. function PNGImage:getPixel(x, y)
  171.     local off = self:getByteOffsetForPixel(x, y)
  172.     return self.data:byte(off, off + 3)
  173. end
  174. --[[
  175. function PNGImage:setPixel(x, y, col)
  176.     local off = self:getByteOffsetForPixel(x, y)
  177.     self.data = table.concat({self.data:sub(1, off - 1), string.char(col[1], col[2], col[3], col[4]), self.data:sub(off + 4)})
  178. end
  179. function PNGImage:lineXAB(ax, y, bx, col)
  180.     for x=ax, bx do
  181.         self:setPixel(x, y, col)
  182.     end
  183. end
  184. function PNGImage:lineYAB(x, ay, by, col)
  185.     for y=ay, by do
  186.         self:setPixel(x, y, col)
  187.     end
  188. end
  189. function PNGImage:lineRectangleAB(ax, ay, bx, by, col)
  190.     self:lineXAB(ax, ay, bx, col)
  191.     self:lineXAB(ax, by, bx, col)
  192.     self:lineYAB(ax, ay, by, col)
  193.     self:lineYAB(bx, ay, by, col)
  194. end
  195. function PNGImage:fillRectangleAB(ax, ay, bx, by, col)
  196.     for x=ax, bx do
  197.         for y=ay, by do
  198.             self:setPixel(x, y, col)
  199.         end
  200.     end
  201. end
  202. function PNGImage:saveToFile(fn)
  203.     local fh = io.open(fn, 'wb')
  204.     if not fh then
  205.         error("Could not open for writing: " .. fn)
  206.     end
  207.     self:saveToFileHandle(fh)
  208.     fh:close()
  209. end
  210. function PNGImage:generateRawIDATData(outbuf)
  211.     for y = 0, self.ihdr.height - 1 do
  212.         outbuf(0) -- filter type is 0 (Filt(x) = Orig(x))
  213.         for x = 0, self.ihdr.width - 1 do
  214.             local r, g, b, a = self:getPixel(x, y)
  215.             outbuf(r)
  216.             outbuf(g)
  217.             outbuf(b)
  218.             outbuf(a)
  219.         end
  220.     end
  221. end
  222. ]]
  223. function PNGImage:getSize()
  224.     return self.ihdr.width, self.ihdr.height
  225. end
  226.  
  227. local function newFromFile(fh)
  228.     local fh = io.open(fh, 'rb')
  229.     if not fh then
  230.         error("Could not open PNG file")
  231.     end
  232.     local pngi = {}
  233.     setmetatable(pngi, PNGImagemetatable)
  234.     local expecting = "\137\080\078\071\013\010\026\010"
  235.     if fh:read(8) ~= expecting then -- check the 8-byte PNG header exists
  236.         error("Not a PNG file")
  237.     end
  238.     local ihdr
  239.     local outss = outssmt.OutStringStream()
  240.     while true do
  241.         local len = __read_msb_uint32(fh)
  242.         local stype = fh:read(4)
  243.         if stype == 'IHDR' then
  244.             ihdr, msg = __parse_IHDR(fh, len)
  245.         elseif stype == 'IDAT' then
  246.             local res, msg = __parse_IDAT(fh, len, ihdr.compression_method, outss)
  247.         else
  248.             fh:read(len) -- dummy read
  249.         end
  250.         local crc = __read_msb_uint32(fh)
  251.         if stype == 'IEND' then
  252.             break
  253.         end
  254.     end
  255.     fh:close()
  256.     if ihdr.filter_method ~= 0 then
  257.         error("Unsupported Filter Method: " .. ihdr.filter_method)
  258.     end
  259.     if ihdr.interlace_method ~= 0 then
  260.         error("Unsupported Interlace Method (Interlacing is currently unsupported): " .. ihdr.interlace_method)
  261.     end
  262.     if ihdr.color_type ~= 6 --[[TruecolourAlpha]] and ihdr.color_type ~= 2 --[[Truecolour]] then
  263.         error("Currently, only Truecolour and Truecolour+Alpha images are supported.")
  264.     end  
  265.     if ihdr.bit_depth ~= 8 then
  266.         error("Currently, only images with a bit depth of 8 are supported.")
  267.     end      
  268.     -- now parse the IDAT chunks
  269.     local out2 = __parse_IDAT_effective_bytes(outss, ihdr)  
  270.     if ihdr.color_type == 2 --[[Truecolour]] then
  271.         -- add an alpha layer so it effectively becomes RGBA, not RGB
  272.         local inp = out2.str
  273.         out2 = outssmt.OutStringStream()      
  274.         for i=1, ihdr.width*ihdr.height do
  275.             local b = ((i - 1)*3) + 1
  276.             out2(inp:byte(b)) -- R
  277.             out2(inp:byte(b + 1)) -- G
  278.             out2(inp:byte(b + 2)) -- B
  279.             out2(255) -- A
  280.         end
  281.     end  
  282.     pngi.ihdr = ihdr
  283.     pngi.data = out2.str  
  284.     return pngi
  285. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement