Posted By

dom111 on 09/15/10


Tagged

php image gd oop manipulation


Versions (?)

Object Oriented Image Manipulation


 / Published in: PHP
 

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

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.

  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. return image_type_to_extension(
  255. (!empty($this->options['forceWriteType']) ? $this->options['forceWriteType'] : $this->info[2])
  256. );
  257.  
  258. break;
  259.  
  260. // return the image information
  261. case 'imagetype':
  262. return $this->info[2];
  263.  
  264. break;
  265.  
  266. // return the image information
  267. case 'info':
  268. return $this->info;
  269.  
  270. break;
  271.  
  272. // not caught
  273. default:
  274. throw new Exception('Image::__get: Undefined property');
  275.  
  276. break;
  277. }
  278. }
  279.  
  280. /**
  281.   * __set
  282.   *
  283.   * Magic method wrapper for setting values
  284.   *
  285.   * @param string $p The property being 'set'
  286.   * @param mixed $v The value to 'set' property to
  287.   * @return void
  288.   * @access public
  289.   * @author Dom Hastings
  290.   */
  291. public function __set($p, $v) {
  292. switch ($p) {
  293. case 'width':
  294. case 'x':
  295. $this->scale($v, 0);
  296. break;
  297.  
  298. case 'height':
  299. case 'y':
  300. $this->scale(0, $v);
  301. break;
  302.  
  303. case 'watermark':
  304. $this->watermark($v);
  305. break;
  306.  
  307. case 'type':
  308. $this->options['output']['forceType'] = $v;
  309. break;
  310.  
  311. default:
  312. break;
  313. }
  314. }
  315.  
  316. /**
  317.   * load
  318.   *
  319.   * Loads the image and saves the details
  320.   *
  321.   * @return void
  322.   * @access private
  323.   * @author Dom Hastings
  324.   */
  325. private function load($options = array()) {
  326. // merge in the options
  327. $options = array_merge_recursive_distinct(
  328. (is_array($this->options['load'])) ? $this->options['load'] : array(),
  329. (is_array($options)) ? $options : array()
  330. );
  331.  
  332. // get the image details stored
  333. $this->info();
  334.  
  335. // if we're forcing a read type
  336. if (!empty($options['forceType'])) {
  337. // use it
  338. $imageType = $options['forceType'];
  339.  
  340. } else {
  341. // otherwise use the discovered type
  342. $imageType = $this->info[2];
  343. }
  344.  
  345. $this->source = $this->current = $this->loadFile($this->filename, $imageType);
  346.  
  347. // if the image loading failed
  348. if (!$this->source) {
  349. throw new Exception('Imgae::load: Unable to load image \''.$this->filename.'\'.');
  350. }
  351. }
  352.  
  353. /**
  354.   * loadFile
  355.   *
  356.   * Loads an image image from a file
  357.   *
  358.   * @param string f The filename
  359.   * @param string imageType The type of image
  360.   * @return resource The loaded image
  361.   * @access private
  362.   * @author Dom Hastings
  363.   */
  364. private function loadFile($f = null, $imageType = null) {
  365. // switch the type and load using the correct function
  366. switch ($imageType) {
  367. case IMAGETYPE_GIF:
  368. $resource = imagecreatefromgif($this->filename);
  369. break;
  370.  
  371. case IMAGETYPE_JPEG:
  372. case IMAGETYPE_JPEG2000:
  373. case IMAGETYPE_JPC:
  374. case IMAGETYPE_JP2:
  375. case IMAGETYPE_JPX:
  376. $resource = imagecreatefromjpeg($this->filename);
  377. break;
  378.  
  379. case IMAGETYPE_PNG:
  380. $resource = imagecreatefrompng($this->filename);
  381. break;
  382.  
  383. case IMAGETYPE_BMP:
  384. case IMAGETYPE_WBMP:
  385. $resource = imagecreatefromwbmp($this->filename);
  386. break;
  387.  
  388. case IMAGETYPE_XBM:
  389. $resource = imagecreatefromxbm($this->filename);
  390. break;
  391.  
  392. case IMAGETYPE_TIFF_II:
  393. case IMAGETYPE_TIFF_MM:
  394. case IMAGETYPE_IFF:
  395. case IMAGETYPE_JB2:
  396. case IMAGETYPE_SWF:
  397. case IMAGETYPE_PSD:
  398. case IMAGETYPE_SWC:
  399. // case IMAGETYPE_ICO:
  400. default:
  401. $resource = null;
  402. break;
  403. }
  404.  
  405. return $resource;
  406. }
  407.  
  408. /**
  409.   * output
  410.   *
  411.   * Output the image
  412.   *
  413.   * @param string $f (Optional) The filename to output to, if this is omitted the image is output to the browser
  414.   * @return void
  415.   * @access private
  416.   * @author Dom Hastings
  417.   */
  418. public function output($f = null, $options = array()) {
  419. // merge in the options
  420. $options = array_merge_recursive_distinct(
  421. (is_array($this->options['output'])) ? $this->options['output'] : array(),
  422. (is_array($options)) ? $options : array()
  423. );
  424.  
  425. // if we're forcing an output type
  426. if (!empty($options['forceType'])) {
  427. $imageType = $options['forceType'];
  428.  
  429. } else {
  430. $imageType = $this->info[2];
  431. }
  432.  
  433. // use the correct output function
  434. switch ($imageType) {
  435. case IMAGETYPE_GIF:
  436. header('Content-type: '.image_type_to_mime_type($imageType));
  437. imagegif($this->current, $f);
  438. break;
  439.  
  440. case IMAGETYPE_JPEG:
  441. case IMAGETYPE_JPEG2000:
  442. case IMAGETYPE_JPC:
  443. case IMAGETYPE_JP2:
  444. case IMAGETYPE_JPX:
  445. header('Content-type: '.image_type_to_mime_type($imageType));
  446. imagejpeg($this->current, $f, $options['jpeg']['quality']);
  447. break;
  448.  
  449. case IMAGETYPE_PNG:
  450. header('Content-type: '.image_type_to_mime_type($imageType));
  451. imagepng($this->current, $f, $options['png']['quality'], $options['png']['filters']);
  452. break;
  453.  
  454. case IMAGETYPE_BMP:
  455. case IMAGETYPE_WBMP:
  456. header('Content-type: '.image_type_to_mime_type($imageType));
  457. imagewbmp($this->current, $f);
  458. break;
  459.  
  460. case IMAGETYPE_XBM:
  461. header('Content-type: '.image_type_to_mime_type($imageType));
  462. imagexbm($this->current, $f);
  463. break;
  464.  
  465. case IMAGETYPE_TIFF_II:
  466. case IMAGETYPE_TIFF_MM:
  467. case IMAGETYPE_IFF:
  468. case IMAGETYPE_JB2:
  469. case IMAGETYPE_SWF:
  470. case IMAGETYPE_PSD:
  471. case IMAGETYPE_SWC:
  472. // case IMAGETYPE_ICO:
  473. default:
  474. break;
  475. }
  476. }
  477.  
  478. /**
  479.   * write
  480.   *
  481.   * Writes the output data to the specified filename
  482.   *
  483.   * @param string $f The filename
  484.   * @return string The filename written to
  485.   * @access public
  486.   * @author Dom Hastings
  487.   */
  488. public function write($f, $options = array()) {
  489. // merge in the options
  490. $options = array_merge_recursive_distinct(
  491. (is_array($this->options['output']['file'])) ? $this->options['output']['file'] : array(),
  492. (is_array($options)) ? $options : array()
  493. );
  494.  
  495. if ($this->options['output']['forceType']) {
  496. $imageType = $this->options['output']['forceType'];
  497.  
  498. } else {
  499. $imageType = $this->info[2];
  500. }
  501.  
  502. if ($options['extension'] || strpos($f, '.') === false) {
  503. $f .= $this->extension;
  504. }
  505.  
  506. $this->output($f);
  507.  
  508. return $f;
  509. }
  510.  
  511. /**
  512.   * resource
  513.   *
  514.   * Returns the current image as a resource
  515.   *
  516.   * @return void
  517.   * @access public
  518.   * @author Dom Hastings
  519.   */
  520. public function resource() {
  521. return $this->current;
  522. }
  523.  
  524. /**
  525.   * info
  526.   *
  527.   * Gets information about the current image
  528.   *
  529.   * @return void
  530.   * @access private
  531.   * @author Dom Hastings
  532.   */
  533. private function info($f = null) {
  534. // if the filename is empty
  535. if (empty($f)) {
  536. // stores the image information inside the object
  537. $this->info = getimagesize($this->filename);
  538.  
  539. } else {
  540. // it's not the main image so return it directly
  541. return getimagesize($f);
  542. }
  543. }
  544.  
  545. /**
  546.   * x
  547.   *
  548.   * Returns the width of the image
  549.   *
  550.   * @param string $a Reads the image directly, otherwise uses the cached information form load
  551.   * @return integer The width of the image
  552.   * @access public
  553.   * @author Dom Hastings
  554.   */
  555. public function x($a = false) {
  556. if ($a) {
  557. return imagesx($this->source);
  558.  
  559. } else {
  560. if (empty($this->info)) {
  561. $this->info();
  562. }
  563.  
  564. return $this->info[0];
  565. }
  566. }
  567.  
  568. /**
  569.   * currentX
  570.   *
  571.   * Returns the width of the thumb image
  572.   *
  573.   * @param string $a Reads the image directly, otherwise uses the cached information form load
  574.   * @return integer The width of the image
  575.   * @access public
  576.   * @author Dom Hastings
  577.   */
  578. public function currentX() {
  579. if ($this->current) {
  580. return imagesx($this->current);
  581. }
  582. }
  583.  
  584. /**
  585.   * y
  586.   *
  587.   * Returns the height of the image
  588.   *
  589.   * @param boolean $a Reads the image directly, otherwise uses the cached information form load
  590.   * @return integer The height of the image
  591.   * @access public
  592.   * @author Dom Hastings
  593.   */
  594. public function y($a = false) {
  595. if ($a) {
  596. return imagesy($this->source);
  597.  
  598. } else {
  599. if (empty($this->info)) {
  600. $this->info();
  601. }
  602.  
  603. return $this->info[1];
  604. }
  605. }
  606.  
  607. /**
  608.   * currentY
  609.   *
  610.   * Returns the height of the current image
  611.   *
  612.   * @param boolean $a Reads the image directly, otherwise uses the cached information form load
  613.   * @return integer The height of the image
  614.   * @access public
  615.   * @author Dom Hastings
  616.   */
  617. public function currentY($a = false) {
  618. if ($this->current) {
  619. return imagesy($this->current);
  620. }
  621. }
  622.  
  623. /**
  624.   * scale
  625.   *
  626.   * Scales the current image to the dimensions specified, using the options specified
  627.   *
  628.   * @param integer $x The desired width
  629.   * @param integer $y The desired height
  630.   * @param array $options See main options block at top of file
  631.   * @return resource The new image
  632.   * @access public
  633.   * @author Dom Hastings
  634.   */
  635. public function scale($x, $y, $options = array()) {
  636. // merge in the options
  637. $options = array_merge_recursive_distinct(
  638. (is_array($this->options['scale'])) ? $this->options['scale'] : array(),
  639. (is_array($options)) ? $options : array()
  640. );
  641.  
  642. // if we're not forcing the size
  643. if (empty($options['force'])) {
  644. // check we're not trying to enlarge the image
  645. if ($x > $this->x) {
  646. $x = $this->x;
  647. }
  648.  
  649. if ($y > $this->y) {
  650. $y = $this->y;
  651. }
  652.  
  653. // if neither dimension is specified
  654. if ($x == 0 && $y == 0) {
  655. throw new Exception('Image::scale: At least one dimension must be spcified to scale an image.');
  656.  
  657. } elseif ($x > 0 && $y > 0) {
  658. // maths!
  659. $destX = $x;
  660. $destY = intval($x / $this->ratio);
  661.  
  662. if ($destY > $y) {
  663. $destX = intval($y * $this->ratio);
  664. $destY = $y;
  665. }
  666.  
  667. } elseif ($x == 0) {
  668. $destX = intval($y * $this->ratio);
  669. $destY = $y;
  670.  
  671. } elseif ($y == 0) {
  672. $destX = $x;
  673. $destY = intval($x / $this->ratio);
  674. }
  675.  
  676. } else {
  677. $destX = $x;
  678. $destY = $y;
  679. }
  680.  
  681. // create the destination
  682. $dest = imagecreatetruecolor($destX, $destY);
  683.  
  684. // resample the image as specified
  685. if (!imagecopyresampled($dest, $this->source, 0, 0, 0, 0, $destX, $destY, $this->x, $this->y)) {
  686. throw new Exception('Image::scale: Error scaling image');
  687. }
  688.  
  689. $this->current = $dest;
  690.  
  691. return $dest;
  692. }
  693.  
  694. /**
  695.   * cutout
  696.   *
  697.   * Returns a selected portion of the image after optionally resizing it
  698.   *
  699.   * @param integer $x The desired width
  700.   * @param integer $y The desired height
  701.   * @param array $options
  702.   * @return resource The new image
  703.   * @access public
  704.   * @author Dom Hastings
  705.   */
  706. public function cutout($x, $y, $options = array()) {
  707. // merge in the options
  708. $options = array_merge_recursive_distinct(
  709. (is_array($this->options['cutout'])) ? $this->options['cutout'] : array(),
  710. (is_array($options)) ? $options : array()
  711. );
  712.  
  713. // if the source image dimensions haven't been specified, work them out as best you can
  714. if (empty($options['scaleX']) && empty($options['scaleY'])) {
  715. // more maths!
  716. if ($this->x >= $this->y) {
  717. // landscape
  718. $scaleX = intval($y * $this->ratio);
  719. $scaleY = $y;
  720.  
  721. if ($scaleX < $x) {
  722. $scaleX = $x;
  723. $scaleY = intval($x / $this->ratio);
  724. }
  725.  
  726. } else {
  727. // portrait
  728. $scaleX = $x;
  729. $scaleY = intval($x / $this->ratio);
  730.  
  731. if ($scaleY < $y) {
  732. $scaleX = intval($y * $this->ratio);
  733. $scaleY = $y;
  734. }
  735. }
  736.  
  737. } else {
  738. $scaleX = $options['scaleX'];
  739. $scaleY = $options['scaleY'];
  740. }
  741.  
  742. // scale the image
  743. $source = $this->scale($scaleX, $scaleY, array('force' => true));
  744.  
  745. // if the offset hasn't been specified
  746. if (!isset($options['offsetX']) || !isset($options['offsetY'])) {
  747. // calculate the center
  748. $offsetX = intval(($scaleX / 2) - ($x / 2));
  749. $offsetY = intval(($scaleY / 2) - ($y / 2));
  750.  
  751. } else {
  752. $offsetX = $options['offsetX'];
  753. $offsetY = $options['offsetY'];
  754. }
  755.  
  756. // create the destination
  757. $dest = imagecreatetruecolor($x, $y);
  758.  
  759. // cut it out
  760. if (!imagecopy($dest, $source, 0, 0, $offsetX, $offsetY, $scaleX, $scaleY)) {
  761. throw new Exception('Image::scale: Error cutting out image');
  762. }
  763.  
  764. $this->current = $dest;
  765.  
  766. return $dest;
  767. }
  768.  
  769. /**
  770.   * whitespace
  771.   *
  772.   * 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
  773.   *
  774.   * @param string $x
  775.   * @param string $y
  776.   * @param string $options
  777.   * @return void
  778.   * @access public
  779.   * @author Dom Hastings
  780.   */
  781. public function whitespace($x, $y, $options = array()) {
  782. // merge in the options
  783. $options = array_merge_recursive_distinct(
  784. (is_array($this->options['whitespace'])) ? $this->options['whitespace'] : array(),
  785. (is_array($options)) ? $options : array()
  786. );
  787.  
  788. // if we're using an image background
  789. if (!empty($options['image'])) {
  790. // load it
  791. $orig = new Image($options['image']);
  792.  
  793. $orig->scale($x, $y, array('force' => true));
  794.  
  795. $dest = $orig->resource();
  796.  
  797. // else if it's just a colour
  798. } elseif (!empty($options['color'])) {
  799. // create the base image
  800. $dest = imagecreatetruecolor($x, $y);
  801.  
  802. // extract the int values of the colour
  803. list($r, $g, $b) = $this->hexToRGB($options['color']);
  804.  
  805. // allocate the colour
  806. $color = imagecolorallocatealpha($dest, $r, $g, $b, $options['transparency']);
  807.  
  808. // fill it
  809. imagefill($dest, 0, 0, $color);
  810.  
  811. // else, we aren't keeping any whitespace, so just scale it
  812. } else {
  813. return $this->scale($x, $y);
  814. }
  815.  
  816. // if scaling options have been set
  817. if (!empty($options['scaleX']) || !empty($options['scaleY'])) {
  818. // use them
  819. $scaleX = $options['scaleX'];
  820. $scaleY = $options['scaleY'];
  821.  
  822. $options = array(
  823. 'force' => true
  824. );
  825.  
  826. } else {
  827. // otherwise assume the passed options
  828. $scaleX = $x;
  829. $scaleY = $y;
  830.  
  831. $options = array();
  832. }
  833.  
  834. // scale the image
  835. $source = $this->scale($scaleX, $scaleY, $options);
  836.  
  837. // extract the new height and width
  838. $scaleX = $this->currentX;
  839. $scaleY = $this->currentY;
  840.  
  841. // determine the offset
  842. if (!isset($options['offsetX']) || !isset($options['offsetY'])) {
  843. $offsetX = intval(($x / 2) - ($scaleX / 2));
  844. $offsetY = intval(($y / 2) - ($scaleY / 2));
  845.  
  846. } else {
  847. $offsetX = $options['offsetX'];
  848. $offsetY = $options['offsetY'];
  849. }
  850.  
  851. // overlay it
  852. if (!imagecopy($dest, $source, $offsetX, $offsetY, 0, 0, $scaleX, $scaleY)) {
  853. throw new Exception('Image::scale: Error whitespacing image');
  854. }
  855.  
  856. $this->current = $dest;
  857.  
  858. return $dest;
  859. }
  860.  
  861. /**
  862.   * watermark
  863.   *
  864.   * Watermarks the current image with the specified image
  865.   *
  866.   * @param string $i The image to use as a watermark
  867.   * @param array $options The options
  868.   * @return resource The watermarked image
  869.   * @access public
  870.   * @author Dom Hastings
  871.   */
  872. public function watermark($i, $options = array()) {
  873. // merge in the options
  874. $options = array_merge_recursive_distinct(
  875. (is_array($this->options['watermark'])) ? $this->options['watermark'] : array(),
  876. (is_array($options)) ? $options : array()
  877. );
  878.  
  879. if (!file_exists($i)) {
  880. throw new Exception('Image::watermark: Missing watermark image \''.$i.'\'.');
  881. }
  882.  
  883. $dest = $this->current;
  884.  
  885. // load the watermark
  886. $watermark = new Image($i);
  887.  
  888. // determine the offset
  889. if (!isset($options['offsetX']) || !isset($options['offsetY'])) {
  890. $offsetX = intval(($this->currentX / 2) - ($watermark->currentX / 2));
  891. $offsetY = intval(($this->currentY / 2) - ($watermark->currentY / 2));
  892.  
  893. } else {
  894. $offsetX = $options['offsetX'];
  895. $offsetY = $options['offsetY'];
  896. }
  897.  
  898. // overlay it
  899. if (!empty($options['repeatX']) && !empty($options['repeatY'])) {
  900. $offsetX = $offsetY = 0;
  901.  
  902. // rows
  903. for ($i = $offsetY; $i < $this->currentY; $i += $watermark->y) {
  904. // cols
  905. for ($j = $offsetX; $j < $this->currentX; $j += $watermark->x) {
  906. if (!imagecopy($dest, $watermark->resource(), $j, $i, 0, 0, $watermark->x, $watermark->y)) {
  907. throw new Exception('Image::scale: Error watermarking image.');
  908. }
  909. }
  910. }
  911.  
  912. } elseif (!empty($options['repeatX'])) {
  913. $offsetX = 0;
  914.  
  915. for ($i = $offsetX; $i <= $this->currentX; $i += $watermark->x) {
  916. if (!imagecopy($dest, $watermark->resource(), $i, $offsetY, 0, 0, $watermark->x, $watermark->y)) {
  917. throw new Exception('Image::scale: Error watermarking image.');
  918. }
  919. }
  920.  
  921. } elseif (!empty($options['repeatY'])) {
  922. $offsetY = 0;
  923.  
  924. for ($i = $offsetY; $i <= $this->currentY; $i += $watermark->y) {
  925. if (!imagecopy($dest, $watermark->resource(), $offsetX, $i, 0, 0, $watermark->x, $watermark->y)) {
  926. throw new Exception('Image::scale: Error watermarking image.');
  927. }
  928. }
  929.  
  930. } else {
  931. if (!imagecopy($dest, $watermark->resource(), $offsetX, $offsetY, 0, 0, $watermark->x, $watermark->y)) {
  932. throw new Exception('Image::scale: Error watermarking image.');
  933. }
  934. }
  935.  
  936. $this->current = $dest;
  937.  
  938. return $dest;
  939. }
  940.  
  941. /**
  942.   * hexToRGB
  943.   *
  944.   * Returns the integer colour values from an HTML hex code
  945.   *
  946.   * @param string $h The HTML hex code
  947.   * @return array The integer colour values
  948.   * @access public
  949.   * @author Dom Hastings
  950.   */
  951. private function hexToRGB($h) {
  952. // strip off the # if it's there
  953. $h = trim($h, '#');
  954.  
  955. if (strlen($h) == 6) {
  956. return array(
  957. hexdec(substr($h, 0, 2)),
  958. hexdec(substr($h, 2, 2)),
  959. hexdec(substr($h, 4, 2))
  960. );
  961.  
  962. } elseif (strlen($h) == 3) {
  963. return array(
  964. hexdec(substr($h, 0, 1).substr($h, 0, 1)),
  965. hexdec(substr($h, 1, 1).substr($h, 1, 1)),
  966. hexdec(substr($h, 2, 1).substr($h, 2, 1))
  967. );
  968.  
  969. } else {
  970. // default to white
  971. return array(255, 255, 255);
  972. }
  973. }
  974.  
  975. /**
  976.   * addText
  977.   *
  978.   * Adds the specified text to the image at the specified location
  979.   *
  980.   * @param string $t The text to add to the image
  981.   * @param integer $x The x co-ordinate of the text
  982.   * @param integer $y The y co-ordinate of the text
  983.   * @param array $options The options
  984.   * @return array Results from imagettftext()
  985.   * @author Dom Hastings
  986.   */
  987. function addText($text, $x, $y, $options = array()) {
  988. // merge in the options
  989. $options = array_merge_recursive_distinct(
  990. (is_array($this->options['text'])) ? $this->options['text'] : array(),
  991. (is_array($options)) ? $options : array()
  992. );
  993.  
  994. // check the font file exists
  995. if (substr($options['font'], 0, 1) == '/') {
  996. if (!file_exists($options['font'])) {
  997. throw new Exception('Imge::addText: Unable to find font file \''.$options['font'].'\'');
  998. }
  999.  
  1000. } else {
  1001. if (!file_exists($options['font'].'.ttf')) {
  1002. throw new Exception('Imge::addText: Unable to find font file \''.$options['font'].'\'');
  1003. }
  1004. }
  1005.  
  1006. list($r, $g, $b) = $this->hexToRGB($options['color']);
  1007.  
  1008. $colour = imagecolorallocatealpha($this->current, $r, $g, $b, $options['transparency']);
  1009.  
  1010. return imagettftext($this->current, $options['size'], $options['angle'], $x, $y, $colour, $options['font'], $text);
  1011. }
  1012.  
  1013. /**
  1014.   * drawLine
  1015.   *
  1016.   * Draws a line from the co-ordinates in array start to the co-ordinates in array finish using the GD library function
  1017.   *
  1018.   * @param array $start The start point index 0 should be the x co-ordinate, 1 the y
  1019.   * @param array $finish The end point index 0 should be the x co-ordinate, 1 the y
  1020.   * @param array $options The options
  1021.   * @return mixed The result from imageline()
  1022.   * @author Dom Hastings
  1023.   */
  1024. function drawLine($start, $finish, $options = array()) {
  1025. // merge in the options
  1026. $options = array_merge_recursive_distinct(
  1027. (is_array($this->options['line'])) ? $this->options['line'] : array(),
  1028. (is_array($options)) ? $options : array()
  1029. );
  1030.  
  1031. imagesetthickness($this->current, $options['size']);
  1032.  
  1033. if (!is_array($start) || !is_array($finish)) {
  1034. throw new Exception('Image::drawLine: Arguments 0 and 1 must be arrays.');
  1035. }
  1036.  
  1037. list($sX, $sY, $fX, $fY) = array_merge(array_values($start), array_values($finish));
  1038.  
  1039. list($r, $g, $b) = $this->hexToRGB($options['color']);
  1040.  
  1041. $colour = imagecolorallocatealpha($this->current, $r, $g, $b, $options['transparency']);
  1042.  
  1043. if (!empty($options['style'])) {
  1044. imagesetstyle($this->current, $options['style']);
  1045. }
  1046.  
  1047. return imageline($this->current, $sX, $sY, $fX, $fY, $colour);
  1048. }
  1049.  
  1050. /**
  1051.   * drawBox
  1052.   *
  1053.   * Draws a box from the co-ordinates in array start to the co-ordinates in array finish using the GD library function
  1054.   *
  1055.   * @param array $start The start point index 0 should be the x co-ordinate, 1 the y
  1056.   * @param array $finish The end point index 0 should be the x co-ordinate, 1 the y
  1057.   * @param array $options The options
  1058.   * @return mixed The result from imagerectangle()
  1059.   * @author Dom Hastings
  1060.   */
  1061. function drawBox($start, $finish, $options = array()) {
  1062. // merge in the options
  1063. $options = array_merge_recursive_distinct(
  1064. (is_array($this->options['box'])) ? $this->options['box'] : array(),
  1065. (is_array($options)) ? $options : array()
  1066. );
  1067.  
  1068. imagesetthickness($this->current, $options['size']);
  1069.  
  1070. if (!is_array($start) || !is_array($finish)) {
  1071. throw new Exception('Image::drawLine: Arguments 0 and 1 must be arrays.');
  1072. }
  1073.  
  1074. list($sX, $sY, $fX, $fY) = array_merge(array_values($start), array_values($finish));
  1075.  
  1076. list($r, $g, $b) = $this->hexToRGB($options['color']);
  1077.  
  1078. $colour = imagecolorallocatealpha($this->current, $r, $g, $b, $options['transparency']);
  1079.  
  1080. if (empty($options['filled'])) {
  1081. if (!empty($options['style'])) {
  1082. imagesetstyle($this->current, $options['style']);
  1083. }
  1084.  
  1085. return imagerectangle($this->current, $sX, $sY, $fX, $fY, $colour);
  1086.  
  1087. } else {
  1088. return imagefilledrectangle($this->current, $sX, $sY, $fX, $fY, $colour);
  1089. }
  1090. }
  1091. }
  1092.  
  1093. /**
  1094.  * array_merge_recursive_distinct
  1095.  *
  1096.  * Recursively process an array merge all child nodes together
  1097.  *
  1098.  * @return array
  1099.  * @author Dom Hastings
  1100.  */
  1101. if (!function_exists('array_merge_recursive_distinct')) {
  1102. function array_merge_recursive_distinct() {
  1103. switch (func_num_args()) {
  1104. case 0:
  1105. return array();
  1106. break;
  1107.  
  1108. case 1:
  1109. return (array) func_get_arg(0);
  1110. break;
  1111.  
  1112. default:
  1113. $a = func_get_args();
  1114. $s = (array) array_shift($a);
  1115.  
  1116. foreach ($a as $i => $b) {
  1117. if (!is_array($b)) {
  1118. $b = (array) $b;
  1119. }
  1120.  
  1121. foreach ($b as $k => $v) {
  1122. if (is_numeric($k)) {
  1123. $s[] = $v;
  1124.  
  1125. } else {
  1126. if (isset($s[$k])) {
  1127. if (is_array($s[$k]) && is_array($v)) {
  1128. $s[$k] = array_merge_recursive_distinct($s[$k], $v);
  1129.  
  1130. } else {
  1131. $s[$k] = $v;
  1132. }
  1133.  
  1134. } else {
  1135. $s[$k] = $v;
  1136. }
  1137. }
  1138. }
  1139. }
  1140. break;
  1141. }
  1142.  
  1143. return $s;
  1144. }
  1145. }

Report this snippet  

You need to login to post a comment.