retrieve / find out an image's dimensions

jigloo phus at live.com
Sat Jun 16 19:28:18 CEST 2007


On 6 16 ,   6 27 , Adam Teale <a... at lumanation.com> wrote:
> hey guys
>
> Is there a builtin/standard install method in python for retrieving or
> finding out an image's dimensions?
Sorry, after i review these code in http://www.pycode.com/modules/?id=32,
i found some(not just a few) *BUGS* in it.
I must apologize to you for this.
I have rewrite the moudle.
please have a try.


"""Recognize image file formats and size based on their first few
bytes."""
# Perl Image::Size module clone
# see more http://search.cpan.org/author/RJRAY/Image-Size-3.01/lib/Image/Size.pm
# rewrited by jigloo(phus at live.com)
# GPL-2 license

__all__ = ["what", "imgsz", "size"]

import os		   # for os.path os.error sys.stderr
import StringIO    # for StringIO.StringIO
import struct      # for unpack
import re          # for regex

# jpegsize: gets the width and height (in pixels) of a jpeg file
#
def jpegsize(stream):
	(x, y, error) = (None, None, "could not determine JPEG size")

	# Dummy read to skip header ID
	stream.read(2)
	while True:
		# Extract the segment header.
		(marker, code, length) = struct.unpack("!BBH", stream.read(4))

		# Verify that it's a valid segment.
		if marker != 0xFF:
			# Was it there?
			error = "JPEG marker not found"
			break
		elif code >= 0xC0 and code <= 0xC3:
			# Segments that contain size info
			(y, x) = struct.unpack("!xHH", stream.read(5))
			error = "no error"
			break
		else:
			# Dummy read to skip over data
			stream.read(length - 2)

	return ("JPEG", x, y, error)


# bmpsize: size a Windows-ish BitMaP image
#
def bmpsize(stream):
	(x, y, error) = (None, None, "Unable to determine size of BMP data")

	# Dummy read to skip header data
	stream.read(18)
	(x, y) = struct.unpack("<LL", stream.read(8))
	if x > 0 and y > 0:
		error = "no error"

	return ("BMP", x, y, error)


# pngsize : gets the width & height (in pixels) of a png file
# cor this program is on the cutting edge of technology! (pity it's
blunt!)
#
def pngsize(stream):
	(x, y, error) = (None, None, "could not determine PNG size")

	# Dummy read to skip header data
	stream.read(12)
	if stream.read(4) == "IHDR":
		(x, y) = struct.unpack("!LL", stream.read(8))
		error = "no error"

	return ("PNG", x, y, error)

# gifsize : Subroutine gets the size of the specified GIF
#
# Default behavior for GIFs is to return the "screen" size
GIF_BEHAVIOR = 0
#
def gifsize(stream):

	if GIF_BEHAVIOR > 2:
		return ("GIF", 0, 0, "Out-of-range value for GIF_BEHAVIOR: %d" %
GIF_BEHAVIOR)

	# Skip over the identifying string, since we already know this is a
GIF
	type = stream.read(6)
	buf = stream.read(5)
	if len(buf) != 5:
		return ("GIF", 0, 0, "Invalid/Corrupted GIF (bad header)")
	(sw, sh, x) = struct.unpack("<HHB", buf)
	if GIF_BEHAVIOR == 0:
		return ("GIF", sw, sh, "no error")

	return ("GIF", None, None, "Invalid/Corrupted GIF (missing image
header?)")

