Object Oriented Image Manipulation


/ Published in: PHP

I’ve been working on a CMS lately and having to create thumbnails for uploaded images is always a pain, lots of maths working out the correct sizes and such, so I’ve created a fairly small script to manipulate images in an object-oriented style.


Copy this code and paste it in your HTML
  1. <?php
  2. /**
  3.  * Image
  4.  *
  5.  * Class for manipulating images as PHP objects
  6.  *
  7.  * @package default
  8.  * @author Dom Hastings
  9.  */
  10. class Image {
  11. /**
  12.   * options
  13.   *
  14.   * @var array Contains the options for the functions
  15.   * @access private
  16.   */
  17. private $options = array(
  18. // array Load options
  19. 'load' => array(
  20. // integer Force the input type of image
  21. 'forceType' => false
  22. ),
  23.  
  24. // array Resizing specific options
  25. 'scale' => array(
  26. // boolean Whether or not to force the resize (true) or preserve the ratio
  27. 'force' => false
  28. ),
  29.  
  30. // array Cutout specific options
  31. 'cutout' => array(
  32. // mixed If null will default to taking the cutout from the absolute center of the image, otherwise uses the co-ordinates specified
  33. 'offsetX' => null,
  34. 'offsetY' => null,
  35.  
  36. // mixed If null defaults to the smallest possible size, otherwise resizes (forced) to the specified size
  37. 'sourceX' => null,
  38. 'sourceY' => null
  39. ),
  40.  
  41. // array Whitespace specific options
  42. 'whitespace' => array(
  43. // string HTML hex code for the 'white' space
  44. 'color' => '#ffffff',
  45. // integer Transparency value (see http://php.net/imagecolorallocatealpha)
  46. 'transparency' => 0,
  47. // string Filename for applying as a background image
  48. 'image' => '',
  49. // dimensions for scaling the image
  50. 'scaleX' => null,
  51. 'scaleY' => null,
  52. // offsets for placing the image
  53. 'offsetX' => 0,
  54. 'offsetY' => 0
  55. ),
  56.  
  57. // array Watermarking options
  58. 'watermark' => array(
  59. // mixed If null will default to taking the placing the watermark in the absolute center of the image, otherwise uses the co-ordinates specified
  60. 'offsetX' => null,
  61. 'offsetY' => null,
  62. // boolean Repeats the image on the specified axis
  63. 'repeatX' => true,
  64. 'repeatY' => true
  65. ),
  66.  
  67. // array Text options
  68. 'text' => array(
  69. // string The font file to use (TTF)
  70. 'font' => '',
  71. // integer The font size in px (GD) or pt (GD2)
  72. 'size' => 10,
  73. // integer The angle
  74. 'angle' => 0,
  75. // string HTML colour code
  76. 'color' => '#000',
  77. // integer Transparency value (see http://php.net/imagecolorallocatealpha)
  78. 'transparency' => 0
  79. ),
  80.  
  81. // array Line options
  82. 'line' => array(
  83. // array The style of the line (see http://php.net/imagesetstyle)
  84. 'style' => array(),
  85. // integer The line size in px
  86. 'size' => 1,
  87. // string HTML colour code
  88. 'color' => '#000',
  89. // integer Transparency value (see http://php.net/imagecolorallocatealpha)
  90. 'transparency' => 0
  91. ),
  92.  
  93. // array Line options
  94. 'box' => array(
  95. // array The style of the line (see http://php.net/imagesetstyle)
  96. 'style' => array(),
  97. // integer The line size in px
  98. 'size' => 1,
  99. // string HTML colour code
  100. 'color' => '#000',
  101. // integer Transparency value (see http://php.net/imagecolorallocatealpha)
  102. 'transparency' => 0,
  103. // boolean If the box is filled or not
  104. 'filled' => true
  105. ),
  106.  
  107. // array Outputting options
  108. 'output' => array(
  109. // integer Force the output type of image
  110. 'forceType' => false,
  111. // array File options
  112. 'file' => array(
  113. // boolean Whether to append the default extension
  114. 'extension' => false
  115. ),
  116. // array JPEG options
  117. 'jpeg' => array(
  118. // integer The quality parameter of imagejpeg() (http://php.net/imagejpeg)
  119. 'quality' => 85
  120. ),
  121. // array PNG options
  122. 'png' => array(
  123. // integer The quality parameter of imagepng() (http://php.net/imagepng)
  124. 'quality' => 1,
  125. // integer The filters parameter...
  126. 'filters' => PNG_ALL_FILTERS
  127. )
  128. )
  129. );
  130.  
  131. /**
  132.   * filename
  133.   *
  134.   * @var string The filename of the source image
  135.   * @access private
  136.   */
  137. private $filename = '';
  138.  
  139. /**
  140.   * source
  141.   *
  142.   * @var resource The GD image resource
  143.   * @access private
  144.   */
  145. private $source = null;
  146.  
  147. /**
  148.   * current
  149.   *
  150.   * @var resource The GD image resource
  151.   * @access private
  152.   */
  153. private $current = null;
  154.  
  155. /**
  156.   * info
  157.   *
  158.   * @var array The data from getimagesize() (http://php.net/function.getimagesize)
  159.   * @access private
  160.   */
  161. private $info = null;
  162.  
  163. /**
  164.   * __construct
  165.   *
  166.   * The constructor for the Image object
  167.   *
  168.   * @param string $f The filename of the source image
  169.   * @param array $o The options for the object
  170.   * @access public
  171.   * @author Dom Hastings
  172.   */
  173. public function __construct($f, $o = array()) {
  174. if (file_exists($f)) {
  175. $this->options = array_merge_recursive_distinct($this->options, is_array($o) ? $o : array());
  176.  
  177. // store the filename
  178. $this->filename = $f;
  179.  
  180. // load the image
  181. $this->load();
  182.  
  183. } else {
  184. throw new Exception('Imgae::__construct: Unable to load image \''.$f.'\'.');
  185. }
  186. }
  187.  
  188. /**
  189.   * __get
  190.   *
  191.   * Magic method wrapper for specific properties
  192.   *
  193.   * @param string $p The property being retrieved
  194.   * @return mixed The return value of the function called
  195.   * @access public
  196.   * @author Dom Hastings
  197.   */
  198. public function __get($p) {
  199. // only run this function if the image loaded successfully
  200. if (!$this->source) {
  201. throw new Exception('Image::__get: No image loaded.');
  202. }
  203.  
  204. // switch the property
  205. switch ($p) {
  206. // return the image width
  207. case 'x':
  208. case 'width':
  209. return $this->x();
  210.  
  211. break;
  212.  
  213. // return the image height
  214. case 'y':
  215. case 'height':
  216. return $this->y();
  217.  
  218. break;
  219.  
  220. // return the image width
  221. case 'currentX':
  222. case 'currentWidth':
  223. return $this->currentX();
  224.  
  225. break;
  226.  
  227. // return the image height
  228. case 'currentY':
  229. case 'currentHeight':
  230. return $this->currentY();
  231.  
  232. break;
  233.  
  234. // return the image size ratio
  235. case 'ratio':
  236. return $this->x() / $this->y();
  237.  
  238. break;
  239.  
  240. // return the image size details
  241. case 'size':
  242. return array($this->x(), $this->y());
  243.  
  244. break;
  245.  
  246. // return the image information
  247. case 'mimetype':
  248. return $this->info[3];
  249.  
  250. break;
  251.  
  252. // return the image information
  253. case 'extension':
  254. (!empty($this->options['forceWriteType']) ? $this->options['forceWriteType'] : $this->info[2])
  255. );
  256.  
  257. break;
  258.  
  259. // return the image information
  260. case 'imagetype':
  261. return $this->info[2];
  262.  
  263. break;
  264.  
  265. // return the image information
  266. case 'info':
  267. return $this->info;
  268.  
  269. break;
  270.  
  271. // not caught
  272. default:
  273. throw new Exception('Image::__get: Undefined property');
  274.  
  275. break;
  276. }
  277. }
  278.  
  279. /**
  280.   * __set
  281.   *
  282.   * Magic method wrapper for setting values
  283.   *
  284.   * @param string $p The property being 'set'
  285.   * @param mixed $v The value to 'set' property to
  286.   * @return void
  287.   * @access public
  288.   * @author Dom Hastings
  289.   */
  290. public function __set($p, $v) {
  291. switch ($p) {
  292. case 'width':
  293. case 'x':
  294. $this->scale($v, 0);
  295. break;
  296.  
  297. case 'height':
  298. case 'y':
  299. $this->scale(0, $v);
  300. break;
  301.  
  302. case 'watermark':
  303. $this->watermark($v);
  304. break;
  305.  
  306. case 'type':
  307. $this->options['output']['forceType'] = $v;
  308. break;
  309.  
  310. default:
  311. break;
  312. }
  313. }
  314.  
  315. /**
  316.   * load
  317.   *
  318.   * Loads the image and saves the details
  319.   *
  320.   * @return void
  321.   * @access private
  322.   * @author Dom Hastings
  323.   */
  324. private function load($options = array()) {
  325. // merge in the options
  326. $options = array_merge_recursive_distinct(
  327. (is_array($this->options['load'])) ? $this->options['load'] : array(),
  328. (is_array($options)) ? $options : array()
  329. );
  330.  
  331. // get the image details stored
  332. $this->info();
  333.  
  334. // if we're forcing a read type
  335. if (!empty($options['forceType'])) {
  336. // use it
  337. $imageType = $options['forceType'];
  338.  
  339. } else {
  340. // otherwise use the discovered type
  341. $imageType = $this->info[2];
  342. }
  343.  
  344. $this->source = $this->current = $this->loadFile($this->filename, $imageType);
  345.  
  346. // if the image loading failed
  347. if (!$this->source) {
  348. throw new Exception('Imgae::load: Unable to load image \''.$this->filename.'\'.');
  349. }
  350. }
  351.  
  352. /**
  353.   * loadFile
  354.   *
  355.   * Loads an image image from a file
  356.   *
  357.   * @param string f The filename
  358.   * @param string imageType The type of image
  359.   * @return resource The loaded image
  360.   * @access private
  361.   * @author Dom Hastings
  362.   */
  363. private function loadFile($f = null, $imageType = null) {
  364. // switch the type and load using the correct function
  365. switch ($imageType) {
  366. case IMAGETYPE_GIF:
  367. $resource = imagecreatefromgif($this->filename);
  368. break;
  369.  
  370. case IMAGETYPE_JPEG:
  371. case IMAGETYPE_JPEG2000:
  372. case IMAGETYPE_JPC:
  373. case IMAGETYPE_JP2:
  374. case IMAGETYPE_JPX:
  375. $resource = imagecreatefromjpeg($this->filename);
  376. break;
  377.  
  378. case IMAGETYPE_PNG:
  379. $resource = imagecreatefrompng($this->filename);
  380. break;
  381.  
  382. case IMAGETYPE_BMP:
  383. case IMAGETYPE_WBMP:
  384. $resource = imagecreatefromwbmp($this->filename);
  385. break;
  386.  
  387. case IMAGETYPE_XBM:
  388. $resource = imagecreatefromxbm($this->filename);
  389. break;
  390.  
  391. case IMAGETYPE_TIFF_II:
  392. case IMAGETYPE_TIFF_MM:
  393. case IMAGETYPE_IFF:
  394. case IMAGETYPE_JB2:
  395. case IMAGETYPE_SWF:
  396. case IMAGETYPE_PSD:
  397. case IMAGETYPE_SWC:
  398. // case IMAGETYPE_ICO:
  399. default:
  400. $resource = null;
  401. break;
  402. }
  403.  
  404. return $resource;
  405. }
  406.  
  407. /**
  408.   * output
  409.   *
  410.   * Output the image
  411.   *
  412.   * @param string $f (Optional) The filename to output to, if this is omitted the image is output to the browser
  413.   * @return void
  414.   * @access private
  415.   * @author Dom Hastings
  416.   */
  417. public function output($f = null, $options = array()) {
  418. // merge in the options
  419. $options = array_merge_recursive_distinct(
  420. (is_array($this->options['output'])) ? $this->options['output'] : array(),
  421. (is_array($options)) ? $options : array()
  422. );
  423.  
  424. // if we're forcing an output type
  425. if (!empty($options['forceType'])) {
  426. $imageType = $options['forceType'];
  427.  
  428. } else {
  429. $imageType = $this->info[2];
  430. }
  431.  
  432. // use the correct output function
  433. switch ($imageType) {
  434. case IMAGETYPE_GIF:
  435. header('Content-type: '.image_type_to_mime_type($imageType));
  436. imagegif($this->current, $f);
  437. break;
  438.  
  439. case IMAGETYPE_JPEG:
  440. case IMAGETYPE_JPEG2000:
  441. case IMAGETYPE_JPC:
  442. case IMAGETYPE_JP2:
  443. case IMAGETYPE_JPX:
  444. header('Content-type: '.image_type_to_mime_type($imageType));
  445. imagejpeg($this->current, $f, $options['jpeg']['quality']);
  446. break;
  447.  
  448. case IMAGETYPE_PNG:
  449. header('Content-type: '.image_type_to_mime_type($imageType));
  450. imagepng($this->current, $f, $options['png']['quality'], $options['png']['filters']);
  451. break;
  452.  
  453. case IMAGETYPE_BMP:
  454. case IMAGETYPE_WBMP:
  455. header('Content-type: '.image_type_to_mime_type($imageType));
  456. imagewbmp($this->current, $f);
  457. break;
  458.  
  459. case IMAGETYPE_XBM:
  460. header('Content-type: '.image_type_to_mime_type($imageType));
  461. imagexbm($this->current, $f);
  462. break;
  463.  
  464. case IMAGETYPE_TIFF_II:
  465. case IMAGETYPE_TIFF_MM:
  466. case IMAGETYPE_IFF:
  467. case IMAGETYPE_JB2:
  468. case IMAGETYPE_SWF:
  469. case IMAGETYPE_PSD:
  470. case IMAGETYPE_SWC:
  471. // case IMAGETYPE_ICO:
  472. default:
  473. break;
  474. }
  475. }
  476.  
  477. /**
  478.   * write
  479.   *
  480.   * Writes the output data to the specified filename
  481.   *
  482.   * @param string $f The filename
  483.   * @return string The filename written to
  484.   * @access public
  485.   * @author Dom Hastings
  486.   */
  487. public function write($f, $options = array()) {
  488. // merge in the options
  489. $options = array_merge_recursive_distinct(
  490. (is_array($this->options['output']['file'])) ? $this->options['output']['file'] : array(),
  491. (is_array($options)) ? $options : array()
  492. );
  493.  
  494. if ($this->options['output']['forceType']) {
  495. $imageType = $this->options['output']['forceType'];
  496.  
  497. } else {
  498. $imageType = $this->info[2];
  499. }
  500.  
  501. if ($options['extension'] || strpos($f, '.') === false) {
  502. $f .= $this->extension;
  503. }
  504.  
  505. $this->output($f);
  506.  
  507. return $f;
  508. }
  509.  
  510. /**
  511.   * resource
  512.   *
  513.   * Returns the current image as a resource
  514.   *
  515.   * @return void
  516.   * @access public
  517.   * @author Dom Hastings
  518.   */
  519. public function resource() {
  520. return $this->current;
  521. }
  522.  
  523. /**
  524.   * info
  525.   *
  526.   * Gets information about the current image
  527.   *
  528.   * @return void
  529.   * @access private
  530.   * @author Dom Hastings
  531.   */
  532. private function info($f = null) {
  533. // if the filename is empty
  534. if (empty($f)) {
  535. // stores the image information inside the object
  536. $this->info = getimagesize($this->filename);
  537.  
  538. } else {
  539. // it's not the main image so return it directly
  540. return getimagesize($f);
  541. }
  542. }
  543.  
  544. /**
  545.   * x
  546.   *
  547.   * Returns the width of the image
  548.   *
  549.   * @param string $a Reads the image directly, otherwise uses the cached information form load
  550.   * @return integer The width of the image
  551.   * @access public
  552.   * @author Dom Hastings
  553.   */
  554. public function x($a = false) {
  555. if ($a) {
  556. return imagesx($this->source);
  557.  
  558. } else {
  559. if (empty($this->info)) {
  560. $this->info();
  561. }
  562.  
  563. return $this->info[0];
  564. }
  565. }
  566.  
  567. /**
  568.   * currentX
  569.   *
  570.   * Returns the width of the thumb image
  571.   *
  572.   * @param string $a Reads the image directly, otherwise uses the cached information form load
  573.   * @return integer The width of the image
  574.   * @access public
  575.   * @author Dom Hastings
  576.   */
  577. public function currentX() {
  578. if ($this->current) {
  579. return imagesx($this->current);
  580. }
  581. }
  582.  
  583. /**
  584.   * y
  585.   *
  586.   * Returns the height of the image
  587.   *
  588.   * @param boolean $a Reads the image directly, otherwise uses the cached information form load
  589.   * @return integer The height of the image
  590.   * @access public
  591.   * @author Dom Hastings
  592.   */
  593. public function y($a = false) {
  594. if ($a) {
  595. return imagesy($this->source);
  596.  
  597. } else {
  598. if (empty($this->info)) {
  599. $this->info();
  600. }
  601.  
  602. return $this->info[1];
  603. }
  604. }
  605.  
  606. /**
  607.   * currentY
  608.   *
  609.   * Returns the height of the current image
  610.   *
  611.   * @param boolean $a Reads the image directly, otherwise uses the cached information form load
  612.   * @return integer The height of the image
  613.   * @access public
  614.   * @author Dom Hastings
  615.   */
  616. public function currentY($a = false) {
  617. if ($this->current) {
  618. return imagesy($this->current);
  619. }
  620. }
  621.  
  622. /**
  623.   * scale
  624.   *
  625.   * Scales the current image to the dimensions specified, using the options specified
  626.   *
  627.   * @param integer $x The desired width
  628.   * @param integer $y The desired height
  629.   * @param array $options See main options block at top of file
  630.   * @return resource The new image
  631.   * @access public
  632.   * @author Dom Hastings
  633.   */
  634. public function scale($x, $y, $options = array()) {
  635. // merge in the options
  636. $options = array_merge_recursive_distinct(
  637. (is_array($this->options['scale'])) ? $this->options['scale'] : array(),
  638. (is_array($options)) ? $options : array()
  639. );
  640.  
  641. // if we're not forcing the size
  642. if (empty($options['force'])) {
  643. // check we're not trying to enlarge the image
  644. if ($x > $this->x) {
  645. $x = $this->x;
  646. }
  647.  
  648. if ($y > $this->y) {
  649. $y = $this->y;
  650. }
  651.  
  652. // if neither dimension is specified
  653. if ($x == 0 && $y == 0) {
  654. throw new Exception('Image::scale: At least one dimension must be spcified to scale an image.');
  655.  
  656. } elseif ($x > 0 && $y > 0) {
  657. // maths!
  658. $destX = $x;
  659. $destY = intval($x / $this->ratio);
  660.  
  661. if ($destY > $y) {
  662. $destX = intval($y * $this->ratio);
  663. $destY = $y;
  664. }
  665.  
  666. } elseif ($x == 0) {
  667. $destX = intval($y * $this->ratio);
  668. $destY = $y;
  669.  
  670. } elseif ($y == 0) {
  671. $destX = $x;
  672. $destY = intval($x / $this->ratio);
  673. }
  674.  
  675. } else {
  676. $destX = $x;
  677. $destY = $y;
  678. }
  679.  
  680. // create the destination
  681. $dest = imagecreatetruecolor($destX, $destY);
  682.  
  683. // resample the image as specified
  684. if (!imagecopyresampled($dest, $this->source, 0, 0, 0, 0, $destX, $destY, $this->x, $this->y)) {
  685. throw new Exception('Image::scale: Error scaling image');
  686. }
  687.  
  688. $this->current = $dest;
  689.  
  690. return $dest;
  691. }
  692.  
  693. /**
  694.   * cutout
  695.   *
  696.   * Returns a selected portion of the image after optionally resizing it
  697.   *
  698.   * @param integer $x The desired width
  699.   * @param integer $y The desired height
  700.   * @param array $options
  701.   * @return resource The new image
  702.   * @access public
  703.   * @author Dom Hastings
  704.   */
  705. public function cutout($x, $y, $options = array()) {
  706. // merge in the options
  707. $options = array_merge_recursive_distinct(
  708. (is_array($this->options['cutout'])) ? $this->options['cutout'] : array(),
  709. (is_array($options)) ? $options : array()
  710. );
  711.  
  712. // if the source image dimensions haven't been specified, work them out as best you can
  713. if (empty($options['scaleX']) && empty($options['scaleY'])) {
  714. // more maths!
  715. if ($this->x >= $this->y) {
  716. // landscape
  717. $scaleX = intval($y * $this->ratio);
  718. $scaleY = $y;
  719.  
  720. if ($scaleX < $x) {
  721. $scaleX = $x;
  722. $scaleY = intval($x / $this->ratio);
  723. }
  724.  
  725. } else {
  726. // portrait
  727. $scaleX = $x;
  728. $scaleY = intval($x / $this->ratio);
  729.  
  730. if ($scaleY < $y) {
  731. $scaleX = intval($y * $this->ratio);
  732. $scaleY = $y;
  733. }
  734. }
  735.  
  736. } else {
  737. $scaleX = $options['scaleX'];
  738. $scaleY = $options['scaleY'];
  739. }
  740.  
  741. // scale the image
  742. $source = $this->scale($scaleX, $scaleY, array('force' => true));
  743.  
  744. // if the offset hasn't been specified
  745. if (!isset($options['offsetX']) || !isset($options['offsetY'])) {
  746. // calculate the center
  747. $offsetX = intval(($scaleX / 2) - ($x / 2));
  748. $offsetY = intval(($scaleY / 2) - ($y / 2));
  749.  
  750. } else {
  751. $offsetX = $options['offsetX'];
  752. $offsetY = $options['offsetY'];
  753. }
  754.  
  755. // create the destination
  756. $dest = imagecreatetruecolor($x, $y);
  757.  
  758. // cut it out
  759. if (!imagecopy($dest, $source, 0, 0, $offsetX, $offsetY, $scaleX, $scaleY)) {
  760. throw new Exception('Image::scale: Error cutting out image');
  761. }
  762.  
  763. $this->current = $dest;
  764.  
  765. return $dest;
  766. }
  767.  
  768. /**
  769.   * whitespace
  770.   *
  771.   * Returns a scaled version of the image with any white space on the base filled with an image or a colour, depending on options specified
  772.   *
  773.   * @param string $x
  774.   * @param string $y
  775.   * @param string $options
  776.   * @return void
  777.   * @access public
  778.   * @author Dom Hastings
  779.   */
  780. public function whitespace($x, $y, $options = array()) {
  781. // merge in the options
  782. $options = array_merge_recursive_distinct(
  783. (is_array($this->options['whitespace'])) ? $this->options['whitespace'] : array(),
  784. (is_array($options)) ? $options : array()
  785. );
  786.  
  787. // if we're using an image background
  788. if (!empty($options['image'])) {
  789. // load it
  790. $orig = new Image($options['image']);
  791.  
  792. $orig->scale($x, $y, array('force' => true));
  793.  
  794. $dest = $orig->resource();
  795.  
  796. // else if it's just a colour
  797. } elseif (!empty($options['color'])) {
  798. // create the base image
  799. $dest = imagecreatetruecolor($x, $y);
  800.  
  801. // extract the int values of the colour
  802. list($r, $g, $b) = $this->hexToRGB($options['color']);
  803.  
  804. // allocate the colour
  805. $color = imagecolorallocatealpha($dest, $r, $g, $b, $options['transparency']);
  806.  
  807. // fill it
  808. imagefill($dest, 0, 0, $color);
  809.  
  810. // else, we aren't keeping any whitespace, so just scale it
  811. } else {
  812. return $this->scale($x, $y);
  813. }
  814.  
  815. // if scaling options have been set
  816. if (!empty($options['scaleX']) || !empty($options['scaleY'])) {
  817. // use them
  818. $scaleX = $options['scaleX'];
  819. $scaleY = $options['scaleY'];
  820.  
  821. $options = array(
  822. 'force' => true
  823. );
  824.  
  825. } else {
  826. // otherwise assume the passed options
  827. $scaleX = $x;
  828. $scaleY = $y;
  829.  
  830. $options = array();
  831. }
  832.  
  833. // scale the image
  834. $source = $this->scale($scaleX, $scaleY, $options);
  835.  
  836. // extract the new height and width
  837. $scaleX = $this->currentX;
  838. $scaleY = $this->currentY;
  839.  
  840. // determine the offset
  841. if (!isset($options['offsetX']) || !isset($options['offsetY'])) {
  842. $offsetX = intval(($x / 2) - ($scaleX / 2));
  843. $offsetY = intval(($y / 2) - ($scaleY / 2));
  844.  
  845. } else {
  846. $offsetX = $options['offsetX'];
  847. $offsetY = $options['offsetY'];
  848. }
  849.  
  850. // overlay it
  851. if (!imagecopy($dest, $source, $offsetX, $offsetY, 0, 0, $scaleX, $scaleY)) {
  852. throw new Exception('Image::scale: Error whitespacing image');
  853. }
  854.  
  855. $this->current = $dest;
  856.  
  857. return $dest;
  858. }
  859.  
  860. /**
  861.   * watermark
  862.   *
  863.   * Watermarks the current image with the specified image
  864.   *
  865.   * @param string $i The image to use as a watermark
  866.   * @param array $options The options
  867.   * @return resource The watermarked image
  868.   * @access public
  869.   * @author Dom Hastings
  870.   */
  871. public function watermark($i, $options = array()) {
  872. // merge in the options
  873. $options = array_merge_recursive_distinct(
  874. (is_array($this->options['watermark'])) ? $this->options['watermark'] : array(),
  875. (is_array($options)) ? $options : array()
  876. );
  877.  
  878. if (!file_exists($i)) {
  879. throw new Exception('Image::watermark: Missing watermark image \''.$i.'\'.');
  880. }
  881.  
  882. $dest = $this->current;
  883.  
  884. // load the watermark
  885. $watermark = new Image($i);
  886.  
  887. // determine the offset
  888. if (!isset($options['offsetX']) || !isset($options['offsetY'])) {
  889. $offsetX = intval(($this->currentX / 2) - ($watermark->currentX / 2));
  890. $offsetY = intval(($this->currentY / 2) - ($watermark->currentY / 2));
  891.  
  892. } else {
  893. $offsetX = $options['offsetX'];
  894. $offsetY = $options['offsetY'];
  895. }
  896.  
  897. // overlay it
  898. if (!empty($options['repeatX']) && !empty($options['repeatY'])) {
  899. $offsetX = $offsetY = 0;
  900.  
  901. // rows
  902. for ($i = $offsetY; $i < $this->currentY; $i += $watermark->y) {
  903. // cols
  904. for ($j = $offsetX; $j < $this->currentX; $j += $watermark->x) {
  905. if (!imagecopy($dest, $watermark->resource(), $j, $i, 0, 0, $watermark->x, $watermark->y)) {
  906. throw new Exception('Image::scale: Error watermarking image.');
  907. }
  908. }
  909. }
  910.  
  911. } elseif (!empty($options['repeatX'])) {
  912. $offsetX = 0;
  913.  
  914. for ($i = $offsetX; $i <= $this->currentX; $i += $watermark->x) {
  915. if (!imagecopy($dest, $watermark->resource(), $i, $offsetY, 0, 0, $watermark->x, $watermark->y)) {
  916. throw new Exception('Image::scale: Error watermarking image.');
  917. }
  918. }
  919.  
  920. } elseif (!empty($options['repeatY'])) {
  921. $offsetY = 0;
  922.  
  923. for ($i = $offsetY; $i <= $this->currentY; $i += $watermark->y) {
  924. if (!imagecopy($dest, $watermark->resource(), $offsetX, $i, 0, 0, $watermark->x, $watermark->y)) {
  925. throw new Exception('Image::scale: Error watermarking image.');
  926. }
  927. }
  928.  
  929. } else {
  930. if (!imagecopy($dest, $watermark->resource(), $offsetX, $offsetY, 0, 0, $watermark->x, $watermark->y)) {
  931. throw new Exception('Image::scale: Error watermarking image.');
  932. }
  933. }
  934.  
  935. $this->current = $dest;
  936.  
  937. return $dest;
  938. }
  939.  
  940. /**
  941.   * hexToRGB
  942.   *
  943.   * Returns the integer colour values from an HTML hex code
  944.   *
  945.   * @param string $h The HTML hex code
  946.   * @return array The integer colour values
  947.   * @access public
  948.   * @author Dom Hastings
  949.   */
  950. private function hexToRGB($h) {
  951. // strip off the # if it's there
  952. $h = trim($h, '#');
  953.  
  954. if (strlen($h) == 6) {
  955. return array(
  956. hexdec(substr($h, 0, 2)),
  957. hexdec(substr($h, 2, 2)),
  958. hexdec(substr($h, 4, 2))
  959. );
  960.  
  961. } elseif (strlen($h) == 3) {
  962. return array(
  963. hexdec(substr($h, 0, 1).substr($h, 0, 1)),
  964. hexdec(substr($h, 1, 1).substr($h, 1, 1)),
  965. hexdec(substr($h, 2, 1).substr($h, 2, 1))
  966. );
  967.  
  968. } else {
  969. // default to white
  970. return array(255, 255, 255);
  971. }
  972. }
  973.  
  974. /**
  975.   * addText
  976.   *
  977.   * Adds the specified text to the image at the specified location
  978.   *
  979.   * @param string $t The text to add to the image
  980.   * @param integer $x The x co-ordinate of the text
  981.   * @param integer $y The y co-ordinate of the text
  982.   * @param array $options The options
  983.   * @return array Results from imagettftext()
  984.   * @author Dom Hastings
  985.   */
  986. function addText($text, $x, $y, $options = array()) {
  987. // merge in the options
  988. $options = array_merge_recursive_distinct(
  989. (is_array($this->options['text'])) ? $this->options['text'] : array(),
  990. (is_array($options)) ? $options : array()
  991. );
  992.  
  993. // check the font file exists
  994. if (substr($options['font'], 0, 1) == '/') {
  995. if (!file_exists($options['font'])) {
  996. throw new Exception('Imge::addText: Unable to find font file \''.$options['font'].'\'');
  997. }
  998.  
  999. } else {
  1000. if (!file_exists($options['font'].'.ttf')) {
  1001. throw new Exception('Imge::addText: Unable to find font file \''.$options['font'].'\'');
  1002. }
  1003. }
  1004.  
  1005. list($r, $g, $b) = $this->hexToRGB($options['color']);
  1006.  
  1007. $colour = imagecolorallocatealpha($this->current, $r, $g, $b, $options['transparency']);
  1008.  
  1009. return imagettftext($this->current, $options['size'], $options['angle'], $x, $y, $colour, $options['font'], $text);
  1010. }
  1011.  
  1012. /**
  1013.   * drawLine
  1014.   *
  1015.   * Draws a line from the co-ordinates in array start to the co-ordinates in array finish using the GD library function
  1016.   *
  1017.   * @param array $start The start point index 0 should be the x co-ordinate, 1 the y
  1018.   * @param array $finish The end point index 0 should be the x co-ordinate, 1 the y
  1019.   * @param array $options The options
  1020.   * @return mixed The result from imageline()
  1021.   * @author Dom Hastings
  1022.   */
  1023. function drawLine($start, $finish, $options = array()) {
  1024. // merge in the options
  1025. $options = array_merge_recursive_distinct(
  1026. (is_array($this->options['line'])) ? $this->options['line'] : array(),
  1027. (is_array($options)) ? $options : array()
  1028. );
  1029.  
  1030. imagesetthickness($this->current, $options['size']);
  1031.  
  1032. if (!is_array($start) || !is_array($finish)) {
  1033. throw new Exception('Image::drawLine: Arguments 0 and 1 must be arrays.');
  1034. }
  1035.  
  1036. list($sX, $sY, $fX, $fY) = array_merge(array_values($start), array_values($finish));
  1037.  
  1038. list($r, $g, $b) = $this->hexToRGB($options['color']);
  1039.  
  1040. $colour = imagecolorallocatealpha($this->current, $r, $g, $b, $options['transparency']);
  1041.  
  1042. if (!empty($options['style'])) {
  1043. imagesetstyle($this->current, $options['style']);
  1044. }
  1045.  
  1046. return imageline($this->current, $sX, $sY, $fX, $fY, $colour);
  1047. }
  1048.  
  1049. /**
  1050.   * drawBox
  1051.   *
  1052.   * Draws a box from the co-ordinates in array start to the co-ordinates in array finish using the GD library function
  1053.   *
  1054.   * @param array $start The start point index 0 should be the x co-ordinate, 1 the y
  1055.   * @param array $finish The end point index 0 should be the x co-ordinate, 1 the y
  1056.   * @param array $options The options
  1057.   * @return mixed The result from imagerectangle()
  1058.   * @author Dom Hastings
  1059.   */
  1060. function drawBox($start, $finish, $options = array()) {
  1061. // merge in the options
  1062. $options = array_merge_recursive_distinct(
  1063. (is_array($this->options['box'])) ? $this->options['box'] : array(),
  1064. (is_array($options)) ? $options : array()
  1065. );
  1066.  
  1067. imagesetthickness($this->current, $options['size']);
  1068.  
  1069. if (!is_array($start) || !is_array($finish)) {
  1070. throw new Exception('Image::drawLine: Arguments 0 and 1 must be arrays.');
  1071. }
  1072.  
  1073. list($sX, $sY, $fX, $fY) = array_merge(array_values($start), array_values($finish));
  1074.  
  1075. list($r, $g, $b) = $this->hexToRGB($options['color']);
  1076.  
  1077. $colour = imagecolorallocatealpha($this->current, $r, $g, $b, $options['transparency']);
  1078.  
  1079. if (empty($options['filled'])) {
  1080. if (!empty($options['style'])) {
  1081. imagesetstyle($this->current, $options['style']);
  1082. }
  1083.  
  1084. return imagerectangle($this->current, $sX, $sY, $fX, $fY, $colour);
  1085.  
  1086. } else {
  1087. return imagefilledrectangle($this->current, $sX, $sY, $fX, $fY, $colour);
  1088. }
  1089. }
  1090. }
  1091.  
  1092. /**
  1093.  * array_merge_recursive_distinct
  1094.  *
  1095.  * Recursively process an array merge all child nodes together
  1096.  *
  1097.  * @return array
  1098.  * @author Dom Hastings
  1099.  */
  1100. if (!function_exists('array_merge_recursive_distinct')) {
  1101. function array_merge_recursive_distinct() {
  1102. switch (func_num_args()) {
  1103. case 0:
  1104. return array();
  1105. break;
  1106.  
  1107. case 1:
  1108. return (array) func_get_arg(0);
  1109. break;
  1110.  
  1111. default:
  1112. $a = func_get_args();
  1113. $s = (array) array_shift($a);
  1114.  
  1115. foreach ($a as $i => $b) {
  1116. if (!is_array($b)) {
  1117. $b = (array) $b;
  1118. }
  1119.  
  1120. foreach ($b as $k => $v) {
  1121. if (is_numeric($k)) {
  1122. $s[] = $v;
  1123.  
  1124. } else {
  1125. if (isset($s[$k])) {
  1126. if (is_array($s[$k]) && is_array($v)) {
  1127. $s[$k] = array_merge_recursive_distinct($s[$k], $v);
  1128.  
  1129. } else {
  1130. $s[$k] = $v;
  1131. }
  1132.  
  1133. } else {
  1134. $s[$k] = $v;
  1135. }
  1136. }
  1137. }
  1138. }
  1139. break;
  1140. }
  1141.  
  1142. return $s;
  1143. }
  1144. }

URL: http://www.dom111.co.uk/blog/coding/php-object-oriented-image-manipulation/156

Report this snippet


Comments

RSS Icon Subscribe to comments

You need to login to post a comment.