How to obtain image size using standard Python class (without using external library)?

I am using Python 2.5. And using the standard classes from Python, I want to determine the image size of a file.

I've heard PIL (Python Image Library), but it requires installation to work.

How might I obtain an image's size without using any external library, just using Python 2.5's own modules?

Note I want to support common image formats, particularly JPG and PNG.

127379 次浏览

While it's possible to call open(filename, 'rb') and check through the binary image headers for the dimensions, it seems much more useful to install PIL and spend your time writing great new software! You gain greater file format support and the reliability that comes from widespread usage. From the PIL documentation, it appears that the code you would need to complete your task would be:

from PIL import Image
im = Image.open('filename.png')
print 'width: %d - height: %d' % im.size # returns (width, height) tuple

As for writing code yourself, I'm not aware of a module in the Python standard library that will do what you want. You'll have to open() the image in binary mode and start decoding it yourself. You can read about the formats at:

Kurts answer needed to be slightly modified to work for me.

First, on ubuntu: sudo apt-get install python-imaging

Then:

from PIL import Image
im=Image.open(filepath)
im.size # (width,height) tuple

Check out the handbook for more info.

If you happen to have ImageMagick installed, then you can use 'identify'. For example, you can call it like this:

path = "//folder/image.jpg"
dim = subprocess.Popen(["identify","-format","\"%w,%h\"",path], stdout=subprocess.PIPE).communicate()[0]
(width, height) = [ int(x) for x in re.sub('[\t\r\n"]', '', dim).split(',') ]

Here's a python 3 script that returns a tuple containing an image height and width for .png, .gif and .jpeg without using any external libraries (ie what Kurt McKee referenced above). Should be relatively easy to transfer it to Python 2.

import struct
import imghdr


def get_image_size(fname):
'''Determine the image type of fhandle and return its size.
from draco'''
with open(fname, 'rb') as fhandle:
head = fhandle.read(24)
if len(head) != 24:
return
if imghdr.what(fname) == 'png':
check = struct.unpack('>i', head[4:8])[0]
if check != 0x0d0a1a0a:
return
width, height = struct.unpack('>ii', head[16:24])
elif imghdr.what(fname) == 'gif':
width, height = struct.unpack('<HH', head[6:10])
elif imghdr.what(fname) == 'jpeg':
try:
fhandle.seek(0) # Read 0xff next
size = 2
ftype = 0
while not 0xc0 <= ftype <= 0xcf:
fhandle.seek(size, 1)
byte = fhandle.read(1)
while ord(byte) == 0xff:
byte = fhandle.read(1)
ftype = ord(byte)
size = struct.unpack('>H', fhandle.read(2))[0] - 2
# We are at a SOFn block
fhandle.seek(1, 1)  # Skip `precision' byte.
height, width = struct.unpack('>HH', fhandle.read(4))
except Exception: #IGNORE:W0703
return
else:
return
return width, height

That code does accomplish 2 things:

  • Getting the image dimension

  • Find the real EOF of a jpg file

Well when googling I was more interest in the later one. The task was to cut out a jpg file from a datastream. Since I I didn't find any way to use Pythons 'image' to a way to get the EOF of so jpg-File I made up this.

Interesting things /changes/notes in this sample:

  • extending the normal Python file class with the method uInt16 making source code better readable and maintainable. Messing around with struct.unpack() quickly makes code to look ugly

  • Replaced read over'uninteresting' areas/chunk with seek

  • Incase you just like to get the dimensions you may remove the line:

    hasChunk = ord(byte) not in range( 0xD0, 0xDA) + [0x00]
    

    ->since that only get's important when reading over the image data chunk and comment in

    #break
    

    to stop reading as soon as the dimension were found. ...but smile what I'm telling - you're the Coder ;)

      import struct
    import io,os
    
    
    class myFile(file):
    
    
    def byte( self ):
    return file.read( self,  1);
    
    
    def uInt16( self ):
    tmp = file.read( self,  2)
    return struct.unpack( ">H", tmp )[0];
    
    
    jpeg = myFile('grafx_ui.s00_\\08521678_Unknown.jpg', 'rb')
    
    
    try:
    height = -1
    width  = -1
    EOI    = -1
    
    
    type_check = jpeg.read(2)
    if type_check != b'\xff\xd8':
    print("Not a JPG")
    
    
    else:
    
    
    byte = jpeg.byte()
    
    
    while byte != b"":
    
    
    while byte != b'\xff': byte = jpeg.byte()
    while byte == b'\xff': byte = jpeg.byte()
    
    
    
    
    # FF D8       SOI Start of Image
    # FF D0..7  RST DRI Define Restart Interval inside CompressedData
    # FF 00           Masked FF inside CompressedData
    # FF D9       EOI End of Image
    # http://en.wikipedia.org/wiki/JPEG#Syntax_and_structure
    hasChunk = ord(byte) not in range( 0xD0, 0xDA) + [0x00]
    if hasChunk:
    ChunkSize   =  jpeg.uInt16()  - 2
    ChunkOffset =  jpeg.tell()
    Next_ChunkOffset = ChunkOffset + ChunkSize
    
    
    
    
    # Find bytes \xFF \xC0..C3 That marks the Start of Frame
    if (byte >= b'\xC0' and byte <= b'\xC3'):
    
    
    # Found  SOF1..3 data chunk - Read it and quit
    jpeg.seek(1, os.SEEK_CUR)
    h = jpeg.uInt16()
    w = jpeg.uInt16()
    
    
    
    
    #break
    
    
    
    
    elif (byte == b'\xD9'):
    # Found End of Image
    EOI = jpeg.tell()
    break
    else:
    # Seek to next data chunk
    print "Pos: %.4x %x" % (jpeg.tell(), ChunkSize)
    
    
    if hasChunk:
    jpeg.seek(Next_ChunkOffset)
    
    
    byte = jpeg.byte()
    
    
    width  = int(w)
    height = int(h)
    
    
    print("Width: %s, Height: %s  JpgFileDataSize: %x" % (width, height, EOI))
    finally:
    jpeg.close()
    