# ppmsize :  gets data on the PPM/PGM/PBM family.
#
def ppmsize(stream):
	(mime, x, y, error) = ("PPM", None, None, "Unable to determine size
of PPM/PGM/PBM data")

	header = stream.read(1024)
	# PPM file of some sort
	re.sub(r"^\#.*", "", header, re.M)
	m = re.match(r"^(P[1-6])\s+(\d+)\s+(\d+)", header, re.S)
	if m:
		(n, x, y) = m.group(1, 2, 3)
		mime = {"P1":"PBM", "P2":"PGM", "P3":"PPM", "P4":"BPM", "P5":"PGM",
"P6":"PPM"}[n]
		if n == "P7":
			mime = "XV"
			m = re.match(r"IMGINFO:(\d+)x(\d+)", header, re.S)
			if m:
				(x, y) = m.group(1, 2)
		error = "no error"
		return (mime, int(x), int(y), error)
	else:
		return (mime, x, y, error)

# xbmsize :
#
def xbmsize(stream):
	(x, y, error) = (None, None, "could not determine XBM size")

	header = stream.read(1024)

	m = re.match(r"^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)",
header, re.S|re.I)
	if m:
		(x, y) = m.group(1, 2)
		error = "no error"
		return ("XBM", int(x), int(y), error)
	else:
		return ("XBM", x, y, error)


# Size an XPM file by looking for the "X Y N W" line, where X and Y
are
# dimensions, N is the total number of colors defined, and W is the
width of
# a color in the ASCII representation, in characters. We only care
about X & Y.
def xpmsize(stream):
	(x, y, error) = (None, None, "could not determine XPM size")

	while True:
		line = stream.readline()
		if line == "":
			break
		m = re.compile(r"\s*(\d+)\s+(\d+)(\s+\d+\s+\d+){1,2}\s*",
re.M).match(line, 1)
		if m:
			(x, y) = map(lambda x: int(x), m.group(1, 2))
			error = "no error"
			break

	return ("XPM", x, y, error)


# tiffsize : size a TIFF image
#
def tiffsize(stream):
	(x, y, error) = (None, None, "Unable to determine size of TIFF data")

	be = "!"           # Default to big-endian; I like it better
	if stream.read(4) == "II\x2a\x00": # little-endian
		be = "<"

	# Set up an association between data types and their corresponding
    # pack/unpack specification.  Don't take any special pains to deal
with
    # signed numbers; treat them as unsigned because none of the image
    # dimensions should ever be negative.  (I hope.)
	packspec = [ None,      # nothing (shouldn't happen)
			     "Bxxx",    # BYTE (8-bit unsigned integer)
				 None,      # ASCII
				 be+"Hxx",  # SHORT (16-bit unsigned integer)
				 be+"L",    # LONG (32-bit unsigned integer)
				 None,      # RATIONAL
				 "bxxx",    # SBYTE (8-bit signed integer)
				 None,      # UNDEFINED
				 be+"Hxx",  # SSHORT (16-bit unsigned integer)
				 be+"L"     # SLONG (32-bit unsigned integer)
				 ]

	offset = struct.unpack(be+"L", stream.read(4))[0] # Get offset to IFD

	ifd = stream.read(2) # Get number of directory entries
	num_dirent = struct.unpack(be+"H", ifd)[0] # Make it useful
	num_dirent = offset + (num_dirent * 12) # Calc. maximum offset of IFD

	# Do all the work
	ifd = ''
	tag = 0
	type = 0
	while x is None or y is None:
		ifd = stream.read(12)  # Get first directory entry
		if ifd == "" or stream.tell() > num_dirent:
			break
		tag = struct.unpack(be+"H", ifd[:2])[0] # ...and decode its tag
		type = struct.unpack(be+"H", ifd[2:2+2])[0] # ...and the data type
		# Check the type for sanity.
		if type > len(packspec) or packspec[type] is None:
			continue
		if tag == 0x0100: # ImageWidth (x)
			# Decode the value
			x = struct.unpack(packspec[type], ifd[8:4+8])[0]
		elif tag == 0x0101: # ImageLength (y)
			# Decode the value
			y = struct.unpack(packspec[type], ifd[8:4+8])[0]

	# Decide if we were successful or not
	if x and y:
		error = "no error"
	else:
		error = ""
		if x is None:
			error = "ImageWidth "
		if y is None:
			if error != "":
				error = error + "and "
			error = error + "ImageWidth "
		error = error + "tag(s) could not be found"

	return ("TIFF", x, y, error)

# psdsize : determine the size of a PhotoShop save-file (*.PSD)
#
def psdsize(stream):
	(x, y, error) = (None, None, "could not determine PSD size")

	stream.read(14)
	(y, x) = struct.unpack("!LL", stream.read(8))
	if x > 0 and y > 0:
		error = "no error"
	return ("PSD", x, y, error)

# pcdsize :
# Kodak photo-CDs are weird. Don't ask me why, you really don't want
details.
PCD_MAP = { "base/16" : [ 192,  128  ],
			"base/4"  : [ 384,  256  ],
			"base"    : [ 768,  512  ],
			"base4"	  : [ 1536, 1024 ],
			"base16"  : [ 3072, 2048 ],
			"base64"  : [ 6144, 4096 ]}
# Default scale for PCD images
PCD_SCALE = "base";
#
def pcdsize(stream):
	(x, y, error) = (None, None, "Unable to determine size of PCD data")

	buff = strean.read(0xf00)
	if buff[0x800:3+0x800] != "PCD":
		error = "Invalid/Corrupted PCD (bad header)"
		return ("PCD", x, y, error)

	orient = ord(buff[0x0e02:1+0x0e02]) & 1 # Clear down to one bit
	if orient:
		(x, y) = PCD_MAP[PCD_SCALE]
	else:
		(y, x) = PCD_MAP[PCD_SCALE]
	error = "no error"
	return ("PCD", x, y, error)


# swfsize :
#
def swfsize(stream):
	(x, y, error) = (None, None, "not implemented. --I hate swf :(")

	return ("SWF", x, y, error)

# swfmxsize :
#
def swfmxsize(stream):
	(x, y, error) = (None, None, "not implemented. --I hate swf :(")

	return ("SWF", x, y, error)

# mngsize : gets the width and height (in pixels) of an MNG file.
#
# Basically a copy of pngsize.
def mngsize(stream):
	(x, y, error) = (None, None, "could not determine MNG size")

	stream.read(12)
	if stream.read(4) == "MHDR":
		# MHDR = Image Header
		(x, y) = struct.unpack("!LL", stream.read(8))
		error = "MNG"
	else:
		error = "Invalid/Corrupted MNG (bad header)"

	return ("MNG", x, y, error)

# type_map used in function type_map_match
type_map = { re.compile(r"^\xFF\xD8")					: ["JPEG", jpegsize],
			 re.compile(r"^BM")          				: ["BMP",  bmpsize],
			 re.compile(r"^\x89PNG\x0d\x0a\x1a\x0a")	: ["PNG",  pngsize],
			 re.compile(r"^P[1-7]")						: ["PPM",  ppmsize], # also XVpics
			 re.compile(r"\#define\s+\S+\s+\d+")		: ["XBM",  xbmsize],
			 re.compile(r"\/\* XPM \*\/")            	: ["XPM",  xpmsize],
			 re.compile(r"^MM\x00\x2a")					: ["TIFF", tiffsize],
			 re.compile(r"^II\x2a\x00")					: ["TIFF", tiffsize],
			 re.compile(r"^8BPS")						: ["PSD",  psdsize],
			 re.compile(r"^PCD_OPA")					: ["PCD",  pcdsize],
			 re.compile(r"^FWS")						: ["SWF",  swfsize],
			 re.compile(r"^CWS")						: ["SWF",  swfmxsize],
			 re.compile(r"^\x8aMNG\x0d\x0a\x1a\x0a")	: ["MNG",  mngsize],
			 re.compile(r"^GIF8[7,9]a")              	: ["GIF",  gifsize]}
# type_map_match to get MIME-TYPE and callback function
def type_map_match(buffer):
	for rx in type_map.keys():
		if rx.match(buffer):
			return type_map[rx]
	else:
		return None

# Recognize image headers
#
def what(filename):
	try:
		f = open(filename, "rb")
		h = f.read(512)
	except IOError:
		print "IOError %s\n" % os.error
	finally:
		if f: f.close()
	m = type_map_match(h)
	if m:
		return m[0]
	return None


# size: size a image from buffer
#
def size(buffer):
	m = type_map_match(buffer)
	if m:
		return (m[1])(StringIO.StringIO(buffer))
	else:
		return (None, None, None, "Unable to Recognize image file format")

# imgsz: size a image by file name
#
def imgsz(path):
	(type, x, y, error) = (None, None, None, "Unable to Recognize image
file format")
	f = None
	try:
		f = open(path, "rb")
		header = f.read(256)
		f.seek(0)
		m = type_map_match(header)
		if m:
			(type, x, y, error) = (m[1])(f)
	except:
		print "IOError %s\n" % os.error
		return None
	finally:
		if f: f.close()

	return (type, x, y, error)

if __name__ == "__main__":
	for filename in [f for f in os.listdir(".") if os.path.isfile(f)]:
		print filename, imgsz(filename)

>
> A quick google found me this:http://www.pythonware.com/library/pil/handbook/introduction.htm
>
> but it looks like it is something I will need to install - I'd like to
> be able to pass my script around to people without them needing any
> additional modules
>
> Any help would be fantastic!
>
> Cheers
>
> Adam
> python 2.3.5
> osx 10.4.9





More information about the Python-list mailing list