PngLib - Extract data from png files (width, height, color depth etc)

Png lib is a lua library that is useful for grabbing info from png files directly. This has a main use of automatically grabbing your base images width/height when using display.newImageRect for instance.

It returns the following properties from a png file (it gets the data directly from the png file, it doesn’t load the image using display.newImage first, its pure i/o)

width
height
bitDepth
colorType
filterMethod
interlaceMethod

Sample usage:

pngLib.getPngInfo(“filename.png”, path)

first parameter is the png file you want to get info from, the second parameter is the path. By default it is set to system.ResourceDirectory if no path is passed to the function.
Download it from the link below, sample is included and usage instructions
http://www.infuseddreams.com/apps/pngLib.zip [import]uid: 84637 topic_id: 26389 reply_id: 326389[/import]

Sounds great, pretty much exactly what I was looking for. However one quick, most likely stupid, question. I’m assuming it only works with png’s? [import]uid: 119420 topic_id: 26389 reply_id: 107005[/import]

At the moment yes, if there is enough interest I could extend it to support jpeg files also [import]uid: 84637 topic_id: 26389 reply_id: 107008[/import]

@danny: cool stuff!

EDIT: dl link not working… [import]uid: 90610 topic_id: 26389 reply_id: 107009[/import]

Fixed the download link, sorry about that [import]uid: 84637 topic_id: 26389 reply_id: 107010[/import]

np, thanks for fixing! [import]uid: 90610 topic_id: 26389 reply_id: 107011[/import]

That’s great then! I wasn’t sure if there was a technical limitation or just a time limitation. Even with just PNG alone this will be fantastically helpful in our current project so thank you very much for creating and sharing!

Will give it a test now :slight_smile: [import]uid: 119420 topic_id: 26389 reply_id: 107013[/import]

@All your very welcome. Please let me know if you have any issues with it [import]uid: 84637 topic_id: 26389 reply_id: 107014[/import]

In and working perfectly, no issues so far :slight_smile: [import]uid: 119420 topic_id: 26389 reply_id: 107018[/import]

Glad to hear it ! [import]uid: 84637 topic_id: 26389 reply_id: 107019[/import]

Hey Danny, am I seeing things incorrectly or does the getPngInfo function not actually close the file? The code I downloaded does ‘return self’ after reading the IHDR chunk, which seems to skip the cleanup code.

EDIT

If interested, I added a couple sanity checks in my local implementation. Thanks by the way.

[lua]function pngLib.getPngInfo(fh, path)
local filePath = path or nil
local theFile = nil

if filePath == nil then
theFile = system.pathForFile(fh, system.ResourceDirectory)
else
theFile = system.pathForFile(fh, path)
end

local fileHandle = assert(io.open(theFile, ‘rb’))

local self = {}

local fileHeader = fileHandle:read(4)
assert( fileHeader == string.char(137)…“PNG”, “pngLib only works on PNG files” )

while 1 do
local stype = fileHandle:read(4)
if stype == ‘IHDR’ then
self.width = readMsbUint32(fileHandle)
self.height = readMsbUint32(fileHandle)
self.bitDepth = readByte(fileHandle)
self.colorType = readByte(fileHandle)
self.filterMethod = readByte(fileHandle)
self.interlaceMethod = readByte(fileHandle)

break
end

assert( stype ~= nil, “Reached EOF without finding PNG IHDR chunk” )
end

–close the file
io.close(fileHandle)
fileHandle = nil
return self
end[/lua] [import]uid: 44647 topic_id: 26389 reply_id: 107044[/import]

Thanks toby, you were correct about the file not being closed, i have fixed that issue and also incorporated your changes.

I have updated the original zip file so the download link is the same as the one in the first post [import]uid: 84637 topic_id: 26389 reply_id: 107058[/import]

Hi everybody,

Does anyone know how to implenet this with the JPG files?
Otherwise it works great, the only problem is that you have to write too much .png in the script, which is not practical.

regards [import]uid: 8303 topic_id: 26389 reply_id: 107165[/import]

“you have to write too much .png in the script, which is not practical.”

Is that a real complaint?

Extending this to support JPG shouldn’t be tough to do, the relevant information is in a Start-Of-Frame segment. The building blocks are all here. [import]uid: 44647 topic_id: 26389 reply_id: 107200[/import]

@toby the jpeg data seems to be a little more varied than png data from my testing.

Your function will fail on certain jpeg files and give incorrect dimensions on others.

I am working on implementing this and will hopefully have a new version out soon. [import]uid: 84637 topic_id: 26389 reply_id: 107283[/import]

Just for fun, I whipped up this extra function for jpegs. It’s a little more pedantic than you might need, and I admit I only tested it on one file. It returns a table with bitDepth, width, and height. You can paste it into Danny’s module, though then the name pngLib loses some of its honesty.

