eros
eros

Reputation: 5086

How can I obtain the image size using a standard Python class (without using an 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.

Upvotes: 87

Views: 132249

Answers (10)

Safvan CK
Safvan CK

Reputation: 1340

It depends on the output of file command 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]))

Upvotes: 0

Mercury
Mercury

Reputation: 8008

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.

Upvotes: 5

Fred the Fantastic
Fred the Fantastic

Reputation: 1345

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

Upvotes: 104

Parker
Parker

Reputation: 1

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.

Upvotes: -4

Nadu
Nadu

Reputation: 2501

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()
    

Upvotes: 1

cbautista
cbautista

Reputation: 330

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

Upvotes: 20

tjb
tjb

Reputation: 11728

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.

Upvotes: 70

Dagh Bunnstad
Dagh Bunnstad

Reputation: 199

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.

Upvotes: 10

Kurt McKee
Kurt McKee

Reputation: 1410

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:

Upvotes: 18

jensph
jensph

Reputation: 785

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(',') ]

Upvotes: 5

Related Questions