Portable Network Graphics Parser

lua-users home
wiki

This is a simple example of parsing portable network graphics (PNG) files [1][2] using pure Lua code (no C code like libpng [3] or zlib [4][5]). In its current form, it does not fully decode PNG files but rather only the top-level structure, including metadata. A full implementation would need to implement zlib [4] too (see ModuleCompressDeflateLua). Extend if you wish.

-- pngparse.lua

-- Simple example of parsing the main sections of a PNG file.

--

-- This is mostly just an example.  Not intended to be complete,

-- robust, modular, or well tested.

--

-- (c) 2008 David Manura. Licensed under the same terms as Lua (MIT license).





-- Unpack 32-bit unsigned integer (most-significant-byte, MSB, first)

-- from byte string.

local function unpack_msb_uint32(s)

  local a,b,c,d = s:byte(1,#s)

  local num = (((a*256) + b) * 256 + c) * 256 + d

  return num

end



-- Read 32-bit unsigned integer (most-significant-byte, MSB, first) from file.

local function read_msb_uint32(fh)

  return unpack_msb_uint32(fh:read(4))

end



-- Read unsigned byte (integer) from file

local function read_byte(fh)

  return fh:read(1):byte()

end





local function parse_zlib(fh, len)

  local byte1 = read_byte(fh)

  local byte2 = read_byte(fh)



  local compression_method = byte1 % 16

  local compression_info = math.floor(byte1 / 16)



  local fcheck = byte2 % 32

  local fdict = math.floor(byte2 / 32) % 1

  local flevel = math.floor(byte2 / 64)



  print("compression_method=", compression_method)

  print("compression_info=", compression_info)

  print("fcheck=", fcheck)

  print("fdict=", fdict)

  print("flevel=", flevel)



  fh:read(len - 6)

  print("(deflate data not displayed)")



  local checksum = read_msb_uint32(fh)

  print("checksum=", checksum)

end



local function parse_IHDR(fh, len)

  assert(len == 13, 'format error')

  local width = read_msb_uint32(fh)

  local height = read_msb_uint32(fh)

  local bit_depth = read_byte(fh)

  local color_type = read_byte(fh)

  local compression_method = read_byte(fh)

  local filter_method = read_byte(fh)

  local interlace_method = read_byte(fh)



  print("width=", width)

  print("height=", height)

  print("bit_depth=", bit_depth)

  print("color_type=", color_type)

  print("compression_method=", compression_method)

  print("filter_method=", filter_method)

  print("interlace_method=", interlace_method)



  return compression_method

end



local function parse_sRGB(fh, len)

  assert(len == 1, 'format error')

  local rendering_intent = read_byte(fh)

  print("rendering_intent=", rendering_intent)

end



local function parse_gAMA(fh, len)

  assert(len == 4, 'format error')

  local rendering_intent = read_msb_uint32(fh)

  print("rendering_intent=", rendering_intent)

end



local function parse_cHRM(fh, len)

  assert(len == 32, 'format error')



  local white_x = read_msb_uint32(fh)

  local white_y = read_msb_uint32(fh)

  local red_x = read_msb_uint32(fh)

  local red_y = read_msb_uint32(fh)

  local green_x = read_msb_uint32(fh)

  local green_y = read_msb_uint32(fh)

  local blue_x = read_msb_uint32(fh)

  local blue_y = read_msb_uint32(fh)

  print('white_x=', white_x)

  print('white_y=', white_y)

  print('red_x=', red_x)

  print('red_y=', red_y)

  print('green_x=', green_x)

  print('green_y=', green_y)

  print('blue_x=', blue_x)

  print('blue_y=', blue_y)

end



local function parse_IDAT(fh, len, compression_method)

  if compression_method == 0 then

    -- fh:read(len)

    parse_zlib(fh, len)

  else

    print('(unrecognized compression method)')

  end  

end



local function parse_png(fh)

  -- parse PNG header

  local bytes = fh:read(8)

  local expect = "\137\080\078\071\013\010\026\010"

  if bytes ~= expect then

    error 'not a PNG file'

  end



  -- parse chunks

  local compression_method

  while 1 do

    local len = read_msb_uint32(fh)

    local stype = fh:read(4)

    print("chunk:", "type=", stype, "len=", len)



    if stype == 'IHDR' then

      compression_method = parse_IHDR(fh, len)

    elseif stype == 'sRGB' then

      parse_sRGB(fh, len)

    elseif stype == 'gAMA' then

      parse_gAMA(fh, len)

    elseif stype == 'cHRM' then

      parse_cHRM(fh, len)

    elseif stype == 'IDAT' then

      parse_IDAT(fh, len, compression_method)

    else

      local data = fh:read(len)

      print("data=", len == 0 and "(empty)" or "(not displayed)")

    end



    local crc = read_msb_uint32(fh)

    print("crc=", crc)



    if stype == 'IEND' then

      break

    end

  end

end



local filename = arg[1]



if not filename then

  io.stderr:write("usage: lua pngparse.lua <filename>")

  os.exit(1)

end



local fh = assert(io.open(filename, 'rb'))

parse_png(fh)

Example output:


$ lua pngparse.lua myfile.png

chunk:  type=   IHDR    len=    13

width=  676

height= 647

bit_depth=      8

color_type=     2

compression_method=     0

filter_method=  0

interlace_method=       0

crc=    3625766246

chunk:  type=   sRGB    len=    1

rendering_intent=       0

crc=    2932743401

chunk:  type=   gAMA    len=    4

rendering_intent=       45455

crc=    201089285

chunk:  type=   cHRM    len=    32

white_x=        31270

white_y=        32900

red_x=  64000

red_y=  33000

green_x=        30000

green_y=        60000

blue_x= 15000

blue_y= 6000

crc=    2629456188

chunk:  type=   IDAT    len=    60975

compression_method=     8

compression_info=       7

fcheck= 30

fdict=  0

flevel= 1

(deflate data not displayed)

checksum=       2411816191

crc=    1804717936

chunk:  type=   IEND    len=    0

data=   (empty)

crc=    2923585666

--DavidManura


RecentChanges · preferences
edit · history
Last edited May 2, 2009 1:32 am GMT (diff)