在PHP/GD中从资源获取图像的MIME类型?

13
我正在尝试查找图像的MIME类型。PHP有一个名为getimagesize的函数,但它仅接受文件名,而我有一个图像“资源”-即从imagecreatefromstring创建的图像。
我发现imagesximagesy函数可以从资源返回宽度/高度,但我找不到任何可以从资源告诉我MIME类型的函数。有人知道如何做到这一点吗?
注意:由于奇怪的服务器设置,我们不能正常地从服务器读取/写入文件,只能通过FTP层(这就是我从中读取图像数据的地方)进行访问。

类似问题:https://dev59.com/6UvSa4cB1Zd3GeqPawLO - Gordon
http://www.php.net/manual/en/function.image-type-to-mime-type.php - user1495928
4个回答

14
如果您可以访问图像的二进制数据(使用imagecreatefromstring()函数暗示了这一点),则可以手动检测文件类型:

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];
}

滥用流包装器会变得更加复杂。至少,如果我们不想摆弄全局变量的话。


// 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);

函数image_file_type_from_binary不是完全可靠的。有时候上传的文件类型不是图像/<type>。通过在结束斜杠后添加'i',使preg_match不区分大小写。(已在上述脚本中进行更改,但需要等待批准)使用此函数是一个好方法,因为并非所有网络主机都允许您使用fileinfo或exif扩展... - SomeOne_1
1
当然,它并不是完全可靠的。它只检查图像数据开头的“魔数”。在正则表达式后添加“/i”是一个相当糟糕的想法。二进制文件没有“大小写感知”的概念。 - fireweasel

11

imagecreatefromstring创建的图像不再具有MIME类型,它是从其原生格式解码并存储在GD的内部格式中。

以前曾问过同样的问题,结果也一样。

唯一的方法是在图像被imagecreatefromstring处理之前捕获它,并以某种方式获取其大小信息。

你说你无法在系统上进行文件读写操作,因此无法将文件写出。

无法从变量中读取getimagesize()的事实是众所周知且令人遗憾:http://bugs.php.net/bug.php?id=44239

那个人提到了一个巧妙的解决方法:注册一个新的流包装器,允许在变量上进行文件操作

这在你的服务器设置中是否可行?


抱歉,就像我说的那样,我无法正常地读写服务器上的文件,因此无法使用getimagesize,因为没有文件名。编辑:伙计,不要这么着急,我正在写解释... ;) - DisgruntledGoat
很抱歉,仅使用GD资源无法完成此操作。但是,如果您使用imagecreatefromstring,那么在某个时刻您将拥有原始数据,对吧?为什么不将其写入磁盘并执行getimagesize呢?我同意这很愚蠢,但它可以完成工作。编辑:这就是为什么我先评论然后投票的原因;)第二次编辑:啊,您不能写入磁盘。我想您也不能使用ftp://包装器来获取图像大小? - Pekka
@DisgruntledGoat,我添加了第二种可能性。 - Pekka
顺便说一下,我也遇到了这个问题。目前不支持PHP 5.3,因此getimagesizefromstring()不可用(FileInfo也不行),而且我不想用VariableStream包装器来复杂化实现。下一个最好的选择是使用data://包装器,它允许您创建一个内存URI,供getimagesize()读取。它还通过仅对二进制数据的小子字符串进行编码来优化请求。实现的要点在这里:https://gist.github.com/3751491 - Joe

9
我知道这已经很旧了,但以防万一有人像我一样遇到了这篇文章...... 从PHP 5.4.0开始,有一个更好的选择: getimagesizefromstring 这个新函数与getimagesize完全相同,但允许您从流中检索信息。

1
你可以使用PHP的文件信息函数。
$image_buffer = SomeFunctionToGetStringBufferFromGD();

$fileinfo = finfo_open();

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

它使用魔术数字(与Unix文件命令相同)来识别文件类型。


这个很好用。唯一的缺点是它返回一个字符串,你需要进一步解析才能得到MIME类型,例如 PNG图像,28 x 18,8位色彩映射,非交错 - Joel Mellon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接