local s={}s.__index=s local U=(function()local s={}local U=bit32.band local g=bit32.lshift local L=bit32.rshift local P=0 local p=1 local i=2 local C={[0]=3,4,5;6,7;8;9,10;11,13;15,17;19;23;27,31;35,43;51,59;67;83;99,115;131;163;195,227,258}local F={[0]=0;0;0;0;0,0,0;0,1,1;1;1,2;2,2;2;3;3,3;3,4;4,4,4;5;5;5,5,0}local d={[0]=1;2;3;4,5,7,9;13;17,25;33,49;65;97,129;193,257,385,513,769;1025,1537,2049,3073;4097,6145,8193,12289,16385,24577}local Q={[0]=0,0,0;0;1,1,2,2;3;3,4;4;5,5;6,6;7;7,8,8;9;9,10,10,11;11;12,12;13;13}local I={16,17,18;0;8;7;9,6,10;5,11,4,12;3,13;2,14,1;15}local V={0,8;144,9,256;7;280,8,288}local R={0,5,32}local function h(s)local U={Output=s,Window={},Pos=1}return U end local function c(s,U)local g=s.Pos s.Output(U)s.Window[g]=U s.Pos=g%32768+1 end local function X(s)local U={}local g=setmetatable({},U)function U.__index(L,U)local P=s(U)g[U]=P return P end return g end local t=X(function(s)return 2^s end)local S=setmetatable({},{__mode="\107"})local function e(s)local P=0 local p=0 local i={}S[i]=true function i.GetBitsLeft(s)return p end function i.Read(C,i)i=i or 1 while p<i do local U=s:ReadByte()if not U then return end P=P+g(U,p)p=p+8 end local F if i==0 then F=0 elseif i==32 then F=P P=0 else F=U(P,L(4294967295.0,32-i))P=L(P,i)end p=p-i return F end return i end local function k(s)if S[s]then return s end return e(s)end local function o(s,U)return s.NumBits==U.NumBits and s.Value<U.Value or s.NumBits<U.NumBits end local function T(s,P)local p=0 for P=1,P,1 do p=g(p,1)+U(s,1)s=L(s,1)end return p end local function a(s,U)local g={}if U then for s,U in pairs(s)do if U~=0 then g[#g+1]={Value=s,NumBits=U}end end else for U=1,#s-2,2 do local L=s[U]local P=s[U+1]local p=s[U+2]if P~=0 then for s=L,p-1,1 do g[#g+1]={Value=s,NumBits=P}end end end end table.sort(g,o)local L=1 local P=0 for s,U in ipairs(g)do if U.NumBits~=P then L=L*t[U.NumBits-P]P=U.NumBits end U.Code=L L=L+1 end local p=math.huge local i={}for s,U in ipairs(g)do p=math.min(p,U.NumBits)i[U.Code]=U.Value end local C=X(function(s)return t[p]+T(s,p)end)function g.Read(U,s)local g=1 local L=0 while true do if L==0 then local U=s:Read(p)L=L+p g=C[U]else local U=s:Read()L=L+1 g=g*2+U end local U=i[g]if U then return U end end end return g end local function w(s)local U=s:Read(4)local g=s:Read(4)local L=s:Read(5)local P=s:Read(1)local p=s:Read(2)local i=g*16+U local C=(L+P*32)+p*64 if U~=8 then error("\117\110\114\101\099\111\103\110\105\122\101\100 \122\108\105\098 \099\111\109\112\114\101\115\115\105\111\110 \109\101\116\104\111\100\058 "..U)end if g>7 then error("\105\110\118\097\108\105\100 \122\108\105\098 \119\105\110\100\111\119 \115\105\122\101\058 \099\105\110\102\111\061"..g)end local F=2^(g+8)if(i*256+C)%31~=0 then error("\105\110\118\097\108\105\100 \122\108\105\098 \104\101\097\100\101\114 \040\098\097\100 \102\099\104\101\099\107 \115\117\109\041")end if P==1 then error("\070\073\088\058\084\079\068\079 - \070\068\073\067\084 \110\111\116 \099\117\114\114\101\110\116\108\121 \105\109\112\108\101\109\101\110\116\101\100")end return F end local function O(s)local U=s:Read(5)local g=s:Read(5)local L=s:Read(4)local P={}for U=1,L+4,1 do local g=I[U]P[g]=s:Read(3)end P=a(P,true)local function p(U)local g={}local L local p=0 while p<U do local U=P:Read(s)local i if U<=15 then i=1 L=U elseif U==16 then i=3+s:Read(2)elseif U==17 then i=3+s:Read(3)L=0 elseif U==18 then i=11+s:Read(7)L=0 end for s=1,i,1 do g[p]=L p=p+1 end end return a(g,true)end local i=U+257 local C=g+1 local F=p(i)local d=p(C)return F,d end local function r(s,U,g,L)local P=g:Read(s)if P<256 then c(U,P)elseif P==256 then return true else local g=C[P-257]local p=F[P-257]local i=s:Read(p)local I=g+i local V=L:Read(s)local R=d[V]local h=Q[V]local X=s:Read(h)local t=R+X for s=1,I,1 do local g=((U.Pos-1)-t)%32768+1 local L=assert(U.Window[g],"\105\110\118\097\108\105\100 \100\105\115\116\097\110\099\101")c(U,L)end end return false end local function A(s,U)local g=s:Read(1)local L=s:Read(2)if L==P then local g=s:GetBitsLeft()s:Read(g)local L=s:Read(16)local P=s:Read(16)for g=1,L,1 do local L=s:Read(8)c(U,L)end elseif L==p or L==i then local g,P if L==i then g,P=O(s)else g=a(V)P=a(R)end repeat until r(s,U,g,P)else error("\117\110\114\101\099\111\103\110\105\122\101\100 \099\111\109\112\114\101\115\115\105\111\110 \116\121\112\101")end return g~=0 end function s.Inflate(U,s)local g=h(s.Output)local L=k(s.Input)repeat until A(L,g)end function s.InflateZlib(U,s)local g=k(s.Input)local L=w(g)U:Inflate({Input=g,Output=s.Output})local P=g:GetBitsLeft()g:Read(P)end return s end)()local g=(function()local s={}s.__index=s function s.new(U)local g={Position=1;Buffer=U;Length=#U}return setmetatable(g,s)end function s.ReadByte(s)local U=s.Buffer local g=s.Position if g<=s.Length then local L=U:sub(g,g)s.Position=g+1 return L:byte()end end function s.ReadBytes(g,s,U)local L={}for s=1,s,1 do L[s]=g:ReadByte()end if U then return L end return unpack(L)end function s.ReadAllBytes(s)return s:ReadBytes(s.Length,true)end function s.IterateBytes(s)return function()return s:ReadByte()end end function s.TwosComplementOf(g,s,U)if s>=2^(U-1)then s=s-2^U end return s end function s.ReadUInt16(s)local U,g=s:ReadBytes(2)return U*256+g end function s.ReadInt16(s)local U=s:ReadUInt16()return s:TwosComplementOf(U,16)end function s.ReadUInt32(s)local U=s:ReadUInt16()local g=s:ReadUInt16()return U*65536+g end function s.ReadInt32(s)local U=s:ReadUInt32()return s:TwosComplementOf(U,32)end function s.ReadString(U,s)if s==nil then s=U:ReadByte()end local g=U.Position local L=math.min(U.Length,g+s)local P=U.Buffer:sub(g,L-1)U.Position=L return P end function s.ForkReader(g,U)local L=g:ReadString(U)return s.new(L)end return s end)()local L=(function()local s={}function s.None(P,s,U,g,L)for g=1,#s,1 do U[L][g]=s[g]end end function s.Sub(P,s,U,g,L)for g=1,g,1 do U[L][g]=s[g]end for P=g+1,#s,1 do local p=s[P]local i=U[L][P-g]U[L][P]=bit32.band(p+i,255)end end function s.Up(P,s,U,g,L)if L>1 then local g=U[L-1]for P=1,#s,1 do local p=s[P]local i=g[P]U[L][P]=bit32.band(p+i,255)end else P:None(s,U,g,L)end end function s.Average(P,s,U,g,L)if L>1 then for g=1,g,1 do local P=s[g]local p=U[L-1][g]p=bit32.rshift(p,1)U[L][g]=bit32.band(P+p,255)end for P=g+1,#s,1 do local p=s[P]local i=U[L-1][P]local C=U[L][P-g]local F=bit32.rshift(C+i,1)U[L][P]=bit32.band(p+F,255)end else for g=1,g,1 do U[L][g]=s[g]end for g=g+1,#s,1 do local P=s[g]local p=U[L-1][g]p=bit32.rshift(p,1)U[L][g]=bit32.band(P+p,255)end end end function s.Paeth(P,s,U,g,L)if L>1 then local P for g=1,g,1 do local P=s[g]local p=U[L-1][g]U[L][g]=bit32.band(P+p,255)end for p=g+1,#s,1 do local i=U[L][p-g]local C=U[L-1][p]local F=U[L-1][p-g]local d=s[p]local Q=(i+C)-F local I=math.abs(Q-i)local V=math.abs(Q-C)local R=math.abs(Q-F)if I<=V and I<=R then P=i elseif V<=R then P=C else P=F end U[L][p]=bit32.band(d+P,255)end else P:Sub(s,U,g,L)end end return s end)()local P={}function P.IDAT(s,U)local g=U.CRC local L=s.Hash or 0 local P=U.Data local p=P.Buffer s.Hash=bit32.bxor(L,g)s.ZlibStream=s.ZlibStream..p end function P.IEND(s)s.Reading=nil end function P.IHDR(s,U)local g=U.Data s.Width=g:ReadInt32()s.Height=g:ReadInt32()s.BitDepth=g:ReadByte()s.ColorType=g:ReadByte()s.Methods={Compression=g:ReadByte(),Filtering=g:ReadByte();Interlace=g:ReadByte()}end function P.PLTE(s,U)if not s.Palette then s.Palette={}end local g=U.Data local L=g:ReadAllBytes()if#L%3~=0 then error("\080\078\071 - \073\110\118\097\108\105\100 \080\076\084\069 \099\104\117\110\107\046")end for U=1,#L,3 do local g=L[U]local P=L[U+1]local p=L[U+2]local i=Color3.fromRGB(g,P,p)local C=#s.Palette+1 s.Palette[C]=i end end function P.bKGD(s,U)local g=U.Data local L=s.BitDepth local P=s.ColorType L=2^L-1 if P==3 then local U=g:ReadByte()s.BackgroundColor=s.Palette[U]elseif P==0 or P==4 then local U=g:ReadUInt16()/L s.BackgroundColor=Color3.fromHSV(0,0,U)elseif P==2 or P==6 then local U=g:ReadUInt16()/L local P=g:ReadUInt16()/L local p=g:ReadUInt16()/L s.BackgroundColor=Color3.new(U,P,p)end end function P.cHRM(s,U)local g={"\087\104\105\116\101","\082\101\100","\071\114\101\101\110";"\066\108\117\101"}local L={}local P=U.Data for s=1,4,1 do local U=g[s]L[U]={[1]=P:ReadUInt32()/100000.0,[2]=P:ReadUInt32()/100000.0}end s.Chromaticity=L end function P.gAMA(s,U)local g=U.Data local L=g:ReadUInt32()s.Gamma=L/100000.0 end function P.sRGB(s,U)local g=U.Data s.RenderIntent=g:ReadByte()end function P.tEXt(s,U)local g=U.Data local L,P="",""for s in g:IterateBytes()do local U=string.char(s)if U=="\000"then L=P P=""else P=P..U end end s.Metadata[L]=P end function P.tIME(s,U)local g=U.Data local L={Year=g:ReadUInt16();Month=g:ReadByte();Day=g:ReadByte();Hour=g:ReadByte(),Minute=g:ReadByte(),Second=g:ReadByte()}s.TimeStamp=L end function P.tRNS(s,U)local g=U.Data local L=s.BitDepth local P=s.ColorType L=2^L-1 if P==3 then local U=s.Palette local L={}for s=1,#U,1 do local U=g:ReadByte()if not U then U=255 end L[s]=U end s.AlphaData=L elseif P==0 then local U=g:ReadUInt16()s.Alpha=U/L elseif P==2 then local U=g:ReadUInt16()/L local P=g:ReadUInt16()/L local p=g:ReadUInt16()/L s.Alpha=Color3.new(U,P,p)else error("\080\078\071 - \073\110\118\097\108\105\100 \116\082\078\083 \099\104\117\110\107")end end local function p(s)if s==0 or s==3 then return 1 elseif s==4 then return 2 elseif s==2 then return 3 elseif s==6 then return 4 else return 0 end end local function i(s,U,g)local L=tonumber(s)or 0 L=math.floor(L+.5)return math.clamp(L,U,g)end local function C(s,U,g)local L=s.Width local P=s.Height local p=i(U,1,L)local C=i(g,1,P)local F=s.Bitmap local d=s.BytesPerPixel local Q=(p-1)*d+1 local I=Q+d return F[C],Q,I end function s.GetPixel(g,s,U)local L,P,p=C(g,s,U)local i=g.ColorType local F,d do if i==0 then local s=unpack(L,P,p)F=Color3.fromHSV(0,0,s)d=255 elseif i==2 then local s,U,g=unpack(L,P,p)F=Color3.fromRGB(s,U,g)d=255 elseif i==3 then local s=g.Palette local U=g.AlphaData local i=unpack(L,P,p)i=i+1 if s then F=s[i]end if U then d=U[i]end elseif i==4 then local s,U=unpack(L,P,p)F=Color3.fromHSV(0,0,s)d=U elseif i==6 then local s,U,g,i=unpack(L,P,p)F=Color3.fromRGB(s,U,g,i)d=i end end if not F then F=Color3.new()end if not d then d=255 end return F,d end function s.new(i)local C=g.new(i)local F={Chunks={},Metadata={};Reading=true;ZlibStream=""}local d=C:ReadString(8)if d~="\137\080\078\071\r\n\026\n"then error("\080\078\071 - \073\110\112\117\116 \100\097\116\097 \105\115 \110\111\116 \097 \080\078\071 \102\105\108\101\046",2)end while F.Reading do local s=C:ReadInt32()local U=C:ReadString(4)local g,L if s>0 then g=C:ForkReader(s)L=C:ReadUInt32()end local p={Length=s,Type=U,Data=g,CRC=L}local i=P[U]if i then i(F,p)end table.insert(F.Chunks,p)end local Q,I=pcall(function()local s={}local L=0 U:InflateZlib({Input=g.new(F.ZlibStream);Output=function(U)L=L+1 s[L]=string.char(U)end})return table.concat(s)end)if not Q then error("\080\078\071 - \085\110\097\098\108\101 \116\111 \117\110\112\097\099\107 \080\078\071 \100\097\116\097\046 "..tostring(I),2)end local V=F.Width local R=F.Height local h=F.BitDepth local c=F.ColorType local X=g.new(I)F.ZlibStream=nil local t={}F.Bitmap=t local S=p(c)F.NumChannels=S local e=math.max(1,S*(h/8))F.BytesPerPixel=e for s=1,R,1 do local U=X:ReadByte()local g=X:ReadBytes(V*e,true)t[s]={}if U==0 then L:None(g,t,e,s)elseif U==1 then L:Sub(g,t,e,s)elseif U==2 then L:Up(g,t,e,s)elseif U==3 then L:Average(g,t,e,s)elseif U==4 then L:Paeth(g,t,e,s)end end return setmetatable(F,s)end return s
-- Note: Compacted into a single script for Remote-Image-Library.

---------------------------------------------------------------------------------------------
-- @ CloneTrooper1019, 2019
---------------------------------------------------------------------------------------------
-- [PNG Library]
--
--  A module for opening PNG files into a readable bitmap.
--  This implementation works with most PNG files.
--
---------------------------------------------------------------------------------------------

--[[
	
MIT License

Copyright (c) 2019 Max G. (CloneTrooper1019)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]

local PNG = {}
PNG.__index = PNG

local Deflate = (function()
	--[[
	LUA MODULE

	compress.deflatelua - deflate (and zlib) implemented in Lua.

	DESCRIPTION

	This is a pure Lua implementation of decompressing the DEFLATE format,
	including the related zlib format.

	Note: This library only supports decompression.
	Compression is not currently implemented.

	REFERENCES

	[1] DEFLATE Compressed Data Format Specification version 1.3
	http://tools.ietf.org/html/rfc1951
	[2] GZIP file format specification version 4.3
	http://tools.ietf.org/html/rfc1952
	[3] http://en.wikipedia.org/wiki/DEFLATE
	[4] pyflate, by Paul Sladen
	http://www.paul.sladen.org/projects/pyflate/
	[5] Compress::Zlib::Perl - partial pure Perl implementation of
	Compress::Zlib
	http://search.cpan.org/~nwclark/Compress-Zlib-Perl/Perl.pm

	LICENSE

	(c) 2008-2011 David Manura.  Licensed under the same terms as Lua (MIT).
	    Heavily modified by Max G. (2019)

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
	(end license)
	--]]

	local Deflate = {}

	local band = bit32.band
	local lshift = bit32.lshift
	local rshift = bit32.rshift

	local BTYPE_NO_COMPRESSION = 0
	local BTYPE_FIXED_HUFFMAN = 1
	local BTYPE_DYNAMIC_HUFFMAN = 2

	local lens = -- Size base for length codes 257..285
		{
			[0] = 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
			35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
		}

	local lext = -- Extra bits for length codes 257..285
		{
			[0] = 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
			3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
		}

	local dists = -- Offset base for distance codes 0..29
		{
			[0] = 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
			257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
			8193, 12289, 16385, 24577
		}

	local dext = -- Extra bits for distance codes 0..29
		{
			[0] = 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
			7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
			12, 12, 13, 13
		}

	local order = -- Permutation of code length codes
		{
			16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 
			11, 4, 12, 3, 13, 2, 14, 1, 15
		}

	-- Fixed literal table for BTYPE_FIXED_HUFFMAN
	local fixedLit = {0, 8, 144, 9, 256, 7, 280, 8, 288}

	-- Fixed distance table for BTYPE_FIXED_HUFFMAN
	local fixedDist = {0, 5, 32}

	local function createState(bitStream)
		local state = 
			{
				Output = bitStream;
				Window = {};
				Pos = 1;
			}

		return state
	end

	local function write(state, byte)
		local pos = state.Pos
		state.Output(byte)
		state.Window[pos] = byte
		state.Pos = pos % 32768 + 1  -- 32K
	end

	local function memoize(fn)
		local meta = {}
		local memoizer = setmetatable({}, meta)

		function meta:__index(k)
			local v = fn(k)
			memoizer[k] = v

			return v
		end

		return memoizer
	end

	-- small optimization (lookup table for powers of 2)
	local pow2 = memoize(function (n) 
		return 2 ^ n 
	end)

	-- weak metatable marking objects as bitstream type
	local isBitStream = setmetatable({}, { __mode = 'k' })

	local function createBitStream(reader)
		local buffer = 0
		local bitsLeft = 0

		local stream = {}
		isBitStream[stream] = true

		function stream:GetBitsLeft()
			return bitsLeft
		end

		function stream:Read(count)
			count = count or 1

			while bitsLeft < count do
				local byte = reader:ReadByte()

				if not byte then 
					return 
				end

				buffer = buffer + lshift(byte, bitsLeft)
				bitsLeft = bitsLeft + 8
			end

			local bits

			if count == 0 then
				bits = 0
			elseif count == 32 then
				bits = buffer
				buffer = 0
			else
				bits = band(buffer, rshift(2^32 - 1, 32 - count))
				buffer = rshift(buffer, count)
			end

			bitsLeft = bitsLeft - count
			return bits
		end

		return stream
	end

	local function getBitStream(obj)
		if isBitStream[obj] then
			return obj
		end

		return createBitStream(obj)
	end

	local function sortHuffman(a, b)
		return a.NumBits == b.NumBits and a.Value < b.Value or a.NumBits < b.NumBits
	end

	local function msb(bits, numBits)
		local res = 0

		for i = 1, numBits do
			res = lshift(res, 1) + band(bits, 1)
			bits = rshift(bits, 1)
		end

		return res
	end

	local function createHuffmanTable(init, isFull)
		local hTable = {}

		if isFull then
			for val, numBits in pairs(init) do
				if numBits ~= 0 then
					hTable[#hTable + 1] = 
						{
							Value = val;
							NumBits = numBits;
						}
				end
			end
		else
			for i = 1, #init - 2, 2 do
				local firstVal = init[i]

				local numBits = init[i + 1]
				local nextVal = init[i + 2]

				if numBits ~= 0 then
					for val = firstVal, nextVal - 1 do
						hTable[#hTable + 1] = 
							{
								Value = val;
								NumBits = numBits;
							}
					end
				end
			end
		end

		table.sort(hTable, sortHuffman)

		local code = 1
		local numBits = 0

		for i, slide in ipairs(hTable) do
			if slide.NumBits ~= numBits then
				code = code * pow2[slide.NumBits - numBits]
				numBits = slide.NumBits
			end

			slide.Code = code
			code = code + 1
		end

		local minBits = math.huge
		local look = {}

		for i, slide in ipairs(hTable) do
			minBits = math.min(minBits, slide.NumBits)
			look[slide.Code] = slide.Value
		end

		local firstCode = memoize(function (bits) 
			return pow2[minBits] + msb(bits, minBits) 
		end)

		function hTable:Read(bitStream)
			local code = 1 -- leading 1 marker
			local numBits = 0

			while true do
				if numBits == 0 then  -- small optimization (optional)
					local index = bitStream:Read(minBits)
					numBits = numBits + minBits
					code = firstCode[index]
				else
					local bit = bitStream:Read()
					numBits = numBits + 1
					code = code * 2 + bit -- MSB first
				end

				local val = look[code]

				if val then
					return val
				end
			end
		end

		return hTable
	end

	local function parseZlibHeader(bitStream)
		-- Compression Method
		local cm = bitStream:Read(4)

		-- Compression info
		local cinfo = bitStream:Read(4)  

		-- FLaGs: FCHECK (check bits for CMF and FLG)   
		local fcheck = bitStream:Read(5)

		-- FLaGs: FDICT (present dictionary)
		local fdict = bitStream:Read(1)

		-- FLaGs: FLEVEL (compression level)
		local flevel = bitStream:Read(2)

		-- CMF (Compresion Method and flags)
		local cmf = cinfo * 16  + cm

		-- FLaGs
		local flg = fcheck + fdict * 32 + flevel * 64 

		if cm ~= 8 then -- not "deflate"
			error("unrecognized zlib compression method: " .. cm)
		end

		if cinfo > 7 then
			error("invalid zlib window size: cinfo=" .. cinfo)
		end

		local windowSize = 2 ^ (cinfo + 8)

		if (cmf * 256 + flg) % 31 ~= 0 then
			error("invalid zlib header (bad fcheck sum)")
		end

		if fdict == 1 then
			error("FIX:TODO - FDICT not currently implemented")
		end

		return windowSize
	end

	local function parseHuffmanTables(bitStream)
		local numLits  = bitStream:Read(5) -- # of literal/length codes - 257
		local numDists = bitStream:Read(5) -- # of distance codes - 1
		local numCodes = bitStream:Read(4) -- # of code length codes - 4

		local codeLens = {}

		for i = 1, numCodes + 4 do
			local index = order[i]
			codeLens[index] = bitStream:Read(3)
		end

		codeLens = createHuffmanTable(codeLens, true)

		local function decode(numCodes)
			local init = {}
			local numBits
			local val = 0

			while val < numCodes do
				local codeLen = codeLens:Read(bitStream)
				local numRepeats

				if codeLen <= 15 then
					numRepeats = 1
					numBits = codeLen
				elseif codeLen == 16 then
					numRepeats = 3 + bitStream:Read(2)
				elseif codeLen == 17 then
					numRepeats = 3 + bitStream:Read(3)
					numBits = 0
				elseif codeLen == 18 then
					numRepeats = 11 + bitStream:Read(7)
					numBits = 0
				end

				for i = 1, numRepeats do
					init[val] = numBits
					val = val + 1
				end
			end

			return createHuffmanTable(init, true)
		end

		local numLitCodes = numLits + 257
		local numDistCodes = numDists + 1

		local litTable = decode(numLitCodes)
		local distTable = decode(numDistCodes)

		return litTable, distTable
	end

	local function parseCompressedItem(bitStream, state, litTable, distTable)
		local val = litTable:Read(bitStream)

		if val < 256 then -- literal
			write(state, val)
		elseif val == 256 then -- end of block
			return true
		else
			local lenBase = lens[val - 257]
			local numExtraBits = lext[val - 257]

			local extraBits = bitStream:Read(numExtraBits)
			local len = lenBase + extraBits

			local distVal = distTable:Read(bitStream)
			local distBase = dists[distVal]

			local distNumExtraBits = dext[distVal]
			local distExtraBits = bitStream:Read(distNumExtraBits)

			local dist = distBase + distExtraBits

			for i = 1, len do
				local pos = (state.Pos - 1 - dist) % 32768 + 1
				local byte = assert(state.Window[pos], "invalid distance")
				write(state, byte)
			end
		end

		return false
	end

	local function parseBlock(bitStream, state)
		local bFinal = bitStream:Read(1)
		local bType = bitStream:Read(2)

		if bType == BTYPE_NO_COMPRESSION then
			local left = bitStream:GetBitsLeft()
			bitStream:Read(left)

			local len = bitStream:Read(16)
			local nlen = bitStream:Read(16)

			for i = 1, len do
				local byte = bitStream:Read(8)
				write(state, byte)
			end
		elseif bType == BTYPE_FIXED_HUFFMAN or bType == BTYPE_DYNAMIC_HUFFMAN then
			local litTable, distTable

			if bType == BTYPE_DYNAMIC_HUFFMAN then
				litTable, distTable = parseHuffmanTables(bitStream)
			else
				litTable = createHuffmanTable(fixedLit)
				distTable = createHuffmanTable(fixedDist)
			end

			repeat until parseCompressedItem(bitStream, state, litTable, distTable)
		else
			error("unrecognized compression type")
		end

		return bFinal ~= 0
	end

	function Deflate:Inflate(io)
		local state = createState(io.Output)
		local bitStream = getBitStream(io.Input)

		repeat until parseBlock(bitStream, state)
	end

	function Deflate:InflateZlib(io)
		local bitStream = getBitStream(io.Input)
		local windowSize = parseZlibHeader(bitStream)

		self:Inflate
		{
			Input = bitStream;
			Output = io.Output;
		}

		local bitsLeft = bitStream:GetBitsLeft()
		bitStream:Read(bitsLeft)
	end

	return Deflate	
end)();

local BinaryReader = (function()
	local BinaryReader = {}
	BinaryReader.__index = BinaryReader

	function BinaryReader.new(buffer)
		local reader = 
			{
				Position = 1;
				Buffer = buffer;
				Length = #buffer;
			}

		return setmetatable(reader, BinaryReader)
	end

	function BinaryReader:ReadByte()
		local buffer = self.Buffer
		local pos = self.Position

		if pos <= self.Length then
			local result = buffer:sub(pos, pos)
			self.Position = pos + 1

			return result:byte()
		end
	end

	function BinaryReader:ReadBytes(count, asArray)
		local values = {}

		for i = 1, count do
			values[i] = self:ReadByte()
		end

		if asArray then
			return values
		end

		return unpack(values)
	end

	function BinaryReader:ReadAllBytes()
		return self:ReadBytes(self.Length, true)
	end

	function BinaryReader:IterateBytes()
		return function ()
			return self:ReadByte()
		end
	end

	function BinaryReader:TwosComplementOf(value, numBits)
		if value >= (2 ^ (numBits - 1)) then
			value = value - (2 ^ numBits)
		end

		return value
	end

	function BinaryReader:ReadUInt16()
		local upper, lower = self:ReadBytes(2)
		return (upper * 256) + lower
	end

	function BinaryReader:ReadInt16()
		local unsigned = self:ReadUInt16()
		return self:TwosComplementOf(unsigned, 16)
	end

	function BinaryReader:ReadUInt32()
		local upper = self:ReadUInt16()
		local lower = self:ReadUInt16()

		return (upper * 65536) + lower
	end

	function BinaryReader:ReadInt32()
		local unsigned = self:ReadUInt32()
		return self:TwosComplementOf(unsigned, 32)
	end

	function BinaryReader:ReadString(length)
		if length == nil then
			length = self:ReadByte()
		end

		local pos = self.Position
		local nextPos = math.min(self.Length, pos + length)

		local result = self.Buffer:sub(pos, nextPos - 1)
		self.Position = nextPos

		return result
	end

	function BinaryReader:ForkReader(length)
		local chunk = self:ReadString(length)
		return BinaryReader.new(chunk)
	end

	return BinaryReader
end)();
local Unfilter = (function()
	local Unfilter = {}

	function Unfilter:None(scanline, pixels, bpp, row)
		for i = 1, #scanline do
			pixels[row][i] = scanline[i]
		end
	end

	function Unfilter:Sub(scanline, pixels, bpp, row)
		for i = 1, bpp do
			pixels[row][i] = scanline[i]
		end

		for i = bpp + 1, #scanline do
			local x = scanline[i]
			local a = pixels[row][i - bpp]
			pixels[row][i] = bit32.band(x + a, 0xFF)
		end
	end

	function Unfilter:Up(scanline, pixels, bpp, row)
		if row > 1 then
			local upperRow = pixels[row - 1]

			for i = 1, #scanline do
				local x = scanline[i]
				local b = upperRow[i]
				pixels[row][i] = bit32.band(x + b, 0xFF)
			end
		else
			self:None(scanline, pixels, bpp, row)
		end
	end

	function Unfilter:Average(scanline, pixels, bpp, row)
		if row > 1 then
			for i = 1, bpp do
				local x = scanline[i]
				local b = pixels[row - 1][i]

				b = bit32.rshift(b, 1)
				pixels[row][i] = bit32.band(x + b, 0xFF)
			end

			for i = bpp + 1, #scanline do
				local x = scanline[i]
				local b = pixels[row - 1][i]

				local a = pixels[row][i - bpp]
				local ab = bit32.rshift(a + b, 1)

				pixels[row][i] = bit32.band(x + ab, 0xFF)
			end
		else
			for i = 1, bpp do
				pixels[row][i] = scanline[i]
			end

			for i = bpp + 1, #scanline do
				local x = scanline[i]
				local b = pixels[row - 1][i]

				b = bit32.rshift(b, 1)
				pixels[row][i] = bit32.band(x + b, 0xFF)
			end
		end
	end

	function Unfilter:Paeth(scanline, pixels, bpp, row)
		if row > 1 then
			local pr

			for i = 1, bpp do
				local x = scanline[i]
				local b = pixels[row - 1][i]
				pixels[row][i] = bit32.band(x + b, 0xFF)
			end

			for i = bpp + 1, #scanline do
				local a = pixels[row][i - bpp]
				local b = pixels[row - 1][i]
				local c = pixels[row - 1][i - bpp]

				local x = scanline[i]
				local p = a + b - c

				local pa = math.abs(p - a)
				local pb = math.abs(p - b)
				local pc = math.abs(p - c)

				if pa <= pb and pa <= pc then
					pr = a
				elseif pb <= pc then
					pr = b
				else
					pr = c
				end

				pixels[row][i] = bit32.band(x + pr, 0xFF)
			end
		else
			self:Sub(scanline, pixels, bpp, row)
		end
	end

	return Unfilter	
end)();

local chunk_handlers = {};

function chunk_handlers.IDAT(file, chunk)
	local crc = chunk.CRC
	local hash = file.Hash or 0

	local data = chunk.Data
	local buffer = data.Buffer

	file.Hash = bit32.bxor(hash, crc)
	file.ZlibStream = file.ZlibStream .. buffer
end

function chunk_handlers.IEND(file)
	file.Reading = nil
end

function chunk_handlers.IHDR(file, chunk)
	local data = chunk.Data

	file.Width = data:ReadInt32();
	file.Height = data:ReadInt32();

	file.BitDepth = data:ReadByte();
	file.ColorType = data:ReadByte();

	file.Methods =
		{
			Compression = data:ReadByte();
			Filtering   = data:ReadByte();
			Interlace   = data:ReadByte();
		}
end

function chunk_handlers.PLTE(file, chunk)
	if not file.Palette then
		file.Palette = {}
	end

	local data = chunk.Data
	local palette = data:ReadAllBytes()

	if #palette % 3 ~= 0 then
		error("PNG - Invalid PLTE chunk.")
	end

	for i = 1, #palette, 3 do
		local r = palette[i]
		local g = palette[i + 1]
		local b = palette[i + 2]

		local color = Color3.fromRGB(r, g, b)
		local index = #file.Palette + 1

		file.Palette[index] = color
	end
end

function chunk_handlers.bKGD(file, chunk)
	local data = chunk.Data

	local bitDepth = file.BitDepth
	local colorType = file.ColorType

	bitDepth = (2 ^ bitDepth) - 1

	if colorType == 3 then
		local index = data:ReadByte()
		file.BackgroundColor = file.Palette[index]
	elseif colorType == 0 or colorType == 4 then
		local gray = data:ReadUInt16() / bitDepth
		file.BackgroundColor = Color3.fromHSV(0, 0, gray)
	elseif colorType == 2 or colorType == 6 then
		local r = data:ReadUInt16() / bitDepth
		local g = data:ReadUInt16() / bitDepth
		local b = data:ReadUInt16() / bitDepth
		file.BackgroundColor = Color3.new(r, g, b)
	end
end


function chunk_handlers.cHRM(file, chunk)
	local colors = {"White", "Red", "Green", "Blue"}
	local chrome = {}
	local data = chunk.Data

	for i = 1, 4 do
		local color = colors[i]

		chrome[color] =
			{
				[1] = data:ReadUInt32() / 10e4;
				[2] = data:ReadUInt32() / 10e4;
			}
	end

	file.Chromaticity = chrome
end

function chunk_handlers.gAMA(file, chunk)
	local data = chunk.Data
	local value = data:ReadUInt32()
	file.Gamma = value / 10e4
end

function chunk_handlers.sRGB(file, chunk)
	local data = chunk.Data
	file.RenderIntent = data:ReadByte()
end

function chunk_handlers.tEXt(file, chunk)
	local data = chunk.Data
	local key, value = "", ""

	for byte in data:IterateBytes() do
		local char = string.char(byte)

		if char == '\0' then
			key = value
			value = ""
		else
			value = value .. char
		end
	end

	file.Metadata[key] = value
end

function chunk_handlers.tIME(file, chunk)
	local data = chunk.Data

	local timeStamp = 
		{
			Year  = data:ReadUInt16();
			Month = data:ReadByte();
			Day   = data:ReadByte();

			Hour   = data:ReadByte();
			Minute = data:ReadByte();
			Second = data:ReadByte();
		}

	file.TimeStamp = timeStamp
end

function chunk_handlers.tRNS(file, chunk)
	local data = chunk.Data

	local bitDepth = file.BitDepth
	local colorType = file.ColorType

	bitDepth = (2 ^ bitDepth) - 1

	if colorType == 3 then
		local palette = file.Palette
		local alphaMap = {}

		for i = 1, #palette do
			local alpha = data:ReadByte()

			if not alpha then
				alpha = 255
			end

			alphaMap[i] = alpha
		end

		file.AlphaData = alphaMap
	elseif colorType == 0 then
		local grayAlpha = data:ReadUInt16()
		file.Alpha = grayAlpha / bitDepth
	elseif colorType == 2 then
		-- TODO: This seems incorrect...
		local r = data:ReadUInt16() / bitDepth
		local g = data:ReadUInt16() / bitDepth
		local b = data:ReadUInt16() / bitDepth
		file.Alpha = Color3.new(r, g, b)
	else
		error("PNG - Invalid tRNS chunk")
	end	
end

local function getBytesPerPixel(colorType)
	if colorType == 0 or colorType == 3 then
		return 1
	elseif colorType == 4 then
		return 2
	elseif colorType == 2 then
		return 3
	elseif colorType == 6 then
		return 4
	else
		return 0
	end
end

local function clampInt(value, min, max)
	local num = tonumber(value) or 0
	num = math.floor(num + .5)
	
	return math.clamp(num, min, max)
end

local function indexBitmap(file, x, y)
	local width = file.Width
	local height = file.Height
	
	local x = clampInt(x, 1, width) 
	local y = clampInt(y, 1, height)
	
	local bitmap = file.Bitmap
	local bpp = file.BytesPerPixel
	
	local i0 = ((x - 1) * bpp) + 1
	local i1 = i0 + bpp
	
	return bitmap[y], i0, i1
end

function PNG:GetPixel(x, y)
	local row, i0, i1 = indexBitmap(self, x, y)
	local colorType = self.ColorType
	
	local color, alpha do
		if colorType == 0 then
			local gray = unpack(row, i0, i1)
			color = Color3.fromHSV(0, 0, gray)
			alpha = 255
		elseif colorType == 2 then
			local r, g, b = unpack(row, i0, i1)
			color = Color3.fromRGB(r, g, b)
			alpha = 255
		elseif colorType == 3 then
			local palette = self.Palette
			local alphaData = self.AlphaData
			
			local index = unpack(row, i0, i1)
			index = index + 1
			
			if palette then
				color = palette[index]
			end
			
			if alphaData then
				alpha = alphaData[index]
			end
		elseif colorType == 4 then
			local gray, a = unpack(row, i0, i1)
			color = Color3.fromHSV(0, 0, gray)
			alpha = a
		elseif colorType == 6 then
			local r, g, b, a = unpack(row, i0, i1)
			color = Color3.fromRGB(r, g, b, a)
			alpha = a
		end
	end
	
	if not color then
		color = Color3.new()
	end
	
	if not alpha then
		alpha = 255
	end
	
	return color, alpha
end

function PNG.new(buffer)
	-- Create the reader.
	local reader = BinaryReader.new(buffer)
	
	-- Create the file object.
	local file =
	{
		Chunks = {};
		Metadata = {};
		
		Reading = true;
		ZlibStream = "";
	}
	
	-- Verify the file header.
	local header = reader:ReadString(8)
	
	if header ~= "\137PNG\r\n\26\n" then
		error("PNG - Input data is not a PNG file.", 2)
	end
	
	while file.Reading do
		local length = reader:ReadInt32()
		local chunkType = reader:ReadString(4)
		
		local data, crc
		
		if length > 0 then
			data = reader:ForkReader(length)
			crc = reader:ReadUInt32()
		end
		
		local chunk = 
		{
			Length = length;
			Type = chunkType;
			
			Data = data;
			CRC = crc;
		}
		
		local handler = chunk_handlers[chunkType]
		
		if handler then
			--handler = require(handler)
			handler(file, chunk)
		end
		
		table.insert(file.Chunks, chunk)
	end
	
	-- Decompress the zlib stream.
	local success, response = pcall(function ()
		local result = {}
		local index = 0
		
		Deflate:InflateZlib
		{
			Input = BinaryReader.new(file.ZlibStream);
			
			Output = function (byte)
				index = index + 1
				result[index] = string.char(byte)
			end
		}
		
		return table.concat(result)
	end)
	
	if not success then
		error("PNG - Unable to unpack PNG data. " .. tostring(response), 2)
	end
	
	-- Grab expected info from the file.
	
	local width = file.Width
	local height = file.Height
	
	local bitDepth = file.BitDepth
	local colorType = file.ColorType
	
	local buffer = BinaryReader.new(response)
	file.ZlibStream = nil
	
	local bitmap = {}
	file.Bitmap = bitmap
	
	local channels = getBytesPerPixel(colorType)
	file.NumChannels = channels
	
	local bpp = math.max(1, channels * (bitDepth / 8))
	file.BytesPerPixel = bpp
	
	-- Unfilter the buffer and 
	-- load it into the bitmap.
	
	for row = 1, height do	
		local filterType = buffer:ReadByte()
		local scanline = buffer:ReadBytes(width * bpp, true)
		
		bitmap[row] = {}
		
		if filterType == 0 then
			-- None
			Unfilter:None(scanline, bitmap, bpp, row)
		elseif filterType == 1 then
			-- Sub
			Unfilter:Sub(scanline, bitmap, bpp, row)
		elseif filterType == 2 then
			-- Up
			Unfilter:Up(scanline, bitmap, bpp, row)
		elseif filterType == 3 then
			-- Average
			Unfilter:Average(scanline, bitmap, bpp, row)
		elseif filterType == 4 then
			-- Paeth
			Unfilter:Paeth(scanline, bitmap, bpp, row)
		end
	end
	
	return setmetatable(file, PNG)
end

return PNG