class_fastImage.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <?php
  2. namespace kcfinder;
  3. /**
  4. * FastImage - Because sometimes you just want the size!
  5. * Based on the Ruby Implementation by Steven Sykes (https://github.com/sdsykes/fastimage)
  6. *
  7. * Copyright (c) 2012 Tom Moor
  8. * Tom Moor, http://tommoor.com
  9. *
  10. * MIT Licensed
  11. * @version 0.1
  12. */
  13. class fastImage
  14. {
  15. private $strpos = 0;
  16. private $str;
  17. private $uri;
  18. private $type;
  19. private $handle;
  20. public function __construct($uri = null)
  21. {
  22. if ($uri) $this->load($uri);
  23. }
  24. public function load($uri)
  25. {
  26. if ($this->handle) $this->close();
  27. $this->uri = $uri;
  28. // Joy - this is a fix for URLs missing "http:"
  29. if ($uri[0] == '/' && $uri[1] == '/') {
  30. $uri = 'http:' . $uri;
  31. }
  32. $this->handle = fopen(
  33. $uri,
  34. 'r',
  35. false,
  36. stream_context_create(array(
  37. 'http'=> array('timeout' => 0.5),
  38. ))
  39. );
  40. }
  41. public function isValid()
  42. {
  43. return empty($this->handle) ? false : true;
  44. }
  45. public function close()
  46. {
  47. if (is_resource($this->handle)) fclose($this->handle);
  48. }
  49. public function getSize()
  50. {
  51. $this->strpos = 0;
  52. if ($this->getType())
  53. {
  54. return array_values($this->parseSize());
  55. }
  56. return false;
  57. }
  58. public function getType()
  59. {
  60. $this->strpos = 0;
  61. if (!$this->type)
  62. {
  63. switch ($this->getChars(2))
  64. {
  65. case "BM":
  66. return $this->type = 'bmp';
  67. case "GI":
  68. return $this->type = 'gif';
  69. case chr(0xFF).chr(0xd8):
  70. return $this->type = 'jpeg';
  71. case chr(0x89).'P':
  72. return $this->type = 'png';
  73. default:
  74. return false;
  75. }
  76. }
  77. return $this->type;
  78. }
  79. private function parseSize()
  80. {
  81. $this->strpos = 0;
  82. switch ($this->type)
  83. {
  84. case 'png':
  85. return $this->parseSizeForPNG();
  86. case 'gif':
  87. return $this->parseSizeForGIF();
  88. case 'bmp':
  89. return $this->parseSizeForBMP();
  90. case 'jpeg':
  91. return $this->parseSizeForJPEG();
  92. }
  93. return null;
  94. }
  95. private function parseSizeForPNG()
  96. {
  97. $chars = $this->getChars(25);
  98. return unpack("N*", substr($chars, 16, 8));
  99. }
  100. private function parseSizeForGIF()
  101. {
  102. $chars = $this->getChars(11);
  103. return unpack("S*", substr($chars, 6, 4));
  104. }
  105. private function parseSizeForBMP()
  106. {
  107. $chars = $this->getChars(29);
  108. $chars = substr($chars, 14, 14);
  109. $type = unpack('C', $chars);
  110. return (reset($type) == 40) ? unpack('L*', substr($chars, 4)) : unpack('L*', substr($chars, 4, 8));
  111. }
  112. private function parseSizeForJPEG()
  113. {
  114. $state = null;
  115. $i = 0;
  116. while (true)
  117. {
  118. switch ($state)
  119. {
  120. default:
  121. $this->getChars(2);
  122. $state = 'started';
  123. break;
  124. case 'started':
  125. $b = $this->getByte();
  126. if ($b === false) return false;
  127. $state = $b == 0xFF ? 'sof' : 'started';
  128. break;
  129. case 'sof':
  130. $b = $this->getByte();
  131. if (in_array($b, range(0xe0, 0xef)))
  132. {
  133. $state = 'skipframe';
  134. }
  135. elseif (in_array($b, array_merge(range(0xC0,0xC3), range(0xC5,0xC7), range(0xC9,0xCB), range(0xCD,0xCF))))
  136. {
  137. $state = 'readsize';
  138. }
  139. elseif ($b == 0xFF)
  140. {
  141. $state = 'sof';
  142. }
  143. else
  144. {
  145. $state = 'skipframe';
  146. }
  147. break;
  148. case 'skipframe':
  149. $skip = $this->readInt($this->getChars(2)) - 2;
  150. $state = 'doskip';
  151. break;
  152. case 'doskip':
  153. $this->getChars($skip);
  154. $state = 'started';
  155. break;
  156. case 'readsize':
  157. $c = $this->getChars(7);
  158. return array($this->readInt(substr($c, 5, 2)), $this->readInt(substr($c, 3, 2)));
  159. }
  160. }
  161. }
  162. private function getChars($n)
  163. {
  164. $response = null;
  165. // do we need more data?
  166. if ($this->strpos + $n -1 >= strlen($this->str))
  167. {
  168. $end = ($this->strpos + $n);
  169. while (strlen($this->str) < $end && $response !== false)
  170. {
  171. // read more from the file handle
  172. $need = $end - ftell($this->handle);
  173. if ($response = fread($this->handle, $need))
  174. {
  175. $this->str .= $response;
  176. }
  177. else
  178. {
  179. return false;
  180. }
  181. }
  182. }
  183. $result = substr($this->str, $this->strpos, $n);
  184. $this->strpos += $n;
  185. return $result;
  186. }
  187. private function getByte()
  188. {
  189. $c = $this->getChars(1);
  190. $b = unpack("C", $c);
  191. return reset($b);
  192. }
  193. private function readInt($str)
  194. {
  195. $size = unpack("C*", $str);
  196. return ($size[1] << 8) + $size[2];
  197. }
  198. public function __destruct()
  199. {
  200. $this->close();
  201. }
  202. }