13

I'm trying to find the mime type of an image. PHP has the function getimagesize but that only takes a filename, whereas I have an image "resource" instead - i.e. an image created from imagecreatefromstring.

I found the functions imagesx and imagesy which return the width/height from a resource but I can't find any function that tell me the mime type from a resource. Anyone know of a way to do this?

Note: Due to a weird server set up, we can't read/write files from the server normally, only through an FTP layer (which is where I read the image data from).

DisgruntledGoat
  • 70,219
  • 68
  • 205
  • 290
  • Similar question: http://stackoverflow.com/questions/1965689/php-gd-finding-image-resource-type – Gordon Feb 05 '10 at 12:59
  • http://www.php.net/manual/en/function.image-type-to-mime-type.php –  Jul 02 '12 at 11:27

4 Answers4

14

If you've got access to the binary data of the image (as the use of imagecreatefromstring() suggests), you can detect the file-type "manually":


function image_file_type_from_binary($binary) {
    if (
        !preg_match(
            '/\A(?:(\xff\xd8\xff)|(GIF8[79]a)|(\x89PNG\x0d\x0a)|(BM)|(\x49\x49(?:\x2a\x00|\x00\x4a))|(FORM.{4}ILBM))/',
            $binary, $hits
        )
    ) {
        return 'application/octet-stream';
    }
    static $type = array (
        1 => 'image/jpeg',
        2 => 'image/gif',
        3 => 'image/png',
        4 => 'image/x-windows-bmp',
        5 => 'image/tiff',
        6 => 'image/x-ilbm',
    );
    return $type[count($hits) - 1];
}

Abusing a stream wrapper gets a bit more complicated. At least if we don't want to fiddle with global variables.


// getimagesize() from string
class GisFromString {
    const proto_default = 'gisfromstring';
    protected static $proto = null;
    protected static $imgdata = null;

    static function getImageSize($imgdata) {
        if (null === self::$proto) {
            self::register();
        }
        self::$imgdata = $imgdata;
        // note: @ suppresses "Read error!" notices if $imgdata isn't valid
        return @getimagesize(self::$proto . '://');
    }

    static function getMimeType($imgdata) {
        return is_array($gis = self::getImageSize($imgdata))
            ? $gis['mime']
            : $gis;
    }

    // streamwrapper helper:

    const unregister = null;

    // register|unregister wrapper for the given protocol|scheme
    // return registered protocol or null
    static function register(
        $proto = self::proto_default // protocol or scheme
    ) {
        if (self::unregister === $proto) { // unregister if possible
            if (null === self::$proto) {
                return null;
            }
            if (!stream_wrapper_unregister(self::$proto)) {
                return null;
            }
            $return = self::$proto;
            self::$proto = null;
            return $return;
        }
        if (!preg_match('/\A([a-zA-Z][a-zA-Z0-9.+\-]*)(:([\/\x5c]{0,3}))?/', $proto, $h)) {
            throw new Exception(
                sprintf('could not register invalid scheme or protocol name "%s" as streamwrapper', $proto)
            );
        }
        if (!stream_wrapper_register($proto = $h[1], __CLASS__)) {
            throw new Exception(
                sprintf('protocol "%s" already registered as streamwrapper', $proto)
            );
        }
        return self::$proto = $proto;
    }

    // streamwrapper API:

    function stream_open($path, $mode) {
        $this->str = (string) self::$imgdata;
        $this->fsize = strlen($this->str);
        $this->fpos = 0;
        return true;
    }

    function stream_close() {
        self::$imgdata = null;
    }

    function stream_read($num_bytes) {
        if (!is_numeric($num_bytes) || $num_bytes < 1) {
            return false;
        }
        /* uncomment this if needed
        if ($this->fpos + $num_bytes > 65540 * 4) {
            // prevent getimagesize() from scanning the whole file
            // 65_540 is the maximum possible bytesize of a JPEG segment
            return false;
        }
        */
        if ($this->fpos + $num_bytes > $this->fsize) {
            $num_bytes = $this->fsize - $this->fpos;
        }
        $read = substr($this->str, $this->fpos, $num_bytes);
        $this->fpos += strlen($read);
        return $read;
    }

