diff --git a/Fastimage.php b/Fastimage.php index 0d3b9a6..5682bc7 100644 --- a/Fastimage.php +++ b/Fastimage.php @@ -4,240 +4,243 @@ * FastImage - Because sometimes you just want the size! * Based on the Ruby Implementation by Steven Sykes (https://github.com/sdsykes/fastimage) * + * ver 0.1 * Copyright (c) 2012 Tom Moor * Tom Moor, http://tommoor.com + * + * ver 0.2 + * 1. Enable WEBP type image + * 2. Add timeout and refer to fopen to avoid time waste and blocked by Hotlink Protection + * Chen.Zhidong http://sillydong.com * * MIT Licensed - * @version 0.1 + * @version 0.2 */ - -class FastImage -{ - private $strpos = 0; - private $str; - private $type; - private $handle; - - public function __construct($uri = null) - { - if ($uri) $this->load($uri); - } - - - public function load($uri) - { - if ($this->handle) $this->close(); - - $this->handle = fopen($uri, 'r'); - } - - - public function close() - { - if ($this->handle) - { - fclose($this->handle); - $this->handle = null; - $this->type = null; - $this->str = null; - } - } - - - public function getSize() - { - $this->strpos = 0; - if ($this->getType()) - { - return array_values($this->parseSize()); - } - - return false; - } - - - public function getType() - { - $this->strpos = 0; - - if (!$this->type) - { - switch ($this->getChars(2)) - { - case "BM": - return $this->type = 'bmp'; - case "GI": - return $this->type = 'gif'; - case chr(0xFF).chr(0xd8): - return $this->type = 'jpeg'; - case chr(0x89).'P': - return $this->type = 'png'; - default: - return false; - } - } - - return $this->type; - } - - - private function parseSize() - { - $this->strpos = 0; - - switch ($this->type) - { - case 'png': - return $this->parseSizeForPNG(); - case 'gif': - return $this->parseSizeForGIF(); - case 'bmp': - return $this->parseSizeForBMP(); - case 'jpeg': - return $this->parseSizeForJPEG(); - } - - return null; - } - - - private function parseSizeForPNG() - { - $chars = $this->getChars(25); - - return unpack("N*", substr($chars, 16, 8)); - } - - - private function parseSizeForGIF() - { - $chars = $this->getChars(11); - - return unpack("S*", substr($chars, 6, 4)); - } - - - private function parseSizeForBMP() - { - $chars = $this->getChars(29); - $chars = substr($chars, 14, 14); - $type = unpack('C', $chars); - - return (reset($type) == 40) ? unpack('L*', substr($chars, 4)) : unpack('L*', substr($chars, 4, 8)); - } - - - private function parseSizeForJPEG() - { - $state = null; - $i = 0; - - while (true) - { - switch ($state) - { - default: - $this->getChars(2); - $state = 'started'; - break; - - case 'started': - $b = $this->getByte(); - if ($b === false) return false; - - $state = $b == 0xFF ? 'sof' : 'started'; - break; - - case 'sof': - $b = $this->getByte(); - if (in_array($b, range(0xe0, 0xef))) - { - $state = 'skipframe'; - } - elseif (in_array($b, array_merge(range(0xC0,0xC3), range(0xC5,0xC7), range(0xC9,0xCB), range(0xCD,0xCF)))) - { - $state = 'readsize'; - } - elseif ($b == 0xFF) - { - $state = 'sof'; - } - else - { - $state = 'skipframe'; - } - break; - - case 'skipframe': - $skip = $this->readInt($this->getChars(2)) - 2; - $state = 'doskip'; - break; - - case 'doskip': - $this->getChars($skip); - $state = 'started'; - break; - - case 'readsize': - $c = $this->getChars(7); - - return array($this->readInt(substr($c, 5, 2)), $this->readInt(substr($c, 3, 2))); - } - } - } - - - private function getChars($n) - { - $response = null; - - // do we need more data? - if ($this->strpos + $n -1 >= strlen($this->str)) - { - $end = ($this->strpos + $n); - - while (strlen($this->str) < $end && $response !== false) - { - // read more from the file handle - $need = $end - ftell($this->handle); - - if ($response = fread($this->handle, $need)) - { - $this->str .= $response; - } - else - { - return false; - } - } - } - - $result = substr($this->str, $this->strpos, $n); - $this->strpos += $n; - - return $result; - } - - - private function getByte() - { - $c = $this->getChars(1); - $b = unpack("C", $c); - - return reset($b); - } - - - private function readInt($str) - { - $size = unpack("C*", $str); - - return ($size[1] << 8) + $size[2]; - } - - - public function __destruct() - { - $this->close(); - } +class FastImage { + private $strpos = 0; + private $str; + private $type; + private $handle; + + public function __construct($uri = null) { + if ($uri) + $this->load($uri); + } + + public function load($uri) { + if ($this->handle) + $this->close(); + + $host = parse_url($uri, PHP_URL_HOST); + $stream_context = @stream_context_create(array( + 'http' => array( + 'timeout' => 1, + 'method' => 'GET', + 'user_agent' => (isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)', + 'header' => "Referer: http://$host\r\n" + ) + )); + $this->handle = fopen($uri, 'rb', null, $stream_context); + } + + public function close() { + if ($this->handle && is_resource($this->handle)) + { + fclose($this->handle); + $this->handle = null; + $this->type = null; + $this->str = null; + } + } + + public function getSize() { + $this->strpos = 0; + if ($this->getType()) + { + return array_values($this->parseSize()); + } + + return false; + } + + public function getType() { + $this->strpos = 0; + + if (!$this->type) + { + switch ($this->getChars(2)) + { + case "BM": + return $this->type = 'bmp'; + case "GI": + return $this->type = 'gif'; + case chr(0xFF) . chr(0xd8): + return $this->type = 'jpeg'; + case chr(0x89) . 'P': + return $this->type = 'png'; + case "RI": + return $this->type = 'webp'; + default: + return false; + } + } + + return $this->type; + } + + private function parseSize() { + $this->strpos = 0; + + switch ($this->type) + { + case 'png': + return $this->parseSizeForPNG(); + case 'gif': + return $this->parseSizeForGIF(); + case 'bmp': + return $this->parseSizeForBMP(); + case 'jpeg': + return $this->parseSizeForJPEG(); + case 'webp': + return $this->parseSizeForWEBP(); + } + + return null; + } + + private function parseSizeForPNG() { + $chars = $this->getChars(25); + + return unpack("N*", substr($chars, 16, 8)); + } + + private function parseSizeForGIF() { + $chars = $this->getChars(11); + + return unpack("S*", substr($chars, 6, 4)); + } + + private function parseSizeForBMP() { + $chars = $this->getChars(29); + $chars = substr($chars, 14, 14); + $type = unpack('C', $chars); + + return (reset($type) == 40) ? unpack('L*', substr($chars, 4)) : unpack('L*', substr($chars, 4, 8)); + } + + private function parseSizeForJPEG() { + $state = null; + $i = 0; + + while (true) + { + switch ($state) + { + default: + $this->getChars(2); + $state = 'started'; + break; + + case 'started': + $b = $this->getByte(); + if ($b === false) + return false; + + $state = $b == 0xFF ? 'sof' : 'started'; + break; + + case 'sof': + $b = $this->getByte(); + if (in_array($b, range(0xe0, 0xef))) + { + $state = 'skipframe'; + } + elseif (in_array($b, array_merge(range(0xC0, 0xC3), range(0xC5, 0xC7), range(0xC9, 0xCB), range(0xCD, 0xCF)))) + { + $state = 'readsize'; + } + elseif ($b == 0xFF) + { + $state = 'sof'; + } + else + { + $state = 'skipframe'; + } + break; + + case 'skipframe': + $skip = $this->readInt($this->getChars(2)) - 2; + $state = 'doskip'; + break; + + case 'doskip': + $this->getChars($skip); + $state = 'started'; + break; + + case 'readsize': + $c = $this->getChars(7); + + return array($this->readInt(substr($c, 5, 2)), $this->readInt(substr($c, 3, 2))); + } + } + } + + private function parseSizeForWEBP() { + $chars = $this->getChars(30); + $result = unpack("C12/S9", $chars); + + return array($result['8'], $result['9']); + } + + private function getChars($n) { + $response = null; + + // do we need more data? + if ($this->strpos + $n - 1 >= strlen($this->str)) + { + $end = ($this->strpos + $n); + + while (strlen($this->str) < $end && $response !== false) + { + // read more from the file handle + $need = $end - ftell($this->handle); + + if ($response = fread($this->handle, $need)) + { + $this->str .= $response; + } + else + { + return false; + } + } + } + + $result = substr($this->str, $this->strpos, $n); + $this->strpos += $n; + + if (function_exists("mb_convert_encoding")) + return mb_convert_encoding($result, "8BIT", "7BIT"); + else + return $result; + } + + private function getByte() { + $c = $this->getChars(1); + $b = unpack("C", $c); + + return reset($b); + } + + private function readInt($str) { + $size = unpack("C*", $str); + + return ($size[1] << 8) + $size[2]; + } + + public function __destruct() { + $this->close(); + } } diff --git a/README.md b/README.md index 24fc71b..7b99320 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,13 @@ FastImage finds the dimensions or filetype of a remote image file given its uri by fetching as little as needed, based on the excellent [Ruby implementation by Stephen Sykes](https://github.com/sdsykes/fastimage). +## Update +###Ver 0.1 By Tom Moor +1. Support BMP,GIF,JPG/JPEG,PNG + +###Ver 0.2 By Chen.Zhidong +1. Enable WEBP type image +2. Add timeout and refer to fopen to avoid time waste and blocked by Hotlink Protection ## Usage ```php @@ -15,12 +22,16 @@ $uri = "http://farm9.staticflickr.com/8151/7357346052_54b8944f23_b.jpg"; $image = new FastImage($uri); list($width, $height) = $image->getSize(); echo "dimensions: " . $width . "x" . $height; +//better to close before use again +$image->close(); // or, create an instance and use the 'load' method $image = new FastImage(); $image->load($uri); $type = $image->getType(); echo "filetype: " . $type; +//better to close before use again +$image->close(); ``` ## References diff --git a/examples/index.php b/examples/index.php index 22df353..ca08a2b 100644 --- a/examples/index.php +++ b/examples/index.php @@ -9,11 +9,15 @@ $time = microtime(true); $image = new FastImage($uri); list($width, $height) = $image->getSize(); +//better to close before use again +$image->close(); echo "FastImage: \n"; echo "Width: ". $width . "px Height: ". $height . "px in " . (microtime(true)-$time) . " seconds \n"; + $time = microtime(true); list($width, $height) = getimagesize($uri); echo "getimagesize: \n"; echo "Width: ". $width . "px Height: ". $height . "px in " . (microtime(true)-$time) . " seconds \n"; -exit; \ No newline at end of file +//better close before use again +exit;