82

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
eros
  • 4,946
  • 18
  • 53
  • 78

10 Answers10

100

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 (i.e., what Kurt McKee referenced). It 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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Fred the Fantastic
  • 1,295
  • 1
  • 9
  • 11
  • 1
    Your code worked mostly like that in 2.7.3. I had to rewrite it because I had already a file like object. – xZise Feb 12 '14 at 22:12
  • 1
    It seems to fail with [this](http://www.sleeplessdomain.com/comics/1431588037-WgsI3vK.jpg). – Malady Dec 04 '15 at 14:18
  • 1
    And with [this.](http://mylittlewiki.org/w/images/7/71/Bangles.jpg), which should return (640,480), but I get (1281, 1). – Malady Dec 06 '15 at 15:44
  • I tested only the PNG portion of this, but that at least works nicely. – tremby Apr 20 '16 at 21:34
  • With the few jpg files I have been testing this it worked fine. But I got no size information with tif files. – Purrrple Dec 22 '16 at 11:50
  • The code does not load width and height of some JPEGs correctly. It seems that @Dagh Bunnstad fixed problems. Look at his answer. – JiriHnidek Jan 03 '17 at 21:45
  • 1
    the image @Malandy provided is a **baseline DCT JEPG** image, not a ICC/IPTC/JFIF compatible image. – Mitoxys May 10 '18 at 07:11
  • This code is nice, but it's a pain for `fhandle` which don't support `seek` (like network responses). And it is not really needed, since we just read the first 24 bytes just to check the header size... – math2001 Nov 16 '19 at 02:50
68

Kurt's 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 information.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tjb
  • 11,480
  • 9
  • 70
  • 91
  • 21
    Doesn't answer the question - "(without using external library)?" is specified in the title, and the question then clarifies with "I've heard PIL (Python Image Library), but it requires to install the library." – Luna Jul 16 '14 at 22:00
  • 18
    @RossAllan: Sure, but this question is #1 on Google for variants of `Python Image dimensions`, so +1 from me for a no-reinventing-of-the-wheel-needed answer :) – Clément Mar 01 '16 at 07:20
20

Here's a way to get dimensions of a PNG file without needing a third-party module. From Python - verify a PNG file and get image dimensions:

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
cbautista
  • 330
  • 3
  • 6
  • 1
    Isn't it a little inefficient to read the entire image data if all you need is the header data? – Adam Parkin May 25 '15 at 02:46
  • 2
    To get around that, one can refactor ```get_image_info()``` to take the filename as a parameter (rather than the binary data), and then just do a ```f.read(25)``` to read the header info only. – Adam Parkin May 25 '15 at 03:23
  • The last link is broken: *"Oops! That page can’t be found."* – Peter Mortensen Feb 01 '23 at 20:13
18

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:

hamidfzm
  • 4,595
  • 8
  • 48
  • 80
Kurt McKee
  • 1,410
  • 13
  • 17
  • 3
    +1 for the file format documentation, but my direction is not using of external library just to get the image size of png & jpg image file. – eros Nov 07 '11 at 06:22
  • 4
    You need `Image.open` not just `Image` per tjb's answer. – Ghopper21 Mar 03 '13 at 15:05
10

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.

Community
  • 1
  • 1
Dagh Bunnstad
  • 189
  • 2
  • 5
5

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(',') ]
jensph
  • 763
  • 1
  • 10
  • 22
  • 2
    This is a good idea, but no need to invoke the regex machinery or list comprehensions: `width, height = list( map( int, dim.decode('utf-8').strip('"').split(',')))` – Giacomo Lacava Aug 25 '18 at 21:56
4

I found a nice solution in another Stack Overflow post (using only standard libraries + dealing with JPEG as well): JohnTESlade answer

And another solution (the quick way) for those who can afford running the 'file' command within the Python interpreter, 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 have got to do now is to format the output to capture the dimensions. 352x198 in my case.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mercury
  • 7,430
  • 3
  • 42
  • 54
1

That code does accomplish two things:

  • Getting the image dimension

  • Find the real EOF of a JPEG file

Well, when googling, I was more interested in the latter one. The task was to cut out a JPEG file from a data stream. Since I didn't find any way to use Pythons 'image' to a way to get the EOF of so a JPEG file, I made up this.

Interesting things /changes/notes in this sample:

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

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

  • In case you just like to get the dimensions, you may remove the line:

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

    -> since that only gets 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 voder ;)

       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 the 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 the 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()
    
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nadu
  • 2,401
  • 1
  • 25
  • 30
0

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]))
Safvan CK
  • 1,140
  • 9
  • 18
  • What do you mean by *"output of file"*? What is 'file'? A Python function? A Python class? An external command? Something else? And a file can not report anything. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/57168575/edit), not here in comments (but ************* ***without*** ************* "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Feb 01 '23 at 21:00
-3

I 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 big it is. I am still kind of new to Python, but it seems like a simple way to do it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Parker
  • 1