    function stream_eof() {
        return $this->fpos >= $this->fsize;
    }

    function stream_tell() {
        return $this->fpos;
    }

    function stream_seek($off, $whence = SEEK_SET) {
        if (SEEK_CUR === $whence) {
            $off = $this->fpos + $off;
        }
        elseif (SEEK_END === $whence) {
            $off = $this->fsize + $off;
        }
        if ($off < 0 || $off > $this->fsize) {
            return false;
        }
        $this->fpos = $off;
        return true;
    }
}


// usage:
//$imgdata = file_get_contents('path/lenna.jpg');


// if the default protocol is already registered
//GisFromString::register('other');

var_dump(GisFromString::getImageSize($imgdata));

echo GisFromString::getMimeType($imgdata);
fireweasel
  • 359
  • 5
  • 6
  • The function `image_file_type_from_binary` is not failsafe. sometimes a file is being uploaded but the mimetype is not an image/. make the preg_match case insensitive by adding an 'i' after the ending forward-slash. (made the change in above script, but have to wait for aproval) the use of this function is a good one since not all webhosts allow you to use fileinfo or exif extensions... – SomeOne_1 Jan 02 '12 at 00:44
  • 1
    Of course it is not failsafe. It only checks the "magic bytes" at the beginning of the imagedata. Appending "/i" to the regex is a rather bad idea. Binary files have no notion of "case awareness". – fireweasel Jun 06 '12 at 20:29
11

An image created using imagecreatefromstring has no MIME type any more, it is decoded from its native format and stored in GD's internal format.

The same question was asked a while back with the same result.

The only way to go is to catch the image before it gets imagecreatefromstringed, and somehow catch the size information from it.

You say that you can't do file read/write operations on your system, so just writing out the file is out of the question.

The fact that getimagesize() can't read from variables is known and lamented: http://bugs.php.net/bug.php?id=44239

The guy there mentions a nifty workaround: Registering a new stream wrapper that allows file operations on variables.

Is this an option on your server setup?

Community
  • 1
  • 1
Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • Sorry, like I said I can't read/write files to the server normally, so it's not possible to use `getimagesize` because there is no filename. EDIT: dude, don't be so impatient, I was writing an explanation... ;) – DisgruntledGoat Feb 05 '10 at 12:40
  • Can't be done with a GD resource only, sorry. But if you `imagecreatefromstring`, you have the original data at some point, right? Why not write it out to disk and do a `getimagesize`? I agree it's silly, but it'll do the job. EDIT: That's why I first comment and then vote ;) Second edit: Ah, you can't write to disk. And I take it you can't use the `ftp://` wrapper for imagesize()? – Pekka Feb 05 '10 at 12:41
  • @DisgruntledGoat, I added a second possibility. – Pekka Feb 05 '10 at 12:49
  • FWIW, I just ran into this as well. PHP 5.3 is not available currently so `getimagesizefromstring()` is not an option (nor is `FileInfo`), and I didn't want to complicate the implementation with the `VariableStream` wrapper. The next best thing is using the `data://` wrapper, which allows you to create an in-memory URI that `getimagesize()` can read. It also optimizes the request by only encoding a small substring of the binary data. The gist of the implementation is here: https://gist.github.com/3751491 – Joe Sep 19 '12 at 19:01
9

I know this is pretty old, but just in case someone come across this post like I did...

A better option it's been released from PHP 5.4.0: getimagesizefromstring

This new function is exactly the same of getimagesize but allows you to retreive the information from a stream.

riverinyo
  • 176
  • 2
  • 10
1

You could use the PHP fileinfo functions.

$image_buffer = SomeFunctionToGetStringBufferFromGD();

$fileinfo = finfo_open();

$type = finfo_buffer($fileinfo, $image_buffer);

It uses the magic numbers (same as the unix file command) to identify the file type.

matiasf
  • 1,098
  • 1
  • 9
  • 17
  • This works great. The only downside is it returns a string you'd have to parse further to get a mime type, eg. `PNG image, 28 x 18, 8-bit colormap, non-interlaced` – Joel Mellon Jan 11 '13 at 20:59