Here's a way to get dimensions of a png file without needing a third-party module. From http://coreygoldberg.blogspot.com/2013/01/python-verify-png-file-and-get-image.html

import struct


def get_image_info(data):
if is_png(data):
w, h = struct.unpack('>LL', data[16:24])
width = int(w)
height = int(h)
else:
raise Exception('not a png image')
return width, height


def is_png(data):
return (data[:8] == '\211PNG\r\n\032\n'and (data[12:16] == 'IHDR'))


if __name__ == '__main__':
with open('foo.png', 'rb') as f:
data = f.read()


print is_png(data)
print get_image_info(data)

When you run this, it will return:

True
(x, y)

And another example that includes handling of JPEGs as well: http://markasread.net/post/17551554979/get-image-size-info-using-pure-python-code

Stumbled upon this one but you can get it by using the following as long as you import numpy.

import numpy as np


[y, x] = np.shape(img[:,:,0])

It works because you ignore all but one color and then the image is just 2D so shape tells you how bid it is. Still kinda new to Python but seems like a simple way to do it.

Regarding Fred the Fantastic's answer:

Not every JPEG marker between C0-CF are SOF markers; I excluded DHT (C4), DNL (C8) and DAC (CC). Note that I haven't looked into whether it is even possible to parse any frames other than C0 and C2 in this manner. However, the other ones seem to be fairly rare (I personally haven't encountered any other than C0 and C2).

Either way, this solves the problem mentioned in comments by Malandy with Bangles.jpg (DHT erroneously parsed as SOF).

The other problem mentioned with 1431588037-WgsI3vK.jpg is due to imghdr only being able detect the APP0 (EXIF) and APP1 (JFIF) headers.

This can be fixed by adding a more lax test to imghdr (e.g. simply FFD8 or maybe FFD8FF?) or something much more complex (possibly even data validation). With a more complex approach I've only found issues with: APP14 (FFEE) (Adobe); the first marker being DQT (FFDB); and APP2 and issues with embedded ICC_PROFILEs.

Revised code below, also altered the call to imghdr.what() slightly:

import struct
import imghdr


def test_jpeg(h, f):
# SOI APP2 + ICC_PROFILE
if h[0:4] == '\xff\xd8\xff\xe2' and h[6:17] == b'ICC_PROFILE':
print "A"
return 'jpeg'
# SOI APP14 + Adobe
if h[0:4] == '\xff\xd8\xff\xee' and h[6:11] == b'Adobe':
return 'jpeg'
# SOI DQT
if h[0:4] == '\xff\xd8\xff\xdb':
return 'jpeg'
imghdr.tests.append(test_jpeg)


def get_image_size(fname):
'''Determine the image type of fhandle and return its size.
from draco'''
with open(fname, 'rb') as fhandle:
head = fhandle.read(24)
if len(head) != 24:
return
what = imghdr.what(None, head)
if what == 'png':
check = struct.unpack('>i', head[4:8])[0]
if check != 0x0d0a1a0a:
return
width, height = struct.unpack('>ii', head[16:24])
elif what == 'gif':
width, height = struct.unpack('<HH', head[6:10])
elif what == 'jpeg':
try:
fhandle.seek(0) # Read 0xff next
size = 2
ftype = 0
while not 0xc0 <= ftype <= 0xcf or ftype in (0xc4, 0xc8, 0xcc):
fhandle.seek(size, 1)
byte = fhandle.read(1)
while ord(byte) == 0xff:
byte = fhandle.read(1)
ftype = ord(byte)
size = struct.unpack('>H', fhandle.read(2))[0] - 2
# We are at a SOFn block
fhandle.seek(1, 1)  # Skip `precision' byte.
height, width = struct.unpack('>HH', fhandle.read(4))
except Exception: #IGNORE:W0703
return
else:
return
return width, height

Note: Created a full answer instead of a comment, since I'm not yet allowed to.

Found a nice solution in another Stackoverflow post (using only standard libraries + dealing with jpg as well): JohnTESlade answer

And another solution (the quick way) for those who can afford running 'file' command within python, run:

import os
info = os.popen("file foo.jpg").read()
print info

Output:

foo.jpg: JPEG image data...density 28x28, segment length 16, baseline, precision 8, 352x198, frames 3

All you gotta do now is to format the output to capture the dimensions. 352x198 in my case.

It depends on the output of file which I am not sure is standardized on all systems. Some JPEGs don't report the image size

import subprocess, re
image_size = list(map(int, re.findall('(\d+)x(\d+)', subprocess.getoutput("file" + filename))[-1]))