Image.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. <?php
  2. namespace Grafika\Gd;
  3. use Grafika\Gd\Helper\GifHelper;
  4. use Grafika\ImageType;
  5. use Grafika\ImageInterface;
  6. /**
  7. * Image class for GD.
  8. * @package Grafika\Gd
  9. */
  10. final class Image implements ImageInterface {
  11. /**
  12. * @var resource GD resource ID.
  13. */
  14. private $gd;
  15. /**
  16. * @var string File path to image.
  17. */
  18. private $imageFile;
  19. /**
  20. * @var int Image width in pixels.
  21. */
  22. private $width;
  23. /**
  24. * @var int Image height in pixels.
  25. */
  26. private $height;
  27. /**
  28. * @var string Image type. See \Grafika\ImageType
  29. */
  30. private $type;
  31. /**
  32. * @var string Contains array of animated GIF data.
  33. */
  34. private $blocks;
  35. /**
  36. * @var bool True if animated GIF.
  37. */
  38. private $animated;
  39. /**
  40. * Image constructor.
  41. *
  42. * @param resource $gd Must use GD's imagecreate* family of functions to create a GD resource.
  43. * @param string $imageFile
  44. * @param int $width
  45. * @param int $height
  46. * @param string $type
  47. * @param string $blocks
  48. * @param bool $animated
  49. */
  50. public function __construct( $gd, $imageFile, $width, $height, $type, $blocks = '', $animated = false ) {
  51. $this->gd = $gd;
  52. $this->imageFile = $imageFile;
  53. $this->width = $width;
  54. $this->height = $height;
  55. $this->type = $type;
  56. $this->blocks = $blocks;
  57. $this->animated = $animated;
  58. }
  59. /**
  60. * Method called when 'clone' keyword is used.
  61. */
  62. public function __clone()
  63. {
  64. $original = $this->gd;
  65. $copy = imagecreatetruecolor($this->width, $this->height);
  66. imagecopy($copy, $original, 0, 0, 0, 0, $this->width, $this->height);
  67. $this->gd = $copy;
  68. }
  69. /**
  70. * Output a binary raw dump of an image in a specified format.
  71. *
  72. * @param string|ImageType $type Image format of the dump.
  73. *
  74. * @throws \Exception When unsupported type.
  75. */
  76. public function blob( $type = 'PNG' ) {
  77. $type = strtoupper($type);
  78. if ( ImageType::GIF == $type ) {
  79. imagegif( $this->gd );
  80. } else if ( ImageType::JPEG == $type ) {
  81. imagejpeg( $this->gd );
  82. } else if ( ImageType::PNG == $type ) {
  83. imagepng( $this->gd );
  84. } else if ( ImageType::WBMP == $type ) {
  85. imagewbmp( $this->gd );
  86. } else {
  87. throw new \Exception( sprintf( 'File type "%s" not supported.', $type ) );
  88. }
  89. }
  90. /**
  91. * Create Image from image file.
  92. *
  93. * @param string $imageFile Path to image.
  94. *
  95. * @return Image
  96. * @throws \Exception
  97. */
  98. public static function createFromFile( $imageFile ) {
  99. if ( ! file_exists( $imageFile ) ) {
  100. throw new \Exception( sprintf( 'Could not open "%s". File does not exist.', $imageFile ) );
  101. }
  102. $type = self::_guessType( $imageFile );
  103. if ( ImageType::GIF == $type ) {
  104. return self::_createGif( $imageFile );
  105. } else if ( ImageType::JPEG == $type ) {
  106. return self::_createJpeg( $imageFile );
  107. } else if ( ImageType::PNG == $type ) {
  108. return self::_createPng( $imageFile );
  109. } else if ( ImageType::WBMP == $type ) {
  110. return self::_createWbmp( $imageFile );
  111. } else {
  112. throw new \Exception( sprintf( 'Could not open "%s". File type not supported.', $imageFile ) );
  113. }
  114. }
  115. /**
  116. * Create an Image from a GD resource. The file type defaults to unknown.
  117. *
  118. * @param resource $gd GD resource.
  119. *
  120. * @return Image
  121. */
  122. public static function createFromCore( $gd ) {
  123. return new self( $gd, '', imagesx( $gd ), imagesy( $gd ), ImageType::UNKNOWN );
  124. }
  125. /**
  126. * Create a blank image.
  127. *
  128. * @param int $width Width in pixels.
  129. * @param int $height Height in pixels.
  130. *
  131. * @return Image
  132. */
  133. public static function createBlank($width = 1, $height = 1){
  134. return new self(imagecreatetruecolor($width, $height), '', $width, $height, ImageType::UNKNOWN);
  135. }
  136. /**
  137. * Set the blending mode for an image. Allows transparent overlays on top of an image.
  138. *
  139. * @param bool $flag True to enable blending mode.
  140. * @return self
  141. */
  142. public function alphaBlendingMode( $flag ){
  143. imagealphablending( $this->gd, $flag );
  144. return $this;
  145. }
  146. /**
  147. * Enable/Disable transparency
  148. *
  149. * @param bool $flag True to enable alpha mode.
  150. * @return self
  151. */
  152. public function fullAlphaMode( $flag ){
  153. if( true === $flag ){
  154. $this->alphaBlendingMode( false ); // Must be false for full alpha mode to work
  155. }
  156. imagesavealpha( $this->gd, $flag );
  157. return $this;
  158. }
  159. /**
  160. * Returns animated flag.
  161. *
  162. * @return bool True if animated GIF.
  163. */
  164. public function isAnimated() {
  165. return $this->animated;
  166. }
  167. /**
  168. * Get GD resource ID.
  169. *
  170. * @return resource
  171. */
  172. public function getCore() {
  173. return $this->gd;
  174. }
  175. /**
  176. * Get image file path.
  177. *
  178. * @return string File path to image.
  179. */
  180. public function getImageFile() {
  181. return $this->imageFile;
  182. }
  183. /**
  184. * Get image width in pixels.
  185. *
  186. * @return int
  187. */
  188. public function getWidth() {
  189. return $this->width;
  190. }
  191. /**
  192. * Get image height in pixels.
  193. *
  194. * @return int
  195. */
  196. public function getHeight() {
  197. return $this->height;
  198. }
  199. /**
  200. * Get image type.
  201. *
  202. * @return string
  203. */
  204. public function getType() {
  205. return $this->type;
  206. }
  207. /**
  208. * Get blocks.
  209. *
  210. * @return string.
  211. */
  212. public function getBlocks() {
  213. return $this->blocks;
  214. }
  215. /**
  216. * Get histogram from an entire image or its sub-region.
  217. *
  218. * @param array|null $slice Array of slice information. array( array( 0,0), array(100,50)) means x,y is 0,0 and width,height is 100,50
  219. *
  220. * @return array Returns array containing RGBA bins array('r'=>array(), 'g'=>array(), 'b'=>array(), 'a'=>array())
  221. */
  222. public function histogram($slice = null)
  223. {
  224. $gd = $this->getCore();
  225. if(null === $slice){
  226. $sliceX = 0;
  227. $sliceY = 0;
  228. $sliceW = $this->getWidth();
  229. $sliceH = $this->getHeight();
  230. } else {
  231. $sliceX = $slice[0][0];
  232. $sliceY = $slice[0][1];
  233. $sliceW = $slice[1][0];
  234. $sliceH = $slice[1][1];
  235. }
  236. $rBin = array();
  237. $gBin = array();
  238. $bBin = array();
  239. $aBin = array();
  240. for ($y = $sliceY; $y < $sliceY+$sliceH; $y++) {
  241. for ($x = $sliceX; $x < $sliceX+$sliceW; $x++) {
  242. $rgb = imagecolorat($gd, $x, $y);
  243. $a = ($rgb >> 24) & 0x7F; // 127 in hex. These are binary operations.
  244. $r = ($rgb >> 16) & 0xFF;
  245. $g = ($rgb >> 8) & 0xFF;
  246. $b = $rgb & 0xFF;
  247. if ( ! isset($rBin[$r])) {
  248. $rBin[$r] = 1;
  249. } else {
  250. $rBin[$r]++;
  251. }
  252. if ( ! isset($gBin[$g])) {
  253. $gBin[$g] = 1;
  254. } else {
  255. $gBin[$g]++;
  256. }
  257. if ( ! isset($bBin[$b])) {
  258. $bBin[$b] = 1;
  259. } else {
  260. $bBin[$b]++;
  261. }
  262. if ( ! isset($aBin[$a])) {
  263. $aBin[$a] = 1;
  264. } else {
  265. $aBin[$a]++;
  266. }
  267. }
  268. }
  269. return array(
  270. 'r' => $rBin,
  271. 'g' => $gBin,
  272. 'b' => $bBin,
  273. 'a' => $aBin
  274. );
  275. }
  276. /**
  277. * Load a GIF image.
  278. *
  279. * @param string $imageFile
  280. *
  281. * @return Image
  282. * @throws \Exception
  283. */
  284. private static function _createGif( $imageFile ){
  285. $gift = new GifHelper();
  286. $bytes = $gift->open($imageFile);
  287. $animated = $gift->isAnimated($bytes);
  288. $blocks = '';
  289. if($animated){
  290. $blocks = $gift->decode($bytes);
  291. }
  292. $gd = @imagecreatefromgif( $imageFile );
  293. if(!$gd){
  294. throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::GIF) );
  295. }
  296. return new self(
  297. $gd,
  298. $imageFile,
  299. imagesx( $gd ),
  300. imagesy( $gd ),
  301. ImageType::GIF,
  302. $blocks,
  303. $animated
  304. );
  305. }
  306. /**
  307. * Load a JPEG image.
  308. *
  309. * @param string $imageFile File path to image.
  310. *
  311. * @return Image
  312. * @throws \Exception
  313. */
  314. private static function _createJpeg( $imageFile ){
  315. $gd = @imagecreatefromjpeg( $imageFile );
  316. if(!$gd){
  317. throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::JPEG ) );
  318. }
  319. return new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::JPEG );
  320. }
  321. /**
  322. * Load a PNG image.
  323. *
  324. * @param string $imageFile File path to image.
  325. *
  326. * @return Image
  327. * @throws \Exception
  328. */
  329. private static function _createPng( $imageFile ){
  330. $gd = @imagecreatefrompng( $imageFile );
  331. if(!$gd){
  332. throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::PNG) );
  333. }
  334. $image = new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::PNG );
  335. $image->fullAlphaMode( true );
  336. return $image;
  337. }
  338. /**
  339. * Load a WBMP image.
  340. *
  341. * @param string $imageFile
  342. *
  343. * @return Image
  344. * @throws \Exception
  345. */
  346. private static function _createWbmp( $imageFile ){
  347. $gd = @imagecreatefromwbmp( $imageFile );
  348. if(!$gd){
  349. throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::WBMP) );
  350. }
  351. return new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::WBMP );
  352. }
  353. /**
  354. * @param $imageFile
  355. *
  356. * @return string
  357. */
  358. private static function _guessType( $imageFile ){
  359. // Values from http://php.net/manual/en/image.constants.php starting with IMAGETYPE_GIF.
  360. // 0 - unknown,
  361. // 1 - GIF,
  362. // 2 - JPEG,
  363. // 3 - PNG
  364. // 15 - WBMP
  365. list($width, $height, $type) = getimagesize( $imageFile );
  366. unset($width, $height);
  367. if ( 1 == $type) {
  368. return ImageType::GIF;
  369. } else if ( 2 == $type) {
  370. return ImageType::JPEG;
  371. } else if ( 3 == $type) {
  372. return ImageType::PNG;
  373. } else if ( 15 == $type) {
  374. return ImageType::WBMP;
  375. }
  376. return ImageType::UNKNOWN;
  377. }
  378. }