[lua]function pngLib.getJpegInfo(fh, path)
local function unpackMsbUint16(s)
local c,d = s:byte(1,#s)
local num = (c * 256) + d
return num
end

local function readMsbUint16(fh)
return unpackMsbUint16(fh:read(2))
end

local filePath = path or nil
local theFile = nil

if filePath == nil then
theFile = system.pathForFile(fh, system.ResourceDirectory)
else
theFile = system.pathForFile(fh, path)
end

local fileHandle = assert(io.open(theFile, ‘rb’))

local self = {}

–JPEG Markers
local jpegSOI = string.char( 255 ) … string.char( 216 )
local jpegAPP0 = string.char( 255 ) … string.char( 224 )
local jpegSOF0 = string.char( 255 ) … string.char( 192 )
local jpegSOF2 = string.char( 255 ) … string.char( 194 )

local tempBytes

repeat
tempBytes = fileHandle:read(2)
until ( ( tempBytes == jpegSOI ) or ( tempBytes == nil ) )
assert( tempBytes~=nil, “SOI not found. Are you sure this is a JPEG file?” )

local APP0Marker = fileHandle:read(2)
assert( APP0Marker == jpegAPP0, “APP0 Marker not what’s expected. Is this really a JPEG file?”)

local APP0Length = readMsbUint16(fileHandle) - 4 --we’re 4 bytes into this thing already
fileHandle:read( APP0Length ) – skipping ahead, but this is where the DPI is stored if you need it.

repeat
tempBytes = fileHandle:read(2)
until ( ( tempBytes == jpegSOF0 ) or ( tempBytes == jpegSOF2 ) or ( tempBytes == nil ) )
assert( tempBytes ~= nil, “Start of Frame not found. I give up.”)

if tempBytes == jpegSOF0 or tempBytes == jpegSOF2 then
fileHandle:read(2) – length of frame
self.bitDepth = readByte(fileHandle) – bit depth
self.height = readMsbUint16(fileHandle) – image height
self.width = readMsbUint16(fileHandle) – image width
end

–close the file
io.close(fileHandle)
fileHandle = nil
return self
end[/lua] [import]uid: 44647 topic_id: 26389 reply_id: 107226[/import]

JPEGs can be wacky. Not nearly as deterministic as PNGs. There can be embedded thumbnails, EXIF data, and other segments in there. “Incorrect dimensions” results are probably hitting the SOF for a thumbnail and returning those dimensions. Not sure what would make it fail entirely though – I think a JPEG is required to at least have the markers the function looks for.

EDIT: Oh, huh. On line 49 it should be checking for jpegSOF2 for progressive-scan files. whoops. [import]uid: 44647 topic_id: 26389 reply_id: 107325[/import]

Hi everybody

I found some interesting resources on the google for someone to digg into, i am more designer kind a head.

http://lua-users.org/wiki/PortableNetworkGraphicsParser

http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html

happy digging :wink: [import]uid: 8303 topic_id: 26389 reply_id: 107355[/import]

Here’s a modified version of my jpeg function. It’s a lot less elegant and efficient than the png function; you can make fewer assumptions about jpegs. I tested it on a progressive-scan JPEG with a n embedded thumbnail, and there’s a print statement in there that tells you about the SOF segments it encounters. Unfortunately, it reads the entire file looking for SOFn segments before settling on the last one it encountered.

[lua]function pngLib.getJpegInfo(fh, path)
local function unpackMsbUint16(s)
local c,d = s:byte(1,#s)
local num = (c * 256) + d
return num
end

local function readMsbUint16(fh)
return unpackMsbUint16(fh:read(2))
end

local filePath = path or nil
local theFile = nil

if filePath == nil then
theFile = system.pathForFile(fh, system.ResourceDirectory)
else
theFile = system.pathForFile(fh, path)
end

local fileHandle = assert(io.open(theFile, ‘rb’))

local self = {}

–JPEG Markers
local jpegSOI = string.char( 255 ) … string.char( 216 )
local jpegAPP0 = string.char( 255 ) … string.char( 224 )
local jpegSOF0 = string.char( 255 ) … string.char( 192 )
local jpegSOF2 = string.char( 255 ) … string.char( 194 )

local tempBytes

repeat
tempBytes = fileHandle:read(2)
until ( ( tempBytes == jpegSOI ) or ( tempBytes == nil ) )
assert( tempBytes~=nil, “SOI not found. Are you sure this is a JPEG file?” )

local APP0Marker = fileHandle:read(2)
assert( APP0Marker == jpegAPP0, “APP0 Marker not what’s expected. Is this really a JPEG file?”)

local APP0Length = readMsbUint16(fileHandle) - 4 --we’re 4 bytes into this thing already
fileHandle:seek( “cur”, APP0Length ) – skipping ahead, but this is where the DPI is stored if you need it.

local readHead

repeat
repeat
tempBytes = fileHandle:read(2)
until ( ( tempBytes == jpegSOF0 ) or ( tempBytes == jpegSOF2 ) or ( tempBytes == nil ) )
if tempBytes ~= nil then
readHead = fileHandle:seek()
print(“Found SOF0:”, tempBytes == jpegSOF0, “SOF2:”, tempBytes == jpegSOF2)
end
until tempBytes == nil

if readHead then
fileHandle:seek(“set”, readHead)
fileHandle:read(2) – length of frame
self.bitDepth = readByte(fileHandle) – bit depth
self.height = readMsbUint16(fileHandle) – image height
self.width = readMsbUint16(fileHandle) – image width
end

–close the file
io.close(fileHandle)
fileHandle = nil
return self
end[/lua] [import]uid: 44647 topic_id: 26389 reply_id: 107448[/import]

@toby, thanks for the continued effort on this, im still working on my version also but am achieving similar issues, that version still fails on a lot of jpegs. It seems from my testing, jpegs that are saved with photoshop are the main culprits that fail [import]uid: 84637 topic_id: 26389 reply_id: 107465[